Move file search to separate component

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

@ -1581,7 +1581,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=2b6a968e6d5343326e20c73f4fcbbb88848b0c35#2b6a968e6d5343326e20c73f4fcbbb88848b0c35" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=4600810fca96bf6b457adc39667245a46127e5e8#4600810fca96bf6b457adc39667245a46127e5e8"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"chrono", "chrono",

@ -30,7 +30,7 @@ features = ["env-filter"]
[dependencies.mediarepo-api] [dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git" git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "2b6a968e6d5343326e20c73f4fcbbb88848b0c35" rev = "4600810fca96bf6b457adc39667245a46127e5e8"
features = ["tauri-plugin"] features = ["tauri-plugin"]
[features] [features]

@ -26,6 +26,8 @@ import {ScrollingModule} from "@angular/cdk/scrolling";
import {LightboxModule} from "ngx-lightbox"; import {LightboxModule} from "ngx-lightbox";
import {MatChipsModule} from "@angular/material/chips"; import {MatChipsModule} from "@angular/material/chips";
import {MatIconModule} from "@angular/material/icon"; import {MatIconModule} from "@angular/material/icon";
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import { FileSearchComponent } from './components/file-search/file-search.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -36,6 +38,7 @@ import {MatIconModule} from "@angular/material/icon";
RepoFormComponent, RepoFormComponent,
FileGridComponent, FileGridComponent,
FileGridEntryComponent, FileGridEntryComponent,
FileSearchComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -56,7 +59,8 @@ import {MatIconModule} from "@angular/material/icon";
ScrollingModule, ScrollingModule,
LightboxModule, LightboxModule,
MatChipsModule, MatChipsModule,
MatIconModule MatIconModule,
MatAutocompleteModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

@ -0,0 +1,23 @@
<mat-form-field class="full-width" #tagSearch appearance="fill">
<mat-chip-list #chipList>
<mat-chip *ngFor="let tag of searchTags" (removed)="removeSearchTag(tag)" [removable]="true">
{{tag}}
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
</mat-chip-list>
<input matInput
#tagInput
[matChipInputFor]="chipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="searchInputSeparators"
[matAutocomplete]="auto"
[formControl]="formControl"
(matChipInputTokenEnd)="addSearchTagByChip($event)"/>
<mat-autocomplete #auto (optionSelected)="addSearchTagByAutocomplete($event)">
<mat-option *ngFor="let tag of suggestionTags | async" [value]="tag">
{{tag}}
</mat-option>
</mat-autocomplete>
</mat-form-field>

@ -0,0 +1,7 @@
#tag-search {
width: 100%;
}
.full-width {
width: 100%;
}

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FileSearchComponent } from './file-search.component';
describe('FileSearchComponent', () => {
let component: FileSearchComponent;
let fixture: ComponentFixture<FileSearchComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FileSearchComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FileSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,66 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {TagService} from "../../services/tag/tag.service";
import {FileService} from "../../services/file/file.service";
import {FormControl} from "@angular/forms";
import {COMMA, ENTER} from "@angular/cdk/keycodes";
import {MatChipInputEvent} from "@angular/material/chips";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
import {map, startWith} from "rxjs/operators";
import {Observable} from "rxjs";
@Component({
selector: 'app-file-search',
templateUrl: './file-search.component.html',
styleUrls: ['./file-search.component.scss']
})
export class FileSearchComponent {
public searchInputSeparators = [ENTER, COMMA];
public formControl = new FormControl();
public searchTags: string[] = [];
public suggestionTags: Observable<string[]>;
private allTags: string[] = [];
@ViewChild('tagInput') tagInput!: ElementRef<HTMLInputElement>;
constructor(private tagService: TagService, private fileService: FileService) {
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)).slice(0, 20) : this.allTags.slice(0, 20)));
}
public async searchForFiles() {
await this.fileService.findFiles(this.searchTags);
}
public addSearchTag(tag: string) {
this.searchTags.push(tag);
}
async removeSearchTag(tag: string) {
const index = this.searchTags.indexOf(tag);
if (index >= 0) {
this.searchTags.splice(index, 1);
}
await this.searchForFiles();
}
async addSearchTagByChip(event: MatChipInputEvent) {
const tag = event.value.trim();
if (tag.length > 0 && this.allTags.includes(tag)) {
this.searchTags.push(tag);
event.chipInput?.clear();
this.formControl.setValue(null);
await this.searchForFiles(); }
}
async addSearchTagByAutocomplete(event: MatAutocompleteSelectedEvent) {
const tag = event.option.viewValue;
this.searchTags.push(tag);
this.formControl.setValue(null);
this.tagInput.nativeElement.value = '';
await this.searchForFiles(); }
}

