Change tag autocomplete to only suggest tags that further filter in this context

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent 161b1c5992
commit c3a0f804cf

@ -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",

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

@ -1,7 +1,7 @@
<mat-card #card (click)="clickEvent.emit(this)" (dblclick)="dblClickEvent.emit(this)" [ngClass]="{'selected': gridEntry.selected}">
<mat-card-title *ngIf="!!gridEntry.file?.name">{{gridEntry.file?.name}}</mat-card-title>
<mat-card-content *ngIf="contentUrl !== undefined">
<app-content-aware-image [maximizeHeight]="false" class="entry-image" *ngIf="contentUrl" [imageSrc]="contentUrl"></app-content-aware-image>
<app-content-aware-image class="entry-image" *ngIf="contentUrl" [imageSrc]="contentUrl"></app-content-aware-image>
</mat-card-content>
<mat-card-footer>
<mat-progress-bar *ngIf="contentUrl === undefined" mode="indeterminate"></mat-progress-bar>

@ -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<string[]>;
@Input() validTags: string[] = [];
@Output() searchStartEvent = new EventEmitter<void>();
@Output() searchEndEvent = new EventEmitter<void>();
private allTags: string[] = [];
@ViewChild("tagInput") tagInput!: ElementRef<HTMLInputElement>;
@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: {

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

@ -11,7 +11,7 @@
<button *ngIf="!this.isSelectedRepository() && repository.local" mat-flat-button color="primary" (click)="startDaemonAndSelectRepository()">Open</button>
<button *ngIf="!this.isSelectedRepository() && !repository.local" mat-flat-button color="primary" (click)="selectRepository()" [disabled]="!this.daemonRunning">Connect</button>
<button *ngIf="this.isSelectedRepository() && repository.local" mat-flat-button color="primary" (click)="this.repoService.closeSelectedRepository()">Close</button>
<button *ngIf="this.isSelectedRepository() && !repository.local" mat-flat-button color="primary" (click)="this.repoService.disconnectSelectedRepository()" [disabled]="!this.daemonRunning">Disconnect</button>
<button *ngIf="this.isSelectedRepository() && !repository.local" mat-flat-button color="primary" (click)="this.repoService.disconnectSelectedRepository()">Disconnect</button>
<button mat-button [mat-menu-trigger-for]="menu" class="menu-button"><mat-icon>more_vert</mat-icon></button>
<mat-menu #menu="matMenu">
<button *ngIf="repository.local" mat-menu-item (click)="removeRepository()">Delete</button>

@ -3,7 +3,7 @@
<div fxLayout="column" class="drawer-sidebar-inner">
<div id="file-search-input" fxFlex="220px">
<app-file-search #filesearch (searchStartEvent)="contentLoading = true"
(searchEndEvent)="contentLoading = false"></app-file-search>
(searchEndEvent)="contentLoading = false" [validTags]="this.getValidTagsForSearch()"></app-file-search>
</div>
<div id="file-tag-list" fxFlex fxFlexAlign="start" fxFlexFill>
<h1>Selection Tags</h1>

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

@ -21,4 +21,14 @@ export class TagService {
const tags = await invoke<Tag[]>("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<Tag[]> {
let tags: Tag[] = []
if (hashes.length === 1) {
tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_file", {hash: hashes[0]});
} else if (hashes.length > 0) {
tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_files", {hashes});
}
return tags.map(t => new Tag(t.id, t.name, t.namespace));
}
}

Loading…
Cancel
Save