mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-14 11:11:29 +03:00
Improved archive viewer
Added archive importing
This commit is contained in:
@@ -578,18 +578,18 @@ paths:
|
|||||||
description: If the archive dir is not found, 404 is sent as a response
|
description: If the archive dir is not found, 404 is sent as a response
|
||||||
security:
|
security:
|
||||||
- Auth query parameter: []
|
- Auth query parameter: []
|
||||||
/api/deleteArchiveItem:
|
/api/deleteArchiveItems:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- archive
|
- archive
|
||||||
summary: Delete item from archive
|
summary: Delete item from archive
|
||||||
description: 'Deletes an item from the archive'
|
description: 'Deletes an item from the archive'
|
||||||
operationId: post-api-deleteArchiveItem
|
operationId: post-api-deleteArchiveItems
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/DeleteArchiveItemRequest'
|
$ref: '#/components/schemas/DeleteArchiveItemsRequest'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: OK
|
description: OK
|
||||||
@@ -608,7 +608,7 @@ paths:
|
|||||||
operationId: post-api-importArchive
|
operationId: post-api-importArchive
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
multipart/form-data:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ImportArchiveRequest'
|
$ref: '#/components/schemas/ImportArchiveRequest'
|
||||||
responses:
|
responses:
|
||||||
@@ -2178,21 +2178,15 @@ components:
|
|||||||
type: number
|
type: number
|
||||||
uid:
|
uid:
|
||||||
type: string
|
type: string
|
||||||
DeleteArchiveItemRequest:
|
DeleteArchiveItemsRequest:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- extractor
|
- archives
|
||||||
- id
|
|
||||||
- type
|
|
||||||
properties:
|
properties:
|
||||||
extractor:
|
archives:
|
||||||
type: string
|
type: array
|
||||||
id:
|
items:
|
||||||
type: string
|
$ref: '#/components/schemas/Archive'
|
||||||
type:
|
|
||||||
$ref: '#/components/schemas/FileType'
|
|
||||||
sub_id:
|
|
||||||
type: string
|
|
||||||
ImportArchiveRequest:
|
ImportArchiveRequest:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@@ -2201,7 +2195,6 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
archive:
|
archive:
|
||||||
type: string
|
type: string
|
||||||
format: binary
|
|
||||||
type:
|
type:
|
||||||
$ref: '#/components/schemas/FileType'
|
$ref: '#/components/schemas/FileType'
|
||||||
sub_id:
|
sub_id:
|
||||||
|
|||||||
@@ -1511,6 +1511,22 @@ app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/getArchives', optionalJwt, async (req, res) => {
|
||||||
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
const sub_id = req.body.sub_id;
|
||||||
|
const filter_obj = {user_uid: uuid, sub_id: sub_id};
|
||||||
|
const type = req.body.type;
|
||||||
|
|
||||||
|
// we do this for file types because if type is null, that means get files of all types
|
||||||
|
if (type) filter_obj['type'] = type;
|
||||||
|
|
||||||
|
const archives = await db_api.getRecords('archives', filter_obj);
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
archives: archives
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/downloadArchive', optionalJwt, async (req, res) => {
|
app.post('/api/downloadArchive', optionalJwt, async (req, res) => {
|
||||||
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
const sub_id = req.body.sub_id;
|
const sub_id = req.body.sub_id;
|
||||||
@@ -1528,6 +1544,36 @@ app.post('/api/downloadArchive', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/importArchive', optionalJwt, async (req, res) => {
|
||||||
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
const archive = req.body.archive;
|
||||||
|
const sub_id = req.body.sub_id;
|
||||||
|
const type = req.body.type;
|
||||||
|
|
||||||
|
const archive_text = Buffer.from(archive.split(',')[1], 'base64').toString();
|
||||||
|
|
||||||
|
const imported_count = await archive_api.importArchiveFile(archive_text, type, uuid, sub_id);
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: !!imported_count,
|
||||||
|
imported_count: imported_count
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/deleteArchiveItems', optionalJwt, async (req, res) => {
|
||||||
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
const archives = req.body.archives;
|
||||||
|
|
||||||
|
let success = true;
|
||||||
|
for (const archive of archives) {
|
||||||
|
success &= await archive_api.removeFromArchive(archive['extractor'], archive['id'], archive['type'], uuid, archive['sub_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: success
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var upload_multer = multer({ dest: __dirname + '/appdata/' });
|
var upload_multer = multer({ dest: __dirname + '/appdata/' });
|
||||||
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
|
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
|
||||||
const new_path = path.join(__dirname, 'appdata', 'cookies.txt');
|
const new_path = path.join(__dirname, 'appdata', 'cookies.txt');
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ exports.importArchiveFile = async (archive_text, type, user_uid = null, sub_id =
|
|||||||
// we can't do a bulk write because we need to avoid duplicate archive items existing in db
|
// we can't do a bulk write because we need to avoid duplicate archive items existing in db
|
||||||
|
|
||||||
const archive_item = createArchiveItem(extractor, id, type, null, user_uid, sub_id);
|
const archive_item = createArchiveItem(extractor, id, type, null, user_uid, sub_id);
|
||||||
await db_api.insertRecordIntoTable('archives', archive_item, {extractor: extractor, id: id});
|
await db_api.insertRecordIntoTable('archives', archive_item, {extractor: extractor, id: id, type: type, sub_id: sub_id, user_uid: user_uid});
|
||||||
archive_import_count++;
|
archive_import_count++;
|
||||||
}
|
}
|
||||||
return archive_import_count;
|
return archive_import_count;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export type { DatabaseFile } from './models/DatabaseFile';
|
|||||||
export { DBBackup } from './models/DBBackup';
|
export { DBBackup } from './models/DBBackup';
|
||||||
export type { DBInfoResponse } from './models/DBInfoResponse';
|
export type { DBInfoResponse } from './models/DBInfoResponse';
|
||||||
export type { DeleteAllFilesResponse } from './models/DeleteAllFilesResponse';
|
export type { DeleteAllFilesResponse } from './models/DeleteAllFilesResponse';
|
||||||
export type { DeleteArchiveItemRequest } from './models/DeleteArchiveItemRequest';
|
export type { DeleteArchiveItemsRequest } from './models/DeleteArchiveItemsRequest';
|
||||||
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
|
export type { DeleteCategoryRequest } from './models/DeleteCategoryRequest';
|
||||||
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
|
export type { DeleteMp3Mp4Request } from './models/DeleteMp3Mp4Request';
|
||||||
export type { DeleteNotificationRequest } from './models/DeleteNotificationRequest';
|
export type { DeleteNotificationRequest } from './models/DeleteNotificationRequest';
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/* istanbul ignore file */
|
|
||||||
/* tslint:disable */
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import type { FileType } from './FileType';
|
|
||||||
|
|
||||||
export type DeleteArchiveItemRequest = {
|
|
||||||
extractor: string;
|
|
||||||
id: string;
|
|
||||||
type: FileType;
|
|
||||||
sub_id?: string;
|
|
||||||
};
|
|
||||||
9
src/api-types/models/DeleteArchiveItemsRequest.ts
Normal file
9
src/api-types/models/DeleteArchiveItemsRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import type { Archive } from './Archive';
|
||||||
|
|
||||||
|
export type DeleteArchiveItemsRequest = {
|
||||||
|
archives: Array<Archive>;
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
import type { FileType } from './FileType';
|
import type { FileType } from './FileType';
|
||||||
|
|
||||||
export type ImportArchiveRequest = {
|
export type ImportArchiveRequest = {
|
||||||
archive: Blob;
|
archive: string;
|
||||||
type: FileType;
|
type: FileType;
|
||||||
sub_id?: string;
|
sub_id?: string;
|
||||||
};
|
};
|
||||||
@@ -21,6 +21,10 @@
|
|||||||
<mat-icon>person</mat-icon>
|
<mat-icon>person</mat-icon>
|
||||||
<span i18n="Profile menu label">Profile</span>
|
<span i18n="Profile menu label">Profile</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button *ngIf="postsService.config && postsService.config.Downloader.use_youtubedl_archive" class="top-menu-button" (click)="openArchivesDialog()" mat-menu-item>
|
||||||
|
<mat-icon>topic</mat-icon>
|
||||||
|
<span i18n="Archives menu label">Archives</span>
|
||||||
|
</button>
|
||||||
<button class="top-menu-button" (click)="themeMenuItemClicked($event)" *ngIf="allowThemeChange" mat-menu-item>
|
<button class="top-menu-button" (click)="themeMenuItemClicked($event)" *ngIf="allowThemeChange" mat-menu-item>
|
||||||
<mat-icon>{{(postsService.theme.key === 'default') ? 'brightness_5' : 'brightness_2'}}</mat-icon>
|
<mat-icon>{{(postsService.theme.key === 'default') ? 'brightness_5' : 'brightness_2'}}</mat-icon>
|
||||||
<span i18n="Dark mode toggle label">Dark</span>
|
<span i18n="Dark mode toggle label">Dark</span>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.compon
|
|||||||
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
||||||
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
||||||
import { NotificationsComponent } from './components/notifications/notifications.component';
|
import { NotificationsComponent } from './components/notifications/notifications.component';
|
||||||
|
import { ArchiveViewerComponent } from './components/archive-viewer/archive-viewer.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -207,6 +208,12 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openArchivesDialog(): void {
|
||||||
|
this.dialog.open(ArchiveViewerComponent, {
|
||||||
|
width: '85vw'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
notificationCountUpdate(new_count: number): void {
|
notificationCountUpdate(new_count: number): void {
|
||||||
this.notification_count = new_count;
|
this.notification_count = new_count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
<!-- (selectionChange)="sidePanelModeChanged($event.value)" -->
|
<mat-form-field class="filter">
|
||||||
|
<mat-icon matPrefix>search</mat-icon>
|
||||||
|
<mat-label i18n="Filter">Filter</mat-label>
|
||||||
|
<input matInput (keyup)="applyFilter($event)" #input>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<div [hidden]="!(archives && archives.length > 0)">
|
<div [hidden]="!(archives && archives.length > 0)">
|
||||||
<div style="overflow: hidden;" class="mat-elevation-z8">
|
<div class="mat-elevation-z8">
|
||||||
<mat-table style="overflow: hidden" matSort [dataSource]="dataSource">
|
<mat-table matSort [dataSource]="dataSource">
|
||||||
|
|
||||||
|
<!-- Select Column -->
|
||||||
|
<!-- Checkbox Column -->
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox (change)="$event ? toggleAllRows() : null"
|
||||||
|
[checked]="selection.hasValue() && isAllSelected()"
|
||||||
|
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox (click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selection.toggle(row) : null"
|
||||||
|
[checked]="selection.isSelected(row)">
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-icon class="audio-video-icon">{{(row.type === 'audio') ? 'audiotrack' : 'movie'}}</mat-icon>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Date Column -->
|
<!-- Date Column -->
|
||||||
<ng-container matColumnDef="timestamp">
|
<ng-container matColumnDef="timestamp">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Date">Date</ng-container> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Date">Date</ng-container> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let element"> {{element.timestamp | date: 'short'}} </mat-cell>
|
<mat-cell *matCellDef="let element"> {{element.timestamp*1000 | date: 'short'}} </mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Title Column -->
|
<!-- Title Column -->
|
||||||
<ng-container matColumnDef="title">
|
<ng-container matColumnDef="title">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let element">
|
<mat-cell *matCellDef="let element">
|
||||||
<span class="one-line" [matTooltip]="element.title ? element.title : null">
|
<span class="max-two-lines" [matTooltip]="element.title ? element.title : null">
|
||||||
{{element.title}}
|
{{element.title}}
|
||||||
</span>
|
</span>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
@@ -40,7 +62,7 @@
|
|||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,4 +70,71 @@
|
|||||||
|
|
||||||
<div *ngIf="(!archives || archives.length === 0)">
|
<div *ngIf="(!archives || archives.length === 0)">
|
||||||
<h4 style="text-align: center; margin-top: 10px;" i18n="Archives empty">Archives empty</h4>
|
<h4 style="text-align: center; margin-top: 10px;" i18n="Archives empty">Archives empty</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style="margin: 10px 10px 10px 0px;">
|
||||||
|
<button [disabled]="selection.selected.length === 0" color="warn" style="margin: 10px;" mat-stroked-button i18n="Delete selected" (click)="openDeleteSelectedArchivesDialog()">Delete selected</button>
|
||||||
|
<span style="float: right">
|
||||||
|
<mat-form-field style="width: 150px;">
|
||||||
|
<mat-label i18n="Subscription">Subscription</mat-label>
|
||||||
|
<mat-select [ngModel]="sub_id" (ngModelChange)="subFilterSelectionChanged($event)">
|
||||||
|
<mat-option [value]="'none'" i18n="None">None</mat-option>
|
||||||
|
<mat-option *ngFor="let sub of postsService.subscriptions" [value]="sub.id">{{sub.name}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field style="width: 100px; margin-left: 10px;">
|
||||||
|
<mat-label i18n="File type">File type</mat-label>
|
||||||
|
<mat-select [ngModel]="type" (ngModelChange)="typeFilterSelectionChanged($event)" [disabled]="sub_id !== 'none'">
|
||||||
|
<mat-option [value]="'both'" i18n="Both">Both</mat-option>
|
||||||
|
<mat-option [value]="'video'" i18n="Video">Video</mat-option>
|
||||||
|
<mat-option [value]="'audio'" i18n="Audio">Audio</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ngx-file-drop [multiple]="false" accept=".txt" dropZoneLabel="Drop file here" (onFileDrop)="dropped($event)">
|
||||||
|
<ng-template class="file-drop" ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
|
||||||
|
<div style="text-align: center">
|
||||||
|
<div>
|
||||||
|
<ng-container i18n="Drag and Drop">Drag and Drop</ng-container>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 6px;">
|
||||||
|
<button mat-stroked-button (click)="openFileSelector()">Browse Files</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ngx-file-drop>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 10px; color: white">
|
||||||
|
<table class="table">
|
||||||
|
<tbody class="upload-name-style">
|
||||||
|
<tr *ngFor="let item of files; let i=index">
|
||||||
|
<td style="vertical-align: middle; border-top: unset">
|
||||||
|
<strong>{{ item.relativePath }}</strong>
|
||||||
|
</td>
|
||||||
|
<td style="border-top: unset">
|
||||||
|
<div style="float: right">
|
||||||
|
<mat-form-field style="width: 150px;">
|
||||||
|
<mat-label i18n="Subscription">Subscription</mat-label>
|
||||||
|
<mat-select [ngModel]="upload_sub_id" (ngModelChange)="subUploadFilterSelectionChanged($event)">
|
||||||
|
<mat-option [value]="'none'" i18n="None">None</mat-option>
|
||||||
|
<mat-option *ngFor="let sub of postsService.subscriptions" [value]="sub.id">{{sub.name}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field style="width: 100px; margin-left: 10px">
|
||||||
|
<mat-label i18n="File type">File type</mat-label>
|
||||||
|
<mat-select [(ngModel)]="upload_type" [value]="upload_type" [disabled]="upload_sub_id !== 'none'">
|
||||||
|
<mat-option [value]="'video'" i18n="Video">Video</mat-option>
|
||||||
|
<mat-option [value]="'audio'" i18n="Audio">Audio</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<button style="margin-left: 10px" [disabled]="uploading_archive || uploaded_archive" (click)="importArchive()" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading_archive" class="spinner" [diameter]="38"></mat-spinner></button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
.filter {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
bottom: 1px;
|
||||||
|
left: 0.5px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-table {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-two-lines {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -moz-box;
|
||||||
|
max-height: 2.4em;
|
||||||
|
line-height: 1.2em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .ngx-file-drop__content {
|
||||||
|
width: 100%;
|
||||||
|
top: -12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { FileType } from 'api-types';
|
||||||
import { Archive } from 'api-types/models/Archive';
|
import { Archive } from 'api-types/models/Archive';
|
||||||
|
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
|
||||||
import { PostsService } from 'app/posts.services';
|
import { PostsService } from 'app/posts.services';
|
||||||
|
import { NgxFileDropEntry } from 'ngx-file-drop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-archive-viewer',
|
selector: 'app-archive-viewer',
|
||||||
@@ -10,23 +15,89 @@ import { PostsService } from 'app/posts.services';
|
|||||||
styleUrls: ['./archive-viewer.component.scss']
|
styleUrls: ['./archive-viewer.component.scss']
|
||||||
})
|
})
|
||||||
export class ArchiveViewerComponent {
|
export class ArchiveViewerComponent {
|
||||||
archives = null;
|
// table
|
||||||
displayedColumns: string[] = ['timestamp', 'title', 'id', 'extractor'];
|
displayedColumns: string[] = ['select', 'timestamp', 'title', 'id', 'extractor'];
|
||||||
dataSource = null;
|
dataSource = null;
|
||||||
|
selection = new SelectionModel<Archive>(true, []);
|
||||||
|
|
||||||
|
// general
|
||||||
|
archives = null;
|
||||||
archives_retrieved = false;
|
archives_retrieved = false;
|
||||||
|
sub_id = 'none';
|
||||||
|
upload_sub_id = 'none';
|
||||||
|
type: FileType | 'both' = 'both';
|
||||||
|
upload_type: FileType = FileType.VIDEO;
|
||||||
|
|
||||||
|
// importing
|
||||||
|
uploading_archive = false;
|
||||||
|
uploaded_archive = false;
|
||||||
|
files = [];
|
||||||
|
|
||||||
|
typeSelectOptions = {
|
||||||
|
video: {
|
||||||
|
key: 'video',
|
||||||
|
label: $localize`Video`
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
key: 'audio',
|
||||||
|
label: $localize`Audio`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
constructor(private postsService: PostsService) {
|
constructor(public postsService: PostsService, private dialog: MatDialog) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSelectionChanged(value: string): void {
|
ngOnInit() {
|
||||||
this.getArchives(value);
|
this.getArchives();
|
||||||
}
|
}
|
||||||
|
|
||||||
getArchives(sub_id: string = null): void {
|
applyFilter(event: Event) {
|
||||||
this.postsService.getArchives(sub_id).subscribe(res => {
|
const filterValue = (event.target as HTMLInputElement).value;
|
||||||
|
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the number of selected elements matches the total number of rows. */
|
||||||
|
isAllSelected() {
|
||||||
|
const numSelected = this.selection.selected.length;
|
||||||
|
const numRows = this.dataSource.data.length;
|
||||||
|
return numSelected === numRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Selects all rows if they are not all selected; otherwise clear selection. */
|
||||||
|
toggleAllRows() {
|
||||||
|
if (this.isAllSelected()) {
|
||||||
|
this.selection.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selection.select(...this.dataSource.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
typeFilterSelectionChanged(value): void {
|
||||||
|
this.type = value;
|
||||||
|
this.getArchives();
|
||||||
|
}
|
||||||
|
|
||||||
|
subFilterSelectionChanged(value): void {
|
||||||
|
this.sub_id = value;
|
||||||
|
if (this.sub_id !== 'none') {
|
||||||
|
this.type = this.postsService.getSubscriptionByID(this.sub_id)['type'];
|
||||||
|
}
|
||||||
|
this.getArchives();
|
||||||
|
}
|
||||||
|
|
||||||
|
subUploadFilterSelectionChanged(value): void {
|
||||||
|
this.upload_sub_id = value;
|
||||||
|
if (this.upload_sub_id !== 'none') {
|
||||||
|
this.upload_type = this.postsService.getSubscriptionByID(this.upload_sub_id)['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getArchives(): void {
|
||||||
|
this.postsService.getArchives(this.type === 'both' ? null : this.type, this.sub_id === 'none' ? null : this.sub_id).subscribe(res => {
|
||||||
if (res['archives'] !== null
|
if (res['archives'] !== null
|
||||||
&& res['archives'] !== undefined
|
&& res['archives'] !== undefined
|
||||||
&& JSON.stringify(this.archives) !== JSON.stringify(res['archives'])) {
|
&& JSON.stringify(this.archives) !== JSON.stringify(res['archives'])) {
|
||||||
@@ -38,4 +109,78 @@ export class ArchiveViewerComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importArchive(): void {
|
||||||
|
this.uploading_archive = true;
|
||||||
|
for (const droppedFile of this.files) {
|
||||||
|
// Is it a file?
|
||||||
|
if (droppedFile.fileEntry.isFile) {
|
||||||
|
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
|
||||||
|
fileEntry.file(async (file: File) => {
|
||||||
|
const archive_base64 = await blobToBase64(file);
|
||||||
|
this.postsService.importArchive(archive_base64 as string, this.upload_type, this.upload_sub_id === 'none' ? null : this.upload_sub_id).subscribe(res => {
|
||||||
|
this.uploading_archive = false;
|
||||||
|
if (res['success']) {
|
||||||
|
this.uploaded_archive = true;
|
||||||
|
this.postsService.openSnackBar($localize`Archive successfully imported!`);
|
||||||
|
}
|
||||||
|
this.getArchives();
|
||||||
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
|
this.uploading_archive = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openDeleteSelectedArchivesDialog(): void {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||||
|
data: {
|
||||||
|
dialogTitle: $localize`Delete archives`,
|
||||||
|
dialogText: $localize`Would you like to delete ${this.selection.selected.length}:selected archives amount: archive(s)?`,
|
||||||
|
submitText: $localize`Delete`,
|
||||||
|
warnSubmitColor: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(confirmed => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.deleteSelectedArchives();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelectedArchives(): void {
|
||||||
|
for (const archive of this.selection.selected) {
|
||||||
|
this.archives = this.archives.filter((_archive: Archive) => !(archive['extractor'] === _archive['extractor'] && archive['id'] !== _archive['id']));
|
||||||
|
}
|
||||||
|
this.postsService.deleteArchiveItems(this.selection.selected).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.postsService.openSnackBar($localize`Successfully deleted archive items!`);
|
||||||
|
} else {
|
||||||
|
this.postsService.openSnackBar($localize`Failed to delete archive items!`);
|
||||||
|
}
|
||||||
|
this.getArchives();
|
||||||
|
});
|
||||||
|
this.selection.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public dropped(files: NgxFileDropEntry[]) {
|
||||||
|
this.files = files;
|
||||||
|
this.uploading_archive = false;
|
||||||
|
this.uploaded_archive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
originalOrder = (): number => {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function blobToBase64(blob: Blob) {
|
||||||
|
return new Promise((resolve, _) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => resolve(reader.result);
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<strong>{{ item.relativePath }}</strong>
|
<strong>{{ item.relativePath }}</strong>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button [disabled]="uploading || uploaded" (click)="uploadFile()" style="float: right" matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading" class="spinner" [diameter]="38"></mat-spinner></button>
|
<button [disabled]="uploading || uploaded" (click)="uploadFile()" style="float: right" matTooltip="Upload" i18n-matTooltip="Upload" mat-mini-fab><mat-icon>publish</mat-icon><mat-spinner *ngIf="uploading" class="spinner" [diameter]="38"></mat-spinner></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export class CookiesUploaderDialogComponent implements OnInit {
|
|||||||
this.postsService.openSnackBar($localize`Cookies successfully uploaded!`);
|
this.postsService.openSnackBar($localize`Cookies successfully uploaded!`);
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
this.uploading = false;
|
this.uploading = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -107,9 +107,12 @@ import {
|
|||||||
GetNotificationsResponse,
|
GetNotificationsResponse,
|
||||||
UpdateTaskOptionsRequest,
|
UpdateTaskOptionsRequest,
|
||||||
User,
|
User,
|
||||||
DeleteArchiveItemRequest,
|
DeleteArchiveItemsRequest,
|
||||||
GetArchivesRequest,
|
GetArchivesRequest,
|
||||||
GetArchivesResponse
|
GetArchivesResponse,
|
||||||
|
ImportArchiveRequest,
|
||||||
|
Archive,
|
||||||
|
Subscription
|
||||||
} from '../api-types';
|
} from '../api-types';
|
||||||
import { isoLangs } from './settings/locales_list';
|
import { isoLangs } from './settings/locales_list';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
@@ -159,7 +162,7 @@ export class PostsService implements CanActivate {
|
|||||||
|
|
||||||
// global vars
|
// global vars
|
||||||
config = null;
|
config = null;
|
||||||
subscriptions = null;
|
subscriptions: Subscription[] = null;
|
||||||
categories: Category[] = null;
|
categories: Category[] = null;
|
||||||
sidenav = null;
|
sidenav = null;
|
||||||
locale = isoLangs['en'];
|
locale = isoLangs['en'];
|
||||||
@@ -265,7 +268,7 @@ export class PostsService implements CanActivate {
|
|||||||
this.theme = this.THEMES_CONFIG[theme];
|
this.theme = this.THEMES_CONFIG[theme];
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubscriptionByID(sub_id) {
|
getSubscriptionByID(sub_id: string): Subscription {
|
||||||
for (let i = 0; i < this.subscriptions.length; i++) {
|
for (let i = 0; i < this.subscriptions.length; i++) {
|
||||||
if (this.subscriptions[i]['id'] === sub_id) {
|
if (this.subscriptions[i]['id'] === sub_id) {
|
||||||
return this.subscriptions[i];
|
return this.subscriptions[i];
|
||||||
@@ -446,22 +449,19 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'downloadArchive', body, {responseType: 'blob', params: this.httpOptions.params});
|
return this.http.post(this.path + 'downloadArchive', body, {responseType: 'blob', params: this.httpOptions.params});
|
||||||
}
|
}
|
||||||
|
|
||||||
getArchives(sub_id: string) {
|
getArchives(type: FileType = null, sub_id: string = null) {
|
||||||
const body: GetArchivesRequest = {sub_id: sub_id};
|
const body: GetArchivesRequest = {type: type, sub_id: sub_id};
|
||||||
return this.http.post<GetArchivesResponse>(this.path + 'getArchives', body, this.httpOptions);
|
return this.http.post<GetArchivesResponse>(this.path + 'getArchives', body, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
importArchive(archiveFile: File, type: FileType, sub_id: string = null) {
|
importArchive(archive_base64: string, type: FileType, sub_id: string = null) {
|
||||||
const formData = new FormData()
|
const body: ImportArchiveRequest = {archive: archive_base64, type: type, sub_id: sub_id}
|
||||||
formData.append('archive', archiveFile, 'archive.txt');
|
return this.http.post<SuccessObject>(this.path + 'importArchive', body, this.httpOptions);
|
||||||
formData.append('type', type);
|
|
||||||
formData.append('sub_id', sub_id);
|
|
||||||
return this.http.post<SuccessObject>(this.path + 'importArchive', formData, this.httpOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteArchiveItem(extractor: string, id: string, type: FileType, sub_id: string = null) {
|
deleteArchiveItems(archives: Archive[]) {
|
||||||
const body: DeleteArchiveItemRequest = {extractor: extractor, id: id, type: type, sub_id: sub_id};
|
const body: DeleteArchiveItemsRequest = {archives: archives};
|
||||||
return this.http.post<SuccessObject>(this.path + 'deleteArchiveItem', body, this.httpOptions);
|
return this.http.post<SuccessObject>(this.path + 'deleteArchiveItems', body, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFileFormats(url) {
|
getFileFormats(url) {
|
||||||
|
|||||||
Reference in New Issue
Block a user