@ -5,22 +5,7 @@
<mat-drawer-container> <mat-drawer-container>
<mat-drawer mode="side" opened> <mat-drawer mode="side" opened>
<div class="drawer-sidebar-inner"> <div class="drawer-sidebar-inner">
<mat-form-field id="tag-search" appearance="fill"> <app-file-search #filesearch></app-file-search>
<mat-chip-list #chipList>
<mat-chip *ngFor="let tag of searchTags" (removed)="removeSearchTag(tag)" [removable]="true">
{{tag}}
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
</mat-chip-list>
<input matInput
[matChipInputFor]="chipList"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="searchInputSeparators"
(matChipInputTokenEnd)="addSearchTag($event)"/>
</mat-form-field>
<div id="file-tag-list"> <div id="file-tag-list">
<h1>Selection Tags</h1> <h1>Selection Tags</h1>
<mat-selection-list [multiple]="false" *ngIf="tags.length > 0" (selectionChange)="addSearchTagFromList($event)"> <mat-selection-list [multiple]="false" *ngIf="tags.length > 0" (selectionChange)="addSearchTagFromList($event)">

@ -8,10 +8,6 @@
overflow: hidden overflow: hidden
} }
#tag-search {
width: 100%;
}
#file-tag-list { #file-tag-list {
height: calc(100% - 8em); height: calc(100% - 8em);
overflow: auto; overflow: auto;

