You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.5 KiB
TypeScript
198 lines
6.5 KiB
TypeScript
import {
|
|
AfterViewInit,
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
EventEmitter,
|
|
Input,
|
|
OnChanges,
|
|
Output,
|
|
SimpleChanges,
|
|
ViewChild
|
|
} from "@angular/core";
|
|
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 {LoggingService} from "../../../../services/logging/logging.service";
|
|
import {BusyIndicatorComponent} from "../../app-common/busy-indicator/busy-indicator.component";
|
|
import {Observable} from "rxjs";
|
|
|
|
@Component({
|
|
selector: "app-tag-edit",
|
|
templateUrl: "./tag-edit.component.html",
|
|
styleUrls: ["./tag-edit.component.scss"],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class TagEditComponent implements AfterViewInit, OnChanges {
|
|
|
|
@Input() files: File[] = [];
|
|
@Output() tagEditEvent = new EventEmitter<TagEditComponent>();
|
|
|
|
@ViewChild("tagScroll") tagScroll!: CdkVirtualScrollViewport;
|
|
@ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent;
|
|
|
|
public tags: Tag[] = [];
|
|
public allTags: Observable<Tag[]>;
|
|
public editMode: string = "Toggle";
|
|
private fileTags: { [key: number]: Tag[] } = {};
|
|
|
|
constructor(
|
|
private changeDetector: ChangeDetectorRef,
|
|
private logger: LoggingService,
|
|
private tagService: TagService,
|
|
) {
|
|
this.allTags = tagService.tags.asObservable();
|
|
}
|
|
|
|
async ngAfterViewInit() {
|
|
await this.tagService.loadTags();
|
|
await this.tagService.loadNamespaces();
|
|
await this.loadFileTags();
|
|
}
|
|
|
|
async ngOnChanges(changes: SimpleChanges) {
|
|
if (changes["files"]) {
|
|
await this.loadFileTags();
|
|
}
|
|
}
|
|
|
|
public async editTag(tag: string): Promise<void> {
|
|
if (tag.length > 0) {
|
|
let tagInstance = this.tagService.tags.value.find(
|
|
t => t.getNormalizedOutput() === tag);
|
|
|
|
if (!tagInstance) {
|
|
tagInstance = (await this.tagService.createTags([tag]))[0];
|
|
this.tagService.tags.next([...this.tagService.tags.value, tagInstance]);
|
|
}
|
|
this.changeDetector.markForCheck();
|
|
switch (this.editMode) {
|
|
case "Toggle":
|
|
await this.toggleTag(tagInstance);
|
|
break;
|
|
case "Add":
|
|
await this.addTag(tagInstance);
|
|
break;
|
|
case "Remove":
|
|
await this.removeTag(tagInstance);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
async toggleTag(tag: Tag) {
|
|
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.loadTags();
|
|
await this.tagService.loadNamespaces();
|
|
this.mapFileTagsToTagList();
|
|
const index = this.tags.indexOf(tag);
|
|
if (index >= 0) {
|
|
this.tagScroll.scrollToIndex(index);
|
|
}
|
|
this.changeDetector.markForCheck();
|
|
});
|
|
this.tagEditEvent.emit(this);
|
|
}
|
|
|
|
async addTag(tag: Tag) {
|
|
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);
|
|
}
|
|
await this.tagService.loadTags();
|
|
await this.tagService.loadNamespaces();
|
|
this.changeDetector.markForCheck();
|
|
});
|
|
this.tagEditEvent.emit(this);
|
|
}
|
|
|
|
public async removeTag(tag: Tag) {
|
|
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]
|
|
);
|
|
}
|
|
}
|
|
await this.tagService.loadTags();
|
|
await this.tagService.loadNamespaces();
|
|
this.mapFileTagsToTagList();
|
|
});
|
|
this.tagEditEvent.emit(this);
|
|
}
|
|
|
|
public trackByTagId(index: number, item: Tag) {
|
|
return item.id;
|
|
}
|
|
|
|
private async loadFileTags() {
|
|
await this.wrapAsyncOperation(async () => {
|
|
console.log("loading tags");
|
|
const mappings = await this.tagService.getFileTagMappings(this.files.map(f => f.cd));
|
|
|
|
for (const file of this.files) {
|
|
this.fileTags[file.id] = mappings[file.cd];
|
|
}
|
|
this.mapFileTagsToTagList();
|
|
});
|
|
}
|
|
|
|
private mapFileTagsToTagList() {
|
|
let tags: Tag[] = [];
|
|
for (const file of this.files) {
|
|
const fileTags = this.fileTags[file.id];
|
|
tags.push(
|
|
...fileTags.filter(
|
|
t => tags.findIndex(tag => tag.id === t.id) < 0));
|
|
}
|
|
this.tags = tags.sort(
|
|
(a, b) => a.getNormalizedOutput()
|
|
.localeCompare(b.getNormalizedOutput()));
|
|
this.changeDetector.markForCheck();
|
|
}
|
|
|
|
private async wrapAsyncOperation<T>(cb: () => Promise<T>): Promise<T | undefined> {
|
|
if (!this.busyIndicator?.wrapAsyncOperation) {
|
|
try {
|
|
return cb();
|
|
} catch (err: any) {
|
|
this.logger.error(err);
|
|
return undefined;
|
|
}
|
|
} else {
|
|
return this.busyIndicator.wrapAsyncOperation(cb);
|
|
}
|
|
}
|
|
}
|