From c3a0f804cf64a558d2c3d36a0cdba18df13734cc Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 4 Nov 2021 21:26:47 +0100 Subject: [PATCH] Change tag autocomplete to only suggest tags that further filter in this context Signed-off-by: trivernis --- mediarepo-ui/src-tauri/Cargo.lock | 4 +- mediarepo-ui/src-tauri/Cargo.toml | 2 +- .../file-grid-entry.component.html | 2 +- .../file-search/file-search.component.ts | 32 ++++++++++------ mediarepo-ui/src/app/models/Tag.ts | 8 +++- .../repository-card.component.html | 2 +- .../home/search-tab/search-tab.component.html | 2 +- .../home/search-tab/search-tab.component.ts | 37 +++++++------------ .../src/app/services/tag/tag.service.ts | 10 +++++ 9 files changed, 57 insertions(+), 42 deletions(-) diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index c5e82b9..26b0139 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -1580,8 +1580,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.1.0" -source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=4e0859cc08723f52057df73f16bff786be1aee43#4e0859cc08723f52057df73f16bff786be1aee43" +version = "0.2.0" +source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=a86cd413637562987a3940a664b923fd7e726cef#a86cd413637562987a3940a664b923fd7e726cef" dependencies = [ "async-trait", "chrono", diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml index 54d6e87..34e8fb6 100644 --- a/mediarepo-ui/src-tauri/Cargo.toml +++ b/mediarepo-ui/src-tauri/Cargo.toml @@ -30,7 +30,7 @@ features = ["env-filter"] [dependencies.mediarepo-api] git = "https://github.com/Trivernis/mediarepo-api.git" -rev = "4e0859cc08723f52057df73f16bff786be1aee43" +rev = "a86cd413637562987a3940a664b923fd7e726cef" features = ["tauri-plugin"] [features] diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.html b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.html index 4746e1f..26eb763 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.html +++ b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.html @@ -1,7 +1,7 @@ {{gridEntry.file?.name}} - + diff --git a/mediarepo-ui/src/app/components/file-search/file-search.component.ts b/mediarepo-ui/src/app/components/file-search/file-search.component.ts index 761dd1a..7262ca1 100644 --- a/mediarepo-ui/src/app/components/file-search/file-search.component.ts +++ b/mediarepo-ui/src/app/components/file-search/file-search.component.ts @@ -1,7 +1,7 @@ import { AfterViewChecked, Component, - ElementRef, EventEmitter, Output, + ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import {TagService} from "../../services/tag/tag.service"; @@ -16,6 +16,7 @@ import {MatDialog} from "@angular/material/dialog"; import {FilterDialogComponent} from "./filter-dialog/filter-dialog.component"; import {ErrorBrokerService} from "../../services/error-broker/error-broker.service"; + @Component({ selector: 'app-file-search', templateUrl: './file-search.component.html', @@ -32,24 +33,29 @@ export class FileSearchComponent implements AfterViewChecked { public searchTags: TagQuery[] = []; public suggestionTags: Observable; + @Input() validTags: string[] = []; @Output() searchStartEvent = new EventEmitter(); @Output() searchEndEvent = new EventEmitter(); - private allTags: string[] = []; - @ViewChild("tagInput") tagInput!: ElementRef; @ViewChild("tagInputList") inputList!: ElementRef; constructor(private errorBroker: ErrorBrokerService, private tagService: TagService, private fileService: FileService, public dialog: MatDialog) { - this.tagService.tags.subscribe( - (tag) => this.allTags = tag.map(t => t.getNormalizedOutput())); - this.suggestionTags = this.formControl.valueChanges.pipe(startWith(null), map( - (tag: string | null) => tag ? this.allTags.filter( - (t: string) => t.includes(tag.replace(/^-/g, ''))) - .map((t) => tag.startsWith("-") ? "-" + t : t) - .slice(0, 20) : this.allTags.slice(0, 20))); + (tag: string | null) => tag ? this.filterSuggestionTag( + tag) : this.validTags.slice(0, 20))); + } + + private filterSuggestionTag(tag: string) { + const negated = tag.startsWith("-"); + const normalizedTag = tag.replace(/^-/, ""); + + return this.validTags.filter( + t => t.includes(normalizedTag) && this.searchTags.findIndex( + s => s.name === t) < 0) + .map(t => negated ? "-" + t : t) + .slice(0, 20); } public async searchForFiles() { @@ -91,7 +97,7 @@ export class FileSearchComponent implements AfterViewChecked { async addSearchTagByInput(event: KeyboardEvent) { if (event.key === "Enter") { const tag = (this.formControl.value as string ?? "").trim(); - if (tag.length > 0 && this.allTags.includes(tag.replace(/-/g, ''))) { + if (tag.length > 0 && this.validTags.includes(tag.replace(/-/g, ''))) { this.addSearchTag(tag); this.formControl.setValue(null); await this.searchForFiles(); @@ -108,7 +114,9 @@ export class FileSearchComponent implements AfterViewChecked { } openSortDialog() { - const sortEntries = this.sortExpression.map(key => JSON.parse(JSON.stringify(key))).map(key => new SortKey(key.sortType, key.sortDirection, key.namespaceName)) + const sortEntries = this.sortExpression.map( + key => JSON.parse(JSON.stringify(key))).map( + key => new SortKey(key.sortType, key.sortDirection, key.namespaceName)) const openedDialog = this.dialog.open(FilterDialogComponent, { minWidth: "40vw", data: { diff --git a/mediarepo-ui/src/app/models/Tag.ts b/mediarepo-ui/src/app/models/Tag.ts index fac2eb1..ce7b11d 100644 --- a/mediarepo-ui/src/app/models/Tag.ts +++ b/mediarepo-ui/src/app/models/Tag.ts @@ -1,4 +1,7 @@ export class Tag { + + private normalizedTag?: string = undefined; + constructor( public id: number, public name: string, @@ -6,6 +9,9 @@ export class Tag { ) {} public getNormalizedOutput(): string { - return this.namespace ? this.namespace + ':' + this.name : this.name + if (!this.normalizedTag) { + this.normalizedTag = this.namespace ? this.namespace + ':' + this.name : this.name + } + return this.normalizedTag; } }; diff --git a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html index 7524206..1a7c7ea 100644 --- a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html +++ b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html @@ -11,7 +11,7 @@ - + diff --git a/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.html b/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.html index d2fe202..2770010 100644 --- a/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.html +++ b/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.html @@ -3,7 +3,7 @@
+ (searchEndEvent)="contentLoading = false" [validTags]="this.getValidTagsForSearch()">

Selection Tags

diff --git a/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.ts b/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.ts index cb7c68e..f65c07e 100644 --- a/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.ts +++ b/mediarepo-ui/src/app/pages/home/search-tab/search-tab.component.ts @@ -17,6 +17,7 @@ import {RepositoryService} from "../../../services/repository/repository.service }) export class SearchTabComponent implements OnInit { + tagsOfFiles: Tag[] = []; tags: Tag[] = []; files: File[] = []; private openingLightbox = false; @@ -34,7 +35,10 @@ export class SearchTabComponent implements OnInit { } async ngOnInit() { - this.fileService.displayedFiles.subscribe((files) => this.files = files); + this.fileService.displayedFiles.subscribe(async (files) => { + this.files = files; + await this.loadTagsForDisplayedFiles(); + }); this.repoService.selectedRepository.subscribe(async (repo) => repo && await this.loadFilesInitially()); await this.loadFilesInitially(); } @@ -64,28 +68,7 @@ export class SearchTabComponent implements OnInit { } async showFileDetails(files: File[]) { - this.tags = []; - - for (const file of files) { - const fileTags = await this.tagService.getTagsForFile(file.hash) - for (const tag of fileTags) { - if (this.tags.findIndex((t) => t.getNormalizedOutput() === tag.getNormalizedOutput()) < 0) { - this.tags.push(tag); - } - } - } - - this.tags = this.tags.sort((a, b) => { - const aNorm = a.getNormalizedOutput(); - const bNorm = b.getNormalizedOutput(); - if (aNorm > bNorm) { - return 1 - } else if (bNorm > aNorm) { - return -1; - } else { - return 0; - } - }); + this.tags = await this.tagService.getTagsForFiles(files.map(f => f.hash)); } async addSearchTagFromList(event: MatSelectionListChange) { @@ -106,4 +89,12 @@ export class SearchTabComponent implements OnInit { this.preselectedFile = preselectedFile; this.showGallery = false; } + + async loadTagsForDisplayedFiles() { + this.tagsOfFiles = await this.tagService.getTagsForFiles(this.files.map(f => f.hash)); + } + + getValidTagsForSearch(): string[] { + return this.tagsOfFiles.map(t => t.getNormalizedOutput()) + } } diff --git a/mediarepo-ui/src/app/services/tag/tag.service.ts b/mediarepo-ui/src/app/services/tag/tag.service.ts index 05f514a..1e66e2a 100644 --- a/mediarepo-ui/src/app/services/tag/tag.service.ts +++ b/mediarepo-ui/src/app/services/tag/tag.service.ts @@ -21,4 +21,14 @@ export class TagService { const tags = await invoke("plugin:mediarepo|get_tags_for_file", {hash}); return tags.map(t => new Tag(t.id, t.name, t.namespace)); } + + public async getTagsForFiles(hashes: string[]): Promise { + let tags: Tag[] = [] + if (hashes.length === 1) { + tags = await invoke("plugin:mediarepo|get_tags_for_file", {hash: hashes[0]}); + } else if (hashes.length > 0) { + tags = await invoke("plugin:mediarepo|get_tags_for_files", {hashes}); + } + return tags.map(t => new Tag(t.id, t.name, t.namespace)); + } }