@ -1,15 +1,18 @@
import { Component, OnInit } from '@angular/core'; import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {FileService} from "../../services/file/file.service"; import {FileService} from "../../services/file/file.service";
import {File} from "../../models/File"; import {File} from "../../models/File";
import {PageEvent} from "@angular/material/paginator";
import {Lightbox, LIGHTBOX_EVENT, LightboxEvent} from "ngx-lightbox"; import {Lightbox, LIGHTBOX_EVENT, LightboxEvent} from "ngx-lightbox";
import {SafeResourceUrl} from "@angular/platform-browser";
import {ErrorBrokerService} from "../../services/error-broker/error-broker.service"; import {ErrorBrokerService} from "../../services/error-broker/error-broker.service";
import {TagService} from "../../services/tag/tag.service"; import {TagService} from "../../services/tag/tag.service";
import {Tag} from "../../models/Tag"; import {Tag} from "../../models/Tag";
import {MatChipInputEvent} from "@angular/material/chips"; import {MatChipInputEvent} from "@angular/material/chips";
import {COMMA, ENTER} from "@angular/cdk/keycodes"; import {COMMA, ENTER} from "@angular/cdk/keycodes";
import {MatSelectionListChange} from "@angular/material/list"; import {MatSelectionListChange} from "@angular/material/list";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
import {Observable} from "rxjs";
import {map, startWith} from "rxjs/operators";
import {FormControl} from "@angular/forms";
import {FileSearchComponent} from "../../components/file-search/file-search.component";
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -18,22 +21,22 @@ import {MatSelectionListChange} from "@angular/material/list";
}) })
export class HomeComponent implements OnInit { export class HomeComponent implements OnInit {
files: File[] = [];
tags: Tag[] = []; tags: Tag[] = [];
searchTags: string[] = []; files: File[] = [];
private openingLightbox = false; private openingLightbox = false;
searchInputSeparators = [ENTER, COMMA];
@ViewChild('filesearch') fileSearch!: FileSearchComponent;
constructor( constructor(
private errorBroker: ErrorBrokerService, private errorBroker: ErrorBrokerService,
private fileService: FileService, private fileService: FileService,
private tagService: TagService, private tagService: TagService,
private lightbox: Lightbox, private lightbox: Lightbox,
private lightboxEvent: LightboxEvent) { } private lightboxEvent: LightboxEvent) {
}
async ngOnInit() { async ngOnInit() {
this.fileService.displayedFiles.subscribe((files) => this.files = files); this.fileService.displayedFiles.subscribe((files) => this.files = files);
await this.fileService.getFiles();
} }
async onFileMultiSelect(files: File[]) { async onFileMultiSelect(files: File[]) {
@ -58,32 +61,15 @@ export class HomeComponent implements OnInit {
this.tags = await this.tagService.getTagsForFile(file.hash); this.tags = await this.tagService.getTagsForFile(file.hash);
} }
async removeSearchTag(tag: string) {
const index = this.searchTags.indexOf(tag);
if (index >= 0) {
this.searchTags.splice(index, 1);
}
await this.fileService.findFiles(this.searchTags);
}
async addSearchTagFromList(event: MatSelectionListChange) { async addSearchTagFromList(event: MatSelectionListChange) {
if (event.options.length > 0) { if (event.options.length > 0) {
const tag = event.options[0].value; const tag = event.options[0].value;
this.searchTags.push(tag); this.fileSearch.addSearchTag(tag);
await this.fileService.findFiles(this.searchTags); await this.fileSearch.searchForFiles();
} }
event.source.deselectAll(); event.source.deselectAll();
} }
async addSearchTag(event: MatChipInputEvent) {
const tag = event.value.trim();
if (tag.length > 0) {
this.searchTags.push(tag);
event.chipInput?.clear();
await this.fileService.findFiles(this.searchTags);
}
}
async openFile(file: File) { async openFile(file: File) {
if (this.openingLightbox) { if (this.openingLightbox) {
return; return;
@ -91,7 +77,7 @@ export class HomeComponent implements OnInit {
this.openingLightbox = true; this.openingLightbox = true;
try { try {
await this.openLightbox(file); await this.openLightbox(file);
} catch(err) { } catch (err) {
this.errorBroker.showError(err); this.errorBroker.showError(err);
} }
this.openingLightbox = false; this.openingLightbox = false;

@ -2,17 +2,25 @@ import { Injectable } from '@angular/core';
import {RepositoryService} from "../repository/repository.service"; import {RepositoryService} from "../repository/repository.service";
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {ErrorBrokerService} from "../error-broker/error-broker.service"; import {ErrorBrokerService} from "../error-broker/error-broker.service";
import {TagService} from "../tag/tag.service";
import {FileService} from "../file/file.service";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class DataloaderService { export class DataloaderService {
constructor(private erroBroker: ErrorBrokerService, private repositoryService: RepositoryService) { } constructor(
private erroBroker: ErrorBrokerService,
private repositoryService: RepositoryService,
private fileService: FileService,
private tagService: TagService) { }
public async loadData() { public async loadData() {
try { try {
await this.repositoryService.loadRepositories(); await this.repositoryService.loadRepositories();
await this.tagService.loadTags();
await this.fileService.getFiles();
} catch (err) { } catch (err) {
this.erroBroker.showError(err); this.erroBroker.showError(err);
} }

@ -1,14 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {invoke} from "@tauri-apps/api/tauri"; import {invoke} from "@tauri-apps/api/tauri";
import {Tag} from "../../models/Tag"; import {Tag} from "../../models/Tag";
import {BehaviorSubject, Observable} from "rxjs";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TagService { export class TagService {
public tags: BehaviorSubject<Tag[]> = new BehaviorSubject<Tag[]>([]);
constructor() { } constructor() { }
public async loadTags() {
const tags = await invoke<Tag[]>("plugin:mediarepo|get_all_tags");
this.tags.next(tags.map(t => new Tag(t.id, t.name, t.namespace)));
}
public async getTagsForFile(hash: string): Promise<Tag[]> { public async getTagsForFile(hash: string): Promise<Tag[]> {
const tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_file", {hash}); const tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_file", {hash});
return tags.map(t => new Tag(t.id, t.name, t.namespace)); return tags.map(t => new Tag(t.id, t.name, t.namespace));

Loading…
Cancel
Save