From ba16535da9db48f570f790102e7ec305067bab7a Mon Sep 17 00:00:00 2001 From: Everett Summer <55684557+EverettSummer@users.noreply.github.com> Date: Sun, 12 Nov 2023 00:49:28 -0800 Subject: [PATCH] download directly support --- package.json | 2 +- src/app/admin/admin.module.ts | 7 +- src/app/admin/admin.service.ts | 8 ++ .../bangumi-detail.component.ts | 30 ++++--- src/app/admin/bangumi-detail/feed.service.ts | 2 +- .../download-editor.component.ts | 69 +++++++++++++++ .../download-editor/download-editor.html | 38 ++++++++ .../download-editor/download-editor.less | 88 +++++++++++++++++++ .../universal-builder.component.ts | 43 +++++++-- .../universal-builder/universal-builder.html | 8 +- .../universal-builder/universal-builder.less | 8 +- src/app/entity/MediaFile.ts | 6 ++ src/app/entity/item.ts | 4 +- src/helpers/clipboard.ts | 30 +++++++ 14 files changed, 321 insertions(+), 22 deletions(-) create mode 100644 src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.component.ts create mode 100644 src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.html create mode 100644 src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.less create mode 100644 src/app/entity/MediaFile.ts create mode 100644 src/helpers/clipboard.ts diff --git a/package.json b/package.json index 9fceb90..924192e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mira-ui", - "version": "2.5.4", + "version": "2.6.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index 70f6496..0dbe17f 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -65,6 +65,7 @@ import { VertexGraphComponent } from './video-process-job-detail/vertex-graph/ve import { StreamLogViewerComponent } from './video-process-job-detail/stream-log-viewer/stream-log-viewer.component'; import { VertexInfoPanelComponent } from './video-process-job-detail/vertex-info-panel/vertex-info-panel.component'; import { DownloadJobDetailComponent } from './download-manager/download-job-detail/download-job-detail.component'; +import { DownloadEditorComponent } from './bangumi-detail/universal-builder/download-editor/download-editor.component'; @NgModule({ @@ -112,7 +113,8 @@ import { DownloadJobDetailComponent } from './download-manager/download-job-deta VertexGraphComponent, StreamLogViewerComponent, VertexInfoPanelComponent, - DownloadJobDetailComponent + DownloadJobDetailComponent, + DownloadEditorComponent ], providers: [ AdminService, @@ -156,7 +158,8 @@ import { DownloadJobDetailComponent } from './download-manager/download-job-deta VideoProcessRuleEditorComponent, FileMappingListComponent, VertexInfoPanelComponent, - DownloadJobDetailComponent + DownloadJobDetailComponent, + DownloadEditorComponent ] }) export class AdminModule { diff --git a/src/app/admin/admin.service.ts b/src/app/admin/admin.service.ts index 25f767e..bba7576 100644 --- a/src/app/admin/admin.service.ts +++ b/src/app/admin/admin.service.ts @@ -113,4 +113,12 @@ export class AdminService extends BaseService { return this.http.put(`${this.baseUrl}/video-file/${videoFile.id}`, videoFile).pipe( catchError(this.handleError),); } + + downloadDirectly(bangumi_id: string, + urlEpsList: {download_url: string, eps_no: number, file_path: string, file_name: string}[]): Observable { + return this.http.post(`${this.baseUrl}/download-directly`, { + bangumi_id, + url_eps_list: urlEpsList + }).pipe(catchError(this.handleError),); + } } diff --git a/src/app/admin/bangumi-detail/bangumi-detail.component.ts b/src/app/admin/bangumi-detail/bangumi-detail.component.ts index a103456..46e6baa 100644 --- a/src/app/admin/bangumi-detail/bangumi-detail.component.ts +++ b/src/app/admin/bangumi-detail/bangumi-detail.component.ts @@ -3,7 +3,7 @@ import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { UIDialog, UIToast, UIToastComponent, UIToastRef } from '@irohalab/deneb-ui'; import { Subscription } from 'rxjs'; -import { filter, mergeMap } from 'rxjs/operators'; +import { filter, mergeMap, tap } from 'rxjs/operators'; import { BaseError } from '../../../helpers/error'; import { Bangumi, Episode } from '../../entity'; import { Announce } from '../../entity/announce'; @@ -227,16 +227,26 @@ export class BangumiDetail implements OnInit, OnDestroy { filter((result: any) => !!result), mergeMap((result: any) => { this.isLoading = true; - this.bangumi.universal = JSON.stringify(result.result); - return this._adminService.updateBangumi(this.bangumi); + if (result.result === UniversalBuilderComponent.DIALOG_RESULT_DOWNLOAD_DIRECTLY) { + return this._adminService.getBangumi(this.bangumi.id) + .pipe(tap((bangumi) => { + this.bangumi = bangumi; + })); + } else { + this.bangumi.universal = JSON.stringify(result.data); + return this._adminService.updateBangumi(this.bangumi); + } }),) - .subscribe(() => { - this.isLoading = false; - this._toastRef.show('更新成功'); - this.updateAvailableModeCount(); - }, (error) => { - this.isLoading = false; - this._toastRef.show(error.message); + .subscribe({ + next: () => { + this.isLoading = false; + this._toastRef.show('更新成功'); + this.updateAvailableModeCount(); + }, + error: (error) => { + this.isLoading = false; + this._toastRef.show(error.message); + } }) ); } diff --git a/src/app/admin/bangumi-detail/feed.service.ts b/src/app/admin/bangumi-detail/feed.service.ts index 5c464f7..27ca34b 100644 --- a/src/app/admin/bangumi-detail/feed.service.ts +++ b/src/app/admin/bangumi-detail/feed.service.ts @@ -38,7 +38,7 @@ export class FeedService extends BaseService { catchError(this.handleError),); } - queryUniversal(mode: string, keyword: string): Observable> { + queryUniversal(mode: string, keyword: string): Observable { return this.http.post<{data: Array, status: number}>(`${this.baseUrl}/universal`, {mode, keyword}).pipe( map(res => res.data), catchError(this.handleError),); diff --git a/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.component.ts b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.component.ts new file mode 100644 index 0000000..6bb052d --- /dev/null +++ b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.component.ts @@ -0,0 +1,69 @@ +import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MediaFile } from '../../../../entity/MediaFile'; +import { UIDialogRef, UIToast, UIToastComponent, UIToastRef } from '@irohalab/deneb-ui'; +import { Subscription } from 'rxjs'; +import { AdminService } from '../../../admin.service'; +import { Item } from '../../../../entity/item'; +import { copyElementValueToClipboard } from '../../../../../helpers/clipboard'; + +@Component({ + selector: 'download-editor', + templateUrl: './download-editor.html', + styleUrls: ['./download-editor.less'] +}) +export class DownloadEditorComponent implements OnDestroy { + private _subscription = new Subscription(); + private _toastRef: UIToastRef; + + torrentTitle: string; + downloadUrl: string; + + files: MediaFile[]; + + eps_mapping: {eps_no: number, format: string, selected: boolean}[]; + + bangumi_id: string; + + @ViewChild('downloadUrlTextBox', {static: true}) _downloadUrlTextBoxRef: ElementRef; + + constructor(private _adminService: AdminService, + private _dialogRef: UIDialogRef, + toast: UIToast) { + this._toastRef = toast.makeText(); + } + copyDownloadUrlToClipboard(): void { + const downloadUrlInput = this._downloadUrlTextBoxRef.nativeElement; + copyElementValueToClipboard(downloadUrlInput) + this._toastRef.show('已经复制到剪贴板'); + } + download(): void { + this._subscription.add( + this._adminService.downloadDirectly( + this.bangumi_id, + this.eps_mapping.filter(mapping => mapping.selected).map((mapping, idx) => { + return { + download_url: this.downloadUrl, + eps_no: mapping.eps_no, + file_path: this.files[idx].path, + file_name: this.files[idx].name + }; + })) + .subscribe({ + next: () => { + this._dialogRef.close(true); + }, + error: (error) => { + this._toastRef.show(error?.message); + } + }) + ); + } + + cancel(): void { + this._dialogRef.close(false); + } + + ngOnDestroy(): void { + this._subscription.unsubscribe(); + } +} diff --git a/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.html b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.html new file mode 100644 index 0000000..09462a7 --- /dev/null +++ b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.html @@ -0,0 +1,38 @@ +
+
+

{{torrentTitle}}

+
+ + +
+
+
+ + + + + + + + + + + + + + + + + +
File PathSizeEpisodeSelect
{{file.path}}{{file.size | readableUnit:'byte':2}} +
+ + +
+
+
+ +
\ No newline at end of file diff --git a/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.less b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.less new file mode 100644 index 0000000..c0c6516 --- /dev/null +++ b/src/app/admin/bangumi-detail/universal-builder/download-editor/download-editor.less @@ -0,0 +1,88 @@ +.download-editor-container { + text-align: left; + @media(max-width: 767px) { + width: 100%; + } + @media(min-width: 768px) and (max-width: 991px) { + width: 723px; + } + @media(min-width: 992px) and (max-width: 1200px) { + width: 933px; + } + @media(min-width: 1201px) and (max-width: 1599px){ + width: 1127px; + } + @media(min-width: 1601px) { + width: 1600px; + } + @media(min-height: 700px) { + height: 80%; + } + @media(max-height: 639px) { + height: 100%; + } + margin: auto; + top: 0; + left: 0; + right: 0; + bottom: 0; + position: absolute; + border-radius: 4px; + background-color: #fff; + overflow: hidden; + padding-top: 5rem; + .ui.dropdown .menu { + z-index: 310; + } +} + +.torrent-info-wrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 5rem; + border-bottom: 1px solid #cccccc; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: #ffffff; + .torrent-name { + margin: 0.5rem 0.6rem; + } + .download-url-wrapper { + margin-left: 0.6rem; + margin-right: 0.6rem; + } +} + +.file-list-wrapper { + position: relative; + height: 100%; + width: 100%; + padding-bottom: 5rem; + overflow-y: scroll; + overflow-x: auto; + + td.episode-number > input[type=number] { + width: 4.5em; + } +} + +.footer { + position: absolute; + width: 100%; + height: 5rem; + left: 0; + bottom: 0; + padding-right: 1rem; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + border-top: 1px solid #e2e2e2; + background-color: #fff; + z-index: 220; + > .ui.button { + margin-right: 2rem; + } +} \ No newline at end of file diff --git a/src/app/admin/bangumi-detail/universal-builder/universal-builder.component.ts b/src/app/admin/bangumi-detail/universal-builder/universal-builder.component.ts index dcd6770..e52cdad 100644 --- a/src/app/admin/bangumi-detail/universal-builder/universal-builder.component.ts +++ b/src/app/admin/bangumi-detail/universal-builder/universal-builder.component.ts @@ -1,10 +1,11 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { UIDialogRef, UIToast, UIToastComponent, UIToastRef } from '@irohalab/deneb-ui'; +import { UIDialog, UIDialogRef, UIToast, UIToastComponent, UIToastRef } from '@irohalab/deneb-ui'; import { Subscription } from 'rxjs'; -import { Bangumi } from '../../../entity'; +import { Bangumi, Episode } from '../../../entity'; import { Item } from '../../../entity/item'; import { FeedService } from '../feed.service'; +import { DownloadEditorComponent } from './download-editor/download-editor.component'; @Component({ selector: 'universal-builder', @@ -26,7 +27,8 @@ export class UniversalBuilderComponent implements OnInit, OnDestroy { @Input() mode: string; - itemList: Array; + tableItemList: Item[]; + itemList: Item[]; keywordControl: FormControl; @@ -35,8 +37,13 @@ export class UniversalBuilderComponent implements OnInit, OnDestroy { isSearching: boolean; noResultFound: boolean; + static DIALOG_RESULT_UPDATE_BANGUMI = 'update_bangumi'; + static DIALOG_RESULT_DOWNLOAD_DIRECTLY = 'download_directly'; + static DIALOG_RESULT_DELETE = 'delete'; + constructor(private _feedService: FeedService, private _dialogRef: UIDialogRef, + private _uiDialog: UIDialog, toast: UIToast) { this._toastRef = toast.makeText(); } @@ -53,7 +60,7 @@ export class UniversalBuilderComponent implements OnInit, OnDestroy { break; } } - this._dialogRef.close({result: universalList}); + this._dialogRef.close({result: UniversalBuilderComponent.DIALOG_RESULT_DELETE, data: universalList}); } ngOnDestroy(): void { @@ -111,7 +118,7 @@ export class UniversalBuilderComponent implements OnInit, OnDestroy { } else { universalList.push(result); } - this._dialogRef.close({result: universalList}); + this._dialogRef.close({result: UniversalBuilderComponent.DIALOG_RESULT_UPDATE_BANGUMI, data: universalList}); } selectMode(mode: string): void { @@ -138,4 +145,30 @@ export class UniversalBuilderComponent implements OnInit, OnDestroy { }) ); } + + downloadItemDirectly(index: number, item: Item): void { + const dialogRef = this._uiDialog.open(DownloadEditorComponent, {stickyDialog: true, backdrop: true}); + dialogRef.componentInstance.files = item.files + dialogRef.componentInstance.eps_mapping = item.eps_no_list.map(entry => { + const episode = this.bangumi.episodes.find(eps => { + return eps.episode_no === entry.eps_no; + }) + return { + eps_no: entry.eps_no, + format: entry.format, + selected: episode ? episode.status === Episode.STATUS_NOT_DOWNLOADED : false}; + }); + dialogRef.componentInstance.downloadUrl = item.magnet_uri; + dialogRef.componentInstance.bangumi_id = this.bangumi.id; + dialogRef.componentInstance.torrentTitle = item.title; + + this._subscription.add(dialogRef.afterClosed().subscribe({ + next: (result: boolean) => { + if (result) { + this._toastRef.show('已添加下载'); + this._dialogRef.close({result: UniversalBuilderComponent.DIALOG_RESULT_DOWNLOAD_DIRECTLY}); + } + } + })); + } } diff --git a/src/app/admin/bangumi-detail/universal-builder/universal-builder.html b/src/app/admin/bangumi-detail/universal-builder/universal-builder.html index 4f81a2b..d84e7f7 100644 --- a/src/app/admin/bangumi-detail/universal-builder/universal-builder.html +++ b/src/app/admin/bangumi-detail/universal-builder/universal-builder.html @@ -20,12 +20,17 @@ + + + - + + + +
时间发布者 标题 集数Action
{{item.timestamp}}{{item.publisher?.name}} {{item.title}} @@ -33,6 +38,7 @@ {{eps_no.format}}
diff --git a/src/app/admin/bangumi-detail/universal-builder/universal-builder.less b/src/app/admin/bangumi-detail/universal-builder/universal-builder.less index 27d94b3..b7d8bfe 100644 --- a/src/app/admin/bangumi-detail/universal-builder/universal-builder.less +++ b/src/app/admin/bangumi-detail/universal-builder/universal-builder.less @@ -9,9 +9,12 @@ @media(min-width: 992px) and (max-width: 1200px) { width: 933px; } - @media(min-width: 1201px) { + @media(min-width: 1201px) and (max-width: 1599px){ width: 1127px; } + @media(min-width: 1601px) { + width: 1600px; + } @media(min-height: 700px) { height: 80%; } @@ -61,6 +64,9 @@ padding-bottom: 5rem; overflow-y: scroll; overflow-x: auto; + .anchor-button { + cursor: pointer; + } } .no-result-wrapper, .searching-wrapper{ diff --git a/src/app/entity/MediaFile.ts b/src/app/entity/MediaFile.ts new file mode 100644 index 0000000..be6c760 --- /dev/null +++ b/src/app/entity/MediaFile.ts @@ -0,0 +1,6 @@ +export class MediaFile { + path: string; + ext: string; + name: string; + size: string; +} diff --git a/src/app/entity/item.ts b/src/app/entity/item.ts index b493324..9c1b657 100644 --- a/src/app/entity/item.ts +++ b/src/app/entity/item.ts @@ -1,6 +1,7 @@ import { ItemType } from './item-type'; import { Publisher } from './publisher'; import { Team } from './team'; +import { MediaFile } from './MediaFile'; export class Item { id: any; @@ -8,10 +9,11 @@ export class Item { eps_no_list: { eps_no: number, format: string }[]; type: ItemType; team?: Team; - timestampe: Date; + timestamp: Date; uri?: string; publisher: Publisher; torrent_url?: string; magnet_uri?: string; ext: string; + files: MediaFile[]; } diff --git a/src/helpers/clipboard.ts b/src/helpers/clipboard.ts new file mode 100644 index 0000000..d4f3f22 --- /dev/null +++ b/src/helpers/clipboard.ts @@ -0,0 +1,30 @@ +export function copyTextToClipboard(text: string): Promise { + // async clipboard api only available in secure context (https) + if (navigator.clipboard) { + return navigator.clipboard.writeText(text); + } + const span = document.createElement('span'); + span.textContent = text; + span.style.whiteSpace = 'pre'; + span.style.userSelect = 'auto'; + + document.body.appendChild(span); + + copyElementValueToClipboard(span); + window.document.body.removeChild(span); + return Promise.resolve(undefined); +} + +export function copyElementValueToClipboard(element: HTMLElement): void { + const selection = window.getSelection(); + const range = window.document.createRange(); + selection.removeAllRanges(); + range.selectNode(element); + selection.addRange(range); + + try { + window.document.execCommand('copy'); + } finally { + selection.removeAllRanges(); + } +}