Change change detection for sidebar components

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/3/head
trivernis 2 years ago
parent 83f820e8ea
commit 12c0aa30f6
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -1,5 +1,5 @@
<ng-content></ng-content>
<div *ngIf="this.busy" [class.blur]="this.blurBackground" [class.darken]="this.darkenBackground"
class="busy-indicator-overlay">
<mat-progress-spinner [mode]="mode" [value]="value" color="primary"></mat-progress-spinner>
<mat-progress-spinner *ngIf="this.busy" [mode]="mode" [value]="value" color="primary"></mat-progress-spinner>
</div>

@ -22,6 +22,10 @@
background-color: rgba(0, 0, 0, 0.5);
}
hidden {
display: none;
}
::ng-deep app-busy-indicator {
width: 100%;
height: 100%;

@ -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<T>(operation: Function): T | undefined {
@ -36,6 +45,7 @@ export class BusyIndicatorComponent {
public async wrapAsyncOperation<T>(operation: Function): Promise<T | undefined> {
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");
}
}
}

@ -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();
}
}

@ -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 {

@ -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[] = [];

@ -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<T> = {
@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[] = [];

@ -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);
}
}

@ -1,4 +1,4 @@
<div class="file-edit-inner" fxLayout="column">
<app-busy-indicator [blurBackground]="true" class="file-edit-inner" fxLayout="column">
<div class="file-edit-header" fxFlex="100px">
<h1>Edit Tags</h1>
<mat-divider></mat-divider>
@ -33,5 +33,4 @@
</mat-select>
</mat-form-field>
</div>
<app-busy-indicator *ngIf="this.loading" [busy]="this.loading" [blurBackground]="true"></app-busy-indicator>
</div>
</app-busy-indicator>

@ -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;
}
*/

@ -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<TagEditComponent>();
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<void> {
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<T>(cb: () => Promise<T>): Promise<T | undefined> {
if (!this.busyIndicator) {
try {
return cb();
} catch (err: any) {
this.errorBroker.showError(err);
return undefined;
}
} else {
return this.busyIndicator.wrapAsyncOperation(cb);
}
}
}

Loading…
Cancel
Save