From 83f820e8eaf788ef81d7cf77227ac10dccb401c9 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 18 Jan 2022 20:03:41 +0100 Subject: [PATCH 01/10] Improve filter query display performance Signed-off-by: trivernis --- mediarepo-ui/angular.json | 3 +- mediarepo-ui/src-tauri/Cargo.lock | 2 +- .../shared/app-common/app-common.module.ts | 3 ++ .../pipes/has-property.pipe.spec.ts | 8 +++ .../app-common/pipes/has-property.pipe.ts | 11 ++++ ...filter-expression-list-item.component.html | 16 +++--- .../filter-expression-list-item.component.ts | 24 ++------- .../filter-expression-item.component.html | 22 ++++---- .../filter-expression-item.component.ts | 50 +++++++------------ .../get-property-query.pipe.spec.ts | 8 +++ .../filter-pipes/get-property-query.pipe.ts | 13 +++++ .../filter-pipes/get-tag-query.pipe.spec.ts | 8 +++ .../filter-pipes/get-tag-query.pipe.ts | 12 +++++ .../shared/sidebar/sidebar.module.ts | 4 ++ 14 files changed, 112 insertions(+), 72 deletions(-) create mode 100644 mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.spec.ts create mode 100644 mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.ts create mode 100644 mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.spec.ts create mode 100644 mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.ts create mode 100644 mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.spec.ts create mode 100644 mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.ts diff --git a/mediarepo-ui/angular.json b/mediarepo-ui/angular.json index 7f6b32a..bb6c9a2 100644 --- a/mediarepo-ui/angular.json +++ b/mediarepo-ui/angular.json @@ -11,7 +11,8 @@ "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss" + "style": "scss", + "changeDetection": "OnPush" }, "@schematics/angular:application": { "strict": true diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 31ff851..98e2cf1 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -40,7 +40,7 @@ checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" [[package]] name = "app" -version = "0.12.0" +version = "0.13.0" dependencies = [ "mediarepo-api", "serde", diff --git a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts index 822cbe0..abdf5e2 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/app-common.module.ts @@ -14,6 +14,7 @@ import {MetadataEntryComponent} from "./metadata-entry/metadata-entry.component" import {BusyDialogComponent} from "./busy-dialog/busy-dialog.component"; import {SelectableComponent} from "./selectable/selectable.component"; import {MatProgressBarModule} from "@angular/material/progress-bar"; +import {HasPropertyPipe} from "./pipes/has-property.pipe"; @NgModule({ @@ -26,6 +27,7 @@ import {MatProgressBarModule} from "@angular/material/progress-bar"; MetadataEntryComponent, BusyDialogComponent, SelectableComponent, + HasPropertyPipe, ], exports: [ ConfirmDialogComponent, @@ -35,6 +37,7 @@ import {MatProgressBarModule} from "@angular/material/progress-bar"; InputReceiverDirective, MetadataEntryComponent, SelectableComponent, + HasPropertyPipe, ], imports: [ CommonModule, diff --git a/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.spec.ts b/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.spec.ts new file mode 100644 index 0000000..4107bcc --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.spec.ts @@ -0,0 +1,8 @@ +import { HasPropertyPipe } from './has-property.pipe'; + +describe('HasPropertyPipe', () => { + it('create an instance', () => { + const pipe = new HasPropertyPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.ts b/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.ts new file mode 100644 index 0000000..11a4cf3 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/app-common/pipes/has-property.pipe.ts @@ -0,0 +1,11 @@ +import {Pipe, PipeTransform} from "@angular/core"; + +@Pipe({ + name: "hasProperty" +}) +export class HasPropertyPipe implements PipeTransform { + + transform(value: any, propertyName: string): unknown { + return propertyName in value; + } +} diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.html b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.html index 44c4af4..1f1ae8f 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.html @@ -7,10 +7,10 @@ - - + + @@ -19,9 +19,9 @@ - - + + diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.ts index e2ea659..1619375 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component.ts @@ -1,16 +1,12 @@ -import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core"; -import { - FilterExpression, - FilterQuery, - FilterQueryProperty, - FilterQueryTag -} from "../../../../../../../api/api-types/files"; +import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core"; +import {FilterExpression, FilterQuery} from "../../../../../../../api/api-types/files"; import {enumerate} from "../../../../../../utils/list-utils"; @Component({ selector: "app-filter-expression-list-item", templateUrl: "./filter-expression-list-item.component.html", - styleUrls: ["./filter-expression-list-item.component.scss"] + styleUrls: ["./filter-expression-list-item.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FilterExpressionListItemComponent implements OnChanges { @@ -35,18 +31,6 @@ export class FilterExpressionListItemComponent implements OnChanges { } } - public queryIs(query: FilterQuery, key: "Property" | "Tag"): boolean { - return key in query; - } - - public propertyQuery(query: FilterQuery): FilterQueryProperty { - return query as FilterQueryProperty; - } - - public tagQuery(query: FilterQuery): FilterQueryTag { - return query as FilterQueryTag; - } - private parseFilter() { if (this.filter && "OrExpression" in this.filter) { this.orExpression = enumerate(this.filter.OrExpression); diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.html b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.html index eb73324..a71228a 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.html @@ -1,15 +1,15 @@ - - - - + + + + OR - - - + + + diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.ts index 3356495..51dbbea 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-expression-item/filter-expression-item.component.ts @@ -1,47 +1,35 @@ -import {Component, Input} from "@angular/core"; -import { - FilterExpression, - FilterExpressionOrExpression, - FilterExpressionQuery, - FilterQuery, - FilterQueryProperty, - FilterQueryTag -} from "../../../../../../api/api-types/files"; +import {ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges} from "@angular/core"; +import {FilterExpression, FilterQuery} from "../../../../../../api/api-types/files"; @Component({ selector: "app-filter-expression-item", templateUrl: "./filter-expression-item.component.html", - styleUrls: ["./filter-expression-item.component.scss"] + styleUrls: ["./filter-expression-item.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class FilterExpressionItemComponent { - - +export class FilterExpressionItemComponent implements OnInit, OnChanges { @Input() filter!: FilterExpression; + public orExpression?: FilterQuery[]; + public query?: FilterQuery; constructor() { } - public is(key: "OrExpression" | "Query"): boolean { - return key in this.filter; - } - - public orExpression(): FilterExpressionOrExpression { - return this.filter as FilterExpressionOrExpression; - } - - public query(): FilterExpressionQuery { - return this.filter as FilterExpressionQuery; - } - - public queryIs(query: FilterQuery, key: "Property" | "Tag"): boolean { - return key in query; + public ngOnInit(): void { + this.parseQuery(); } - public propertyQuery(query: FilterQuery): FilterQueryProperty { - return query as FilterQueryProperty; + public ngOnChanges(changes: SimpleChanges): void { + if (changes["filter"]) { + this.parseQuery(); + } } - public tagQuery(query: FilterQuery): FilterQueryTag { - return query as FilterQueryTag; + private parseQuery() { + if ("Query" in this.filter) { + this.query = this.filter.Query; + } else { + this.orExpression = this.filter.OrExpression; + } } } diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.spec.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.spec.ts new file mode 100644 index 0000000..a76d56f --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.spec.ts @@ -0,0 +1,8 @@ +import { GetPropertyQueryPipe } from './get-property-query.pipe'; + +describe('GetPropertyQueryPipe', () => { + it('create an instance', () => { + const pipe = new GetPropertyQueryPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.ts new file mode 100644 index 0000000..879c019 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-property-query.pipe.ts @@ -0,0 +1,13 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {FilterQuery, FilterQueryProperty, PropertyQuery} from "../../../../../../api/api-types/files"; + +@Pipe({ + name: "getPropertyQuery" +}) +export class GetPropertyQueryPipe implements PipeTransform { + + transform(value: FilterQuery): PropertyQuery { + return (value as FilterQueryProperty).Property; + } + +} diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.spec.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.spec.ts new file mode 100644 index 0000000..ba39149 --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.spec.ts @@ -0,0 +1,8 @@ +import { GetTagQueryPipe } from './get-tag-query.pipe'; + +describe('GetTagQueryPipe', () => { + it('create an instance', () => { + const pipe = new GetTagQueryPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.ts new file mode 100644 index 0000000..96f518e --- /dev/null +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-pipes/get-tag-query.pipe.ts @@ -0,0 +1,12 @@ +import {Pipe, PipeTransform} from "@angular/core"; +import {FilterQuery, FilterQueryTag, TagQuery} from "../../../../../../api/api-types/files"; + +@Pipe({ + name: "getTagQuery" +}) +export class GetTagQueryPipe implements PipeTransform { + + transform(value: FilterQuery): TagQuery { + return (value as FilterQueryTag).Tag; + } +} diff --git a/mediarepo-ui/src/app/components/shared/sidebar/sidebar.module.ts b/mediarepo-ui/src/app/components/shared/sidebar/sidebar.module.ts index 7e8b843..980d6ca 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/sidebar.module.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/sidebar.module.ts @@ -46,6 +46,8 @@ import { import { FilterExpressionListItemComponent } from "./file-search/filter-dialog/filter-expression-list-item/filter-expression-list-item.component"; +import { GetTagQueryPipe } from './file-search/filter-pipes/get-tag-query.pipe'; +import { GetPropertyQueryPipe } from './file-search/filter-pipes/get-property-query.pipe'; @NgModule({ @@ -62,6 +64,8 @@ import { TagQueryItemComponent, PropertyQueryItemComponent, FilterExpressionListItemComponent, + GetTagQueryPipe, + GetPropertyQueryPipe, ], exports: [ TagEditComponent, From 12c0aa30f6228bc18584eb09d6d474105712f82e Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 18 Jan 2022 20:34:50 +0100 Subject: [PATCH 02/10] Change change detection for sidebar components Signed-off-by: trivernis --- .../busy-indicator.component.html | 2 +- .../busy-indicator.component.scss | 4 + .../busy-indicator.component.ts | 23 ++- .../context-menu/context-menu.component.ts | 8 +- .../filter-input/filter-input.component.ts | 5 +- .../file-search/file-search.component.ts | 15 +- .../filter-dialog/filter-dialog.component.ts | 5 +- .../sort-dialog/sort-dialog.component.ts | 22 +-- .../sidebar/tag-edit/tag-edit.component.html | 5 +- .../sidebar/tag-edit/tag-edit.component.scss | 3 + .../sidebar/tag-edit/tag-edit.component.ts | 151 ++++++++++-------- 11 files changed, 143 insertions(+), 100 deletions(-) diff --git a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.html b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.html index 83d912e..4009d30 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.html +++ b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.html @@ -1,5 +1,5 @@
- +
diff --git a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.scss b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.scss index b5bef4e..63724c8 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.scss +++ b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.scss @@ -22,6 +22,10 @@ background-color: rgba(0, 0, 0, 0.5); } +hidden { + display: none; +} + ::ng-deep app-busy-indicator { width: 100%; height: 100%; diff --git a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.ts b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.ts index 89e9f09..69e10b6 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/busy-indicator/busy-indicator.component.ts @@ -1,24 +1,33 @@ -import {Component, Input} from "@angular/core"; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges} from "@angular/core"; import {ProgressSpinnerMode} from "@angular/material/progress-spinner"; @Component({ selector: "app-busy-indicator", templateUrl: "./busy-indicator.component.html", - styleUrls: ["./busy-indicator.component.scss"] + styleUrls: ["./busy-indicator.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class BusyIndicatorComponent { - +export class BusyIndicatorComponent implements OnChanges { @Input() busy: boolean = false; @Input() blurBackground: boolean = false; @Input() darkenBackground: boolean = false; @Input() mode: ProgressSpinnerMode = "indeterminate"; @Input() value: number | undefined; - constructor() { + constructor(private changeDetector: ChangeDetectorRef) { + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes["busy"]) { + this.changeDetector.markForCheck(); + } } public setBusy(busy: boolean) { - this.busy = busy; + if (busy != this.busy) { + this.busy = busy; + this.changeDetector.markForCheck(); + } } public wrapOperation(operation: Function): T | undefined { @@ -36,6 +45,7 @@ export class BusyIndicatorComponent { public async wrapAsyncOperation(operation: Function): Promise { this.setBusy(true); + console.log("busy"); try { const result = await operation(); this.setBusy(false); @@ -44,6 +54,7 @@ export class BusyIndicatorComponent { return undefined; } finally { this.setBusy(false); + console.log("not busy"); } } } diff --git a/mediarepo-ui/src/app/components/shared/app-common/context-menu/context-menu.component.ts b/mediarepo-ui/src/app/components/shared/app-common/context-menu/context-menu.component.ts index 435d46e..6f0ae32 100644 --- a/mediarepo-ui/src/app/components/shared/app-common/context-menu/context-menu.component.ts +++ b/mediarepo-ui/src/app/components/shared/app-common/context-menu/context-menu.component.ts @@ -1,10 +1,11 @@ -import {Component, ViewChild,} from "@angular/core"; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild,} from "@angular/core"; import {MatMenuTrigger} from "@angular/material/menu"; @Component({ selector: "app-context-menu", templateUrl: "./context-menu.component.html", - styleUrls: ["./context-menu.component.scss"] + styleUrls: ["./context-menu.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class ContextMenuComponent { @@ -13,7 +14,7 @@ export class ContextMenuComponent { @ViewChild(MatMenuTrigger) menuTrigger!: MatMenuTrigger; - constructor() { + constructor(private changeDetector: ChangeDetectorRef) { } public onContextMenu(event: MouseEvent) { @@ -22,5 +23,6 @@ export class ContextMenuComponent { this.y = event.clientY + "px"; this.menuTrigger.menu.focusFirstItem("mouse"); this.menuTrigger.openMenu(); + this.changeDetector.markForCheck(); } } diff --git a/mediarepo-ui/src/app/components/shared/input/filter-input/filter-input.component.ts b/mediarepo-ui/src/app/components/shared/input/filter-input/filter-input.component.ts index e52ce67..f6e92fa 100644 --- a/mediarepo-ui/src/app/components/shared/input/filter-input/filter-input.component.ts +++ b/mediarepo-ui/src/app/components/shared/input/filter-input/filter-input.component.ts @@ -1,4 +1,4 @@ -import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core"; +import {ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core"; import {Observable} from "rxjs"; import {FormControl} from "@angular/forms"; import {Tag} from "../../../../../api/models/Tag"; @@ -15,7 +15,8 @@ type AutocompleteEntry = { @Component({ selector: "app-filter-input", templateUrl: "./filter-input.component.html", - styleUrls: ["./filter-input.component.scss"] + styleUrls: ["./filter-input.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FilterInputComponent implements OnChanges { diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts index 6c062ca..159f641 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts @@ -1,4 +1,14 @@ -import {AfterViewChecked, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from "@angular/core"; +import { + AfterViewChecked, + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild +} from "@angular/core"; import {SortKey} from "../../../../models/SortKey"; import {MatDialog} from "@angular/material/dialog"; import {SortDialogComponent} from "./sort-dialog/sort-dialog.component"; @@ -18,7 +28,8 @@ import * as deepEqual from "fast-deep-equal"; @Component({ selector: "app-file-search", templateUrl: "./file-search.component.html", - styleUrls: ["./file-search.component.scss"] + styleUrls: ["./file-search.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class FileSearchComponent implements AfterViewChecked, OnInit { public sortExpression: SortKey[] = []; diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-dialog.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-dialog.component.ts index f11843d..5802227 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-dialog.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/filter-dialog/filter-dialog.component.ts @@ -1,4 +1,4 @@ -import {Component, Inject, OnChanges, SimpleChanges} from "@angular/core"; +import {ChangeDetectionStrategy, Component, Inject, OnChanges, SimpleChanges} from "@angular/core"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {SortDialogComponent} from "../sort-dialog/sort-dialog.component"; import {Tag} from "../../../../../../api/models/Tag"; @@ -13,7 +13,8 @@ type IndexableSelection = { @Component({ selector: "app-filter-dialog", templateUrl: "./filter-dialog.component.html", - styleUrls: ["./filter-dialog.component.scss"] + styleUrls: ["./filter-dialog.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class FilterDialogComponent implements OnChanges { public availableTags: Tag[] = []; diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/sort-dialog/sort-dialog.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/sort-dialog/sort-dialog.component.ts index ff9bf4a..13b6c44 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/sort-dialog/sort-dialog.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/sort-dialog/sort-dialog.component.ts @@ -1,14 +1,16 @@ -import {Component, Inject} from "@angular/core"; +import {ChangeDetectionStrategy, Component, Inject} from "@angular/core"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {SortKey} from "../../../../../models/SortKey"; import {CdkDragDrop, moveItemInArray} from "@angular/cdk/drag-drop"; import {Namespace} from "../../../../../../api/models/Namespace"; import {TagService} from "../../../../../services/tag/tag.service"; +import {compareSearchResults} from "../../../../../utils/compare-utils"; @Component({ selector: "app-sort-dialog", templateUrl: "./sort-dialog.component.html", - styleUrls: ["./sort-dialog.component.scss"] + styleUrls: ["./sort-dialog.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SortDialogComponent { @@ -24,20 +26,6 @@ export class SortDialogComponent { namespaces => this.namespaces = namespaces); } - private static compareSuggestionNamespaces(query: string, l: string, r: string): number { - if (l.startsWith(query) && !r.startsWith(query)) { - return -1; - } else if (!l.startsWith(query) && r.startsWith(query)) { - return 1; - } else if (l.length < r.length) { - return -1; - } else if (l.length > r.length) { - return 1; - } else { - return l.localeCompare(r); - } - } - addNewSortKey() { const sortKey = new SortKey("FileName", "Ascending", undefined); this.sortEntries.push(sortKey); @@ -64,7 +52,7 @@ export class SortDialogComponent { public updateAutocompleteSuggestions(value: string): void { this.suggestedNamespaces = this.namespaces.sort( - (a, b) => SortDialogComponent.compareSuggestionNamespaces(value, a.name, b.name)) + (a, b) => compareSearchResults(value, a.name, b.name)) .slice(0, 50); } } diff --git a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html index c72c883..3578731 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html @@ -1,4 +1,4 @@ -
+

Edit Tags

@@ -33,5 +33,4 @@
- -
+ diff --git a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.scss b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.scss index b3454e0..d8cef2c 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.scss +++ b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.scss @@ -12,6 +12,7 @@ .file-edit-header { text-align: center; + h1 { margin-top: 20px; } @@ -75,6 +76,7 @@ mat-divider { width: 100%; } +/* app-busy-indicator { position: absolute; top: 0; @@ -83,3 +85,4 @@ app-busy-indicator { width: 100%; z-index: 99; } +*/ diff --git a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.ts index 4b2b51f..ae34b58 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.ts @@ -1,9 +1,10 @@ import { + AfterViewInit, + ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, - OnInit, Output, SimpleChanges, ViewChild @@ -12,31 +13,35 @@ import {File} from "../../../../../api/models/File"; import {Tag} from "../../../../../api/models/Tag"; import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; import {TagService} from "../../../../services/tag/tag.service"; +import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; +import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component"; @Component({ selector: "app-tag-edit", templateUrl: "./tag-edit.component.html", - styleUrls: ["./tag-edit.component.scss"] + styleUrls: ["./tag-edit.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TagEditComponent implements OnInit, OnChanges { +export class TagEditComponent implements AfterViewInit, OnChanges { @Input() files: File[] = []; @Output() tagEditEvent = new EventEmitter(); - public tags: Tag[] = []; + @ViewChild("tagScroll") tagScroll!: CdkVirtualScrollViewport; + @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent; + + public tags: Tag[] = []; public allTags: Tag[] = []; public editMode: string = "Toggle"; - @ViewChild("tagScroll") tagScroll!: CdkVirtualScrollViewport; private fileTags: { [key: number]: Tag[] } = {}; - public loading = false; - constructor( + private errorBroker: ErrorBrokerService, private tagService: TagService, ) { } - async ngOnInit() { + async ngAfterViewInit() { this.tagService.tags.subscribe(tags => this.allTags = tags); await this.tagService.loadTags(); await this.tagService.loadNamespaces(); @@ -50,7 +55,6 @@ export class TagEditComponent implements OnInit, OnChanges { } public async editTag(tag: string): Promise { - this.loading = true; if (tag.length > 0) { let tagInstance = this.allTags.find( t => t.getNormalizedOutput() === tag); @@ -71,81 +75,87 @@ export class TagEditComponent implements OnInit, OnChanges { break; } } - this.loading = false; } async toggleTag(tag: Tag) { - for (const file of this.files) { - const fileTags = this.fileTags[file.id]; - let addedTags = []; - let removedTags = []; - if (fileTags.findIndex(i => i.id === tag.id) < 0) { - addedTags.push(tag.id); - } else { - removedTags.push(tag.id); + await this.wrapAsyncOperation(async () => { + for (const file of this.files) { + const fileTags = this.fileTags[file.id]; + let addedTags = []; + let removedTags = []; + if (fileTags.findIndex(i => i.id === tag.id) < 0) { + addedTags.push(tag.id); + } else { + removedTags.push(tag.id); + } + this.fileTags[file.id] = await this.tagService.changeFileTags( + file.id, + addedTags, removedTags + ); + if (addedTags.length > 0) { + await this.tagService.loadTags(); + await this.tagService.loadNamespaces(); + } } - this.fileTags[file.id] = await this.tagService.changeFileTags( - file.id, - addedTags, removedTags); - if (addedTags.length > 0) { - await this.tagService.loadTags(); - await this.tagService.loadNamespaces(); + this.mapFileTagsToTagList(); + const index = this.tags.indexOf(tag); + if (index >= 0) { + this.tagScroll.scrollToIndex(index); } - } - this.mapFileTagsToTagList(); - const index = this.tags.indexOf(tag); - if (index >= 0) { - this.tagScroll.scrollToIndex(index); - } + }); this.tagEditEvent.emit(this); } async addTag(tag: Tag) { - for (const file of this.files) { - if ((this.fileTags[file.id] ?? []).findIndex(t => t.id === tag.id) < 0) { - this.fileTags[file.id] = await this.tagService.changeFileTags( - file.id, - [tag.id], []); + await this.wrapAsyncOperation(async () => { + for (const file of this.files) { + if ((this.fileTags[file.id] ?? []).findIndex(t => t.id === tag.id) < 0) { + this.fileTags[file.id] = await this.tagService.changeFileTags( + file.id, + [tag.id], [] + ); + } } - } - this.mapFileTagsToTagList(); - const index = this.tags.indexOf(tag); - if (index >= 0) { - this.tagScroll.scrollToIndex(index); - } + this.mapFileTagsToTagList(); + const index = this.tags.indexOf(tag); + if (index >= 0) { + this.tagScroll.scrollToIndex(index); + } + await this.tagService.loadTags(); + await this.tagService.loadNamespaces(); + }); this.tagEditEvent.emit(this); - await this.tagService.loadTags(); - await this.tagService.loadNamespaces(); } public async removeTag(tag: Tag) { - this.loading = true; - for (const file of this.files) { - if (this.fileTags[file.id].findIndex(t => t.id === tag.id) >= 0) { - this.fileTags[file.id] = await this.tagService.changeFileTags( - file.id, - [], [tag.id]); + await this.wrapAsyncOperation(async () => { + for (const file of this.files) { + if (this.fileTags[file.id].findIndex(t => t.id === tag.id) >= 0) { + this.fileTags[file.id] = await this.tagService.changeFileTags( + file.id, + [], [tag.id] + ); + } } - } - this.mapFileTagsToTagList(); - this.loading = false; + this.mapFileTagsToTagList(); + }); this.tagEditEvent.emit(this); } private async loadFileTags() { - this.loading = true; - const promises = []; - const loadFn = async (file: File) => { - this.fileTags[file.id] = await this.tagService.getTagsForFiles( - [file.cd]); - }; - for (const file of this.files) { - promises.push(loadFn(file)); - } + await this.wrapAsyncOperation(async () => { + const promises = []; + const loadFn = async (file: File) => { + this.fileTags[file.id] = await this.tagService.getTagsForFiles( + [file.cd]); + }; + for (const file of this.files) { + promises.push(loadFn(file)); + } - await Promise.all(promises); - this.mapFileTagsToTagList(); - this.loading = false; + await Promise.all(promises); + this.mapFileTagsToTagList(); + }); } private mapFileTagsToTagList() { @@ -160,4 +170,17 @@ export class TagEditComponent implements OnInit, OnChanges { (a, b) => a.getNormalizedOutput() .localeCompare(b.getNormalizedOutput())); } + + private async wrapAsyncOperation(cb: () => Promise): Promise { + if (!this.busyIndicator) { + try { + return cb(); + } catch (err: any) { + this.errorBroker.showError(err); + return undefined; + } + } else { + return this.busyIndicator.wrapAsyncOperation(cb); + } + } } From 8768c26e5a2a8152fec42c0e0e70ad07f1d5e15f Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 18 Jan 2022 20:43:11 +0100 Subject: [PATCH 03/10] Change change detection of filecard component Signed-off-by: trivernis --- .../file/file-card/file-card.component.ts | 35 +++++++++++++------ .../file-thumbnail.component.html | 10 +++--- .../file-thumbnail.component.ts | 8 ++++- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts index e8064c8..90bb492 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts @@ -1,4 +1,7 @@ import { + AfterViewChecked, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, ElementRef, EventEmitter, @@ -12,42 +15,49 @@ import { } from "@angular/core"; import {File} from "../../../../../api/models/File"; import {Selectable} from "../../../../models/Selectable"; -import { - SchedulingService -} from "../../../../services/scheduling/scheduling.service"; +import {SchedulingService} from "../../../../services/scheduling/scheduling.service"; const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING"; @Component({ selector: "app-file-card", templateUrl: "./file-card.component.html", - styleUrls: ["./file-card.component.scss"] + styleUrls: ["./file-card.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) -export class FileCardComponent implements OnInit, OnChanges, OnDestroy { +export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked { @ViewChild("card") card!: ElementRef; @Input() public entry!: Selectable; @Output() clickEvent = new EventEmitter(); @Output() dblClickEvent = new EventEmitter(); - + public loading = false; private cachedId: number | undefined; private workId: number | undefined; - public loading = false; + private selectedPrevious = false; - constructor(private schedulingService: SchedulingService) { + constructor(private changeDetector: ChangeDetectorRef, private schedulingService: SchedulingService) { } async ngOnInit() { this.cachedId = this.entry.data.id; + this.selectedPrevious = this.entry.selected; this.setImageDelayed(); } async ngOnChanges(changes: SimpleChanges) { - if (changes["file"] && (this.cachedId === undefined || this.entry.data.id !== this.cachedId)) { + if (changes["entry"] && (this.cachedId === undefined || this.entry.data.id !== this.cachedId)) { this.cachedId = this.entry.data.id; this.setImageDelayed(); } } + public ngAfterViewChecked(): void { + if (this.entry.selected != this.selectedPrevious) { + this.selectedPrevious = this.entry.selected; + this.changeDetector.markForCheck(); + } + } + public ngOnDestroy(): void { if (this.workId) { this.schedulingService.cancelWork(LOADING_WORK_KEY, this.workId); @@ -59,10 +69,13 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy { this.schedulingService.cancelWork(LOADING_WORK_KEY, this.workId); } this.loading = true; - this.workId = this.schedulingService.addWork(LOADING_WORK_KEY, + this.workId = this.schedulingService.addWork( + LOADING_WORK_KEY, async () => { await this.schedulingService.delay(1); this.loading = false; - }); + this.changeDetector.markForCheck(); + } + ); } } diff --git a/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.html b/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.html index b171b71..3a0e1f9 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.html @@ -1,14 +1,14 @@
- +
- - - - + + + +
diff --git a/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.ts b/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.ts index dbf6147..74d7808 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-thumbnail/file-thumbnail.component.ts @@ -14,6 +14,8 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit { @Input() file!: File; public thumbUrl: SafeResourceUrl | undefined; + public fileType!: string; + public thumbnailSupported: boolean = false; private supportedThumbnailTypes = ["image", "video"]; @@ -21,7 +23,9 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit { } public async ngAfterViewInit() { - this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, 250); + if (this.thumbnailSupported) { + this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, 250); + } } public async ngOnChanges(changes: SimpleChanges) { @@ -29,6 +33,8 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit { this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, 250 ); + this.fileType = this.getFileType(); + this.thumbnailSupported = this.getThumbnailSupported(); } } From 8022f2580176d4fd230a04283561b765a942c064 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 18 Jan 2022 20:51:01 +0100 Subject: [PATCH 04/10] Add track by functions to tag and file lists Signed-off-by: trivernis --- .../file-gallery/file-gallery.component.html | 2 +- .../file-multiview/file-gallery/file-gallery.component.ts | 4 ++++ .../file-multiview/file-grid/file-grid.component.html | 4 ++-- .../file/file-multiview/file-grid/file-grid.component.ts | 8 ++++++++ .../shared/sidebar/file-search/file-search.component.html | 2 +- .../shared/sidebar/file-search/file-search.component.ts | 4 ++++ .../shared/sidebar/tag-edit/tag-edit.component.html | 2 +- .../shared/sidebar/tag-edit/tag-edit.component.ts | 4 ++++ 8 files changed, 25 insertions(+), 5 deletions(-) diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html index d81a443..f718760 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html @@ -13,7 +13,7 @@
-
+
diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts index b91a293..d55f5ef 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts @@ -173,6 +173,10 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit { } } + public trackByFileId(index: number, item: Selectable) { + return item.data.id; + } + private scrollToSelection(): void { if (this.selectedFile) { const selectedIndex = this.entries.indexOf(this.selectedFile); diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html index 7034cbc..d5678c6 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html @@ -5,13 +5,13 @@ class="file-gallery-inner"> -
+
diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.ts b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.ts index 1ee2d26..f203b2c 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.ts @@ -169,6 +169,14 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit { this.ctrlClicked = event.ctrlKey ? false : this.ctrlClicked; } + public trackByFileRowId(index: number, item: Selectable[]) { + return item.map(e => e.data.id).join("-"); + } + + public trackByFileId(index: number, item: Selectable) { + return item.data.id; + } + private setPartitionedGridEntries() { this.partitionedGridEntries = []; let scrollToIndex = -1; diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.html b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.html index d52827b..e4de89f 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.html @@ -50,7 +50,7 @@
+ *cdkVirtualFor="let tag of contextTags; trackBy: trackByTagId" class="selectable-tag">
diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts index 159f641..4188f5e 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-search/file-search.component.ts @@ -198,6 +198,10 @@ export class FileSearchComponent implements AfterViewChecked, OnInit { return deepEqual(tagFilter, filter); } + public trackByTagId(index: number, item: Tag) { + return item.id; + } + private assignDisplayedFilters() { this.displayedFilters = this.filters.getFilters().filter(f => !this.isTagFilter(f)); } diff --git a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html index 3578731..388fc69 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/tag-edit/tag-edit.component.html @@ -5,7 +5,7 @@
-
+
diff --git a/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts b/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts index e6400cb..2f52ce5 100644 --- a/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/content-viewer/content-viewer.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, + ChangeDetectionStrategy, Component, Input, OnChanges, @@ -11,25 +12,23 @@ import {SafeResourceUrl} from "@angular/platform-browser"; import {File} from "../../../../../api/models/File"; import {FileService} from "../../../../services/file/file.service"; import {FileHelper} from "../../../../services/file/file.helper"; -import { - ErrorBrokerService -} from "../../../../services/error-broker/error-broker.service"; -import { - BusyIndicatorComponent -} from "../../app-common/busy-indicator/busy-indicator.component"; +import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service"; +import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component"; type ContentType = "image" | "video" | "audio" | "other"; @Component({ selector: "app-content-viewer", templateUrl: "./content-viewer.component.html", - styleUrls: ["./content-viewer.component.scss"] + styleUrls: ["./content-viewer.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestroy { @Input() file!: File; public contentUrl: SafeResourceUrl | undefined; public blobUrl: SafeResourceUrl | undefined; + public contentType: ContentType = "other"; @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent; @@ -40,7 +39,8 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr } public async ngAfterViewInit() { - if (["audio", "video"].includes(this.getContentType())) { + this.contentType = this.getContentType(); + if (["audio", "video"].includes(this.contentType)) { await this.loadBlobUrl(); } else { this.contentUrl = this.fileService.buildContentUrl(this.file); @@ -49,8 +49,9 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr public async ngOnChanges(changes: SimpleChanges) { if (changes["file"]) { - if (["audio", "video"].includes( - this.getContentType()) && this.busyIndicator) { + this.contentType = this.getContentType(); + + if (["audio", "video"].includes(this.contentType) && this.busyIndicator) { await this.loadBlobUrl(); } else { this.contentUrl = this.fileService.buildContentUrl(this.file); @@ -63,23 +64,6 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr this.unloadBlobUrl(); } - public getContentType(): ContentType { - let mimeParts = this.file.mimeType.split("/"); - const type = mimeParts.shift() ?? "other"; - const subtype = mimeParts.shift() ?? "*"; - - switch (type) { - case "image": - return "image"; - case "video": - return "video"; - case "audio": - return "audio"; - default: - return "other"; - } - } - public async downloadContent() { const path = await FileHelper.getFileDownloadLocation(this.file); @@ -103,6 +87,22 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr }); } + private getContentType(): ContentType { + let mimeParts = this.file.mimeType.split("/"); + const type = mimeParts.shift() ?? "other"; + + switch (type) { + case "image": + return "image"; + case "video": + return "video"; + case "audio": + return "audio"; + default: + return "other"; + } + } + private unloadBlobUrl() { if (this.blobUrl) { URL?.revokeObjectURL(this.blobUrl as string); diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.html b/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.html index 0644be9..ed8b0b1 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.html +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.html @@ -3,15 +3,21 @@

File Metadata

- + diff --git a/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.ts b/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.ts index 733385d..f897032 100644 --- a/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.ts +++ b/mediarepo-ui/src/app/components/shared/sidebar/file-metadata/file-metadata.component.ts @@ -1,48 +1,45 @@ -import { - Component, - Input, - OnChanges, - OnInit, - SimpleChanges -} from "@angular/core"; +import {ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from "@angular/core"; import {File} from "../../../../../api/models/File"; import {FileService} from "../../../../services/file/file.service"; import {FileMetadata} from "../../../../../api/api-types/files"; +import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component"; @Component({ selector: "app-file-metadata", templateUrl: "./file-metadata.component.html", - styleUrls: ["./file-metadata.component.scss"] + styleUrls: ["./file-metadata.component.scss"], + changeDetection: ChangeDetectionStrategy.OnPush }) export class FileMetadataComponent implements OnInit, OnChanges { @Input() file!: File; public fileMetadata: FileMetadata | undefined; - public loading = false; + + @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent; constructor(private fileService: FileService) { } public async ngOnInit() { - this.loading = true; - this.fileMetadata = await this.fileService.getFileMetadata(this.file.id); - this.loading = false; + await this.busyIndicator.wrapAsyncOperation(async () => { + this.fileMetadata = await this.fileService.getFileMetadata(this.file.id); + }); } - public async ngOnChanges(changes:SimpleChanges) { + public async ngOnChanges(changes: SimpleChanges) { if (changes["file"] && (!this.fileMetadata || this.fileMetadata.file_id != this.file.id)) { - this.loading = true; - this.fileMetadata = await this.fileService.getFileMetadata(this.file.id); - this.loading = false; + await this.busyIndicator.wrapAsyncOperation(async () => { + this.fileMetadata = await this.fileService.getFileMetadata(this.file.id); + }); } } public async saveFileName(name: string) { - this.loading = true; - const newFile = await this.fileService.updateFileName(this.file.id, name); - if (this.fileMetadata) { - this.fileMetadata.name = newFile.name; - } - this.loading = false; + await this.busyIndicator.wrapAsyncOperation(async () => { + const newFile = await this.fileService.updateFileName(this.file.id, name); + if (this.fileMetadata) { + this.fileMetadata.name = newFile.name; + } + }); } } From d3700932db96a1a1d28f6291cbbf264e669fb0b5 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 18 Jan 2022 21:21:44 +0100 Subject: [PATCH 07/10] Propagate changes to file status Signed-off-by: trivernis --- mediarepo-ui/angular.json | 2 +- .../file/file-card/file-card.component.html | 5 ++- .../file/file-card/file-card.component.ts | 5 +++ .../file-context-menu.component.html | 8 ++--- .../file-context-menu.component.ts | 7 ++++ .../file-gallery/file-gallery.component.html | 6 ++-- .../file-gallery/file-gallery.component.ts | 12 +++++-- .../file-grid/file-grid.component.html | 4 +-- .../file-grid/file-grid.component.ts | 11 ++++-- .../file-thumbnail.component.html | 6 ++-- .../file-thumbnail.component.ts | 35 +++++++++++++++---- 11 files changed, 78 insertions(+), 23 deletions(-) diff --git a/mediarepo-ui/angular.json b/mediarepo-ui/angular.json index bb6c9a2..74ad062 100644 --- a/mediarepo-ui/angular.json +++ b/mediarepo-ui/angular.json @@ -68,7 +68,7 @@ "optimization": { "fonts": false, "styles": false, - "scripts": true + "scripts": false }, "vendorChunk": true, "extractLicenses": false, diff --git a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.html b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.html index ca38d72..e3bc970 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.html @@ -2,7 +2,10 @@ [ngClass]="{'selected': entry.selected}"> - + diff --git a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts index 90bb492..a548058 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-card/file-card.component.ts @@ -16,6 +16,7 @@ import { import {File} from "../../../../../api/models/File"; import {Selectable} from "../../../../models/Selectable"; import {SchedulingService} from "../../../../services/scheduling/scheduling.service"; +import {BehaviorSubject} from "rxjs"; const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING"; @@ -28,6 +29,7 @@ const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING"; export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked { @ViewChild("card") card!: ElementRef; @Input() public entry!: Selectable; + @Input() public fileChanged: BehaviorSubject = new BehaviorSubject(undefined); @Output() clickEvent = new EventEmitter(); @Output() dblClickEvent = new EventEmitter(); public loading = false; @@ -49,6 +51,9 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterVie this.cachedId = this.entry.data.id; this.setImageDelayed(); } + if (changes["fileChanged"]) { + this.fileChanged.subscribe(() => this.changeDetector.markForCheck()); + } } public ngAfterViewChecked(): void { diff --git a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html index a53cbcd..c4c479e 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.html @@ -1,12 +1,12 @@ - - - @@ -14,7 +14,7 @@ *ngIf="actionDeletePermantently" mat-menu-item>Delete permanently - + diff --git a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts index d63e924..83040bc 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-context-menu/file-context-menu.component.ts @@ -7,6 +7,7 @@ import {MatDialog, MatDialogRef} from "@angular/material/dialog"; import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; import {BehaviorSubject} from "rxjs"; import {FileActionBaseComponent} from "../../app-base/file-action-base/file-action-base.component"; +import {FileStatus} from "../../../../../api/api-types/files"; type ProgressDialogContext = { dialog: MatDialogRef, @@ -31,6 +32,7 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements @ViewChild("contextMenu") contextMenu!: ContextMenuComponent; @Output() fileDeleted = new EventEmitter(); + @Output() fileStatusChange = new EventEmitter(); constructor(fileService: FileService, errorBroker: ErrorBrokerService, dialog: MatDialog) { super(dialog, errorBroker, fileService); @@ -56,6 +58,11 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements } } + public async changeFileStatus(status: FileStatus) { + await this.updateStatus(this.files, status); + this.fileStatusChange.emit(this.files); + } + private applyStatus() { this.actionDeletePermantently = true; this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false; diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html index f718760..291cf61 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.html @@ -15,9 +15,11 @@ minBufferPx="1000" orientation="horizontal">
+ [entry]="entry" [fileChanged]="this.fileChanged">
- + diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts index d55f5ef..9a5eb90 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-gallery/file-gallery.component.ts @@ -17,6 +17,7 @@ import {Selectable} from "../../../../../models/Selectable"; import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; import {TabService} from "../../../../../services/tab/tab.service"; import {Key} from "w3c-keys"; +import {BehaviorSubject} from "rxjs"; @Component({ selector: "app-file-gallery", @@ -40,11 +41,15 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit { public entries: Selectable[] = []; public selectedFile: Selectable | undefined; public fileContentUrl: SafeResourceUrl | undefined; + public fileChanged = new BehaviorSubject(undefined); private scrollTimeout: number | undefined; private escapeCount = 0; - constructor(private tabService: TabService, private fileService: FileService) { + constructor( + private tabService: TabService, + private fileService: FileService + ) { tabService.selectedTab.subscribe(() => this.adjustElementSizes()); } @@ -177,6 +182,10 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit { return item.data.id; } + public onFileStatusChange(): void { + this.fileChanged.next(); + } + private scrollToSelection(): void { if (this.selectedFile) { const selectedIndex = this.entries.indexOf(this.selectedFile); @@ -194,7 +203,6 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit { } } - private getPreselectedEntry(): Selectable | undefined { if (this.preselectedFile) { const entry = this.entries.find( diff --git a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html index d5678c6..13dafcc 100644 --- a/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html +++ b/mediarepo-ui/src/app/components/shared/file/file-multiview/file-grid/file-grid.component.html @@ -12,13 +12,13 @@ (contextmenu)="this.selectEntryWhenNotSelected(gridEntry); fileContextMenu.onContextMenu($event, this.getSelectedFiles())" (dblClickEvent)="fileOpen.emit($event.entry.data)" *ngFor="let gridEntry of rowEntry; trackBy: trackByFileId" - [entry]="gridEntry"> + [entry]="gridEntry" [fileChanged]="this.fileChanged">
- +