Improve file search input

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

@ -17,6 +17,7 @@
"@angular/common": "~12.2.0", "@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0", "@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0", "@angular/core": "~12.2.0",
"@angular/flex-layout": "^12.0.0-beta.35",
"@angular/forms": "~12.2.0", "@angular/forms": "~12.2.0",
"@angular/material": "12.2.9", "@angular/material": "12.2.9",
"@angular/platform-browser": "~12.2.0", "@angular/platform-browser": "~12.2.0",

@ -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=547947b05a28bb8a9191d3691d91dc7e3c68af0f#547947b05a28bb8a9191d3691d91dc7e3c68af0f" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=476b9d152457f78c73f6f6a36c2421cbce9c9194#476b9d152457f78c73f6f6a36c2421cbce9c9194"
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 = "547947b05a28bb8a9191d3691d91dc7e3c68af0f" rev = "476b9d152457f78c73f6f6a36c2421cbce9c9194"
features = ["tauri-plugin"] features = ["tauri-plugin"]
[features] [features]

@ -30,6 +30,8 @@ import {MatAutocompleteModule} from "@angular/material/autocomplete";
import { FileSearchComponent } from './components/file-search/file-search.component'; import { FileSearchComponent } from './components/file-search/file-search.component';
import {MatTabsModule} from "@angular/material/tabs"; import {MatTabsModule} from "@angular/material/tabs";
import { SearchPageComponent } from './pages/home/search-page/search-page.component'; import { SearchPageComponent } from './pages/home/search-page/search-page.component';
import {FlexModule, GridModule} from "@angular/flex-layout";
import {MatRippleModule} from "@angular/material/core";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -64,7 +66,10 @@ import { SearchPageComponent } from './pages/home/search-page/search-page.compon
MatChipsModule, MatChipsModule,
MatIconModule, MatIconModule,
MatAutocompleteModule, MatAutocompleteModule,
MatTabsModule MatTabsModule,
FlexModule,
GridModule,
MatRippleModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

@ -0,0 +1,32 @@
@use 'sass:map';
@use '~@angular/material' as mat;
@mixin color($theme) {
$color-config: mat.get-color-config($theme);
$primary-palette: map.get($color-config, 'primary');
$warn-palette: map.get($color-config, 'warn');
.tag-input-item {
background-color: dimgrey;
border-radius: 1em;
}
mat-form-field:focus {
background-color: dimgrey;
}
}
@mixin typography($theme) {
}
@mixin theme($theme) {
$color-config: mat.get-color-config($theme);
@if $color-config != null {
@include color($theme);
}
$typography-config: mat.get-typography-config($theme);
@if $typography-config != null {
@include typography($theme);
}
}

@ -1,20 +1,20 @@
<mat-form-field class="full-width" #tagSearch appearance="fill"> <div class="tag-input-list-and-actions">
<mat-chip-list #chipList> <div class="tag-input-list" #tagInputList>
<mat-chip *ngFor="let tag of searchTags" (removed)="removeSearchTag(tag)" [removable]="true"> <div class="tag-input-list-inner">
{{tag.getNormalizedTag()}} <div class="tag-input-item" *ngFor="let tag of searchTags" mat-ripple (click)="removeSearchTag(tag)">{{tag.getNormalizedTag()}}</div>
<button matChipRemove> </div>
<mat-icon>cancel</mat-icon> </div>
<button id="delete-all-tags-button" mat-button (click)="removeAllSearchTags()">
<mat-icon>delete-sweep</mat-icon>
</button> </button>
</mat-chip> </div>
</mat-chip-list> <mat-form-field class="full-width" appearance="fill">
<mat-label>Enter tags to filter for</mat-label>
<input matInput <input matInput
#tagInput #tagInput
[matChipInputFor]="chipList" (keydown)="addSearchTagByInput($event)"
[matChipInputAddOnBlur]="true"
[matChipInputSeparatorKeyCodes]="searchInputSeparators"
[matAutocomplete]="auto" [matAutocomplete]="auto"
[formControl]="formControl" [formControl]="formControl"/>
(matChipInputTokenEnd)="addSearchTagByChip($event)"/>
<mat-autocomplete #auto (optionSelected)="addSearchTagByAutocomplete($event)"> <mat-autocomplete #auto (optionSelected)="addSearchTagByAutocomplete($event)">
<mat-option *ngFor="let tag of suggestionTags | async" [value]="tag"> <mat-option *ngFor="let tag of suggestionTags | async" [value]="tag">
{{tag}} {{tag}}

@ -1,7 +1,46 @@
#tag-search { .full-width {
width: 100%; min-width: 100%;
width: auto;
} }
.full-width { .tag-input-list {
height: 2.7em;
padding: 0.5em 0;
width: calc(100% - 64px);
overflow-x: auto;
overflow-y: hidden;
box-shadow: 0 0 1em 0.1em rgba(0, 0, 0, 0.05) inset;
}
.tag-input-list-and-actions {
display: block;
height: calc(3em + 10px);
width: 100%; width: 100%;
position: relative;
}
#delete-all-tags-button {
font-size: 0.5em;
width: 64px;
height: 100%;
padding: 0;
position: absolute;
right: 0;
top: 0;
}
.tag-input-list-inner {
display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
width: auto;
height: 2.2em;
}
.tag-input-item {
width: auto;
padding: 0.5em;
margin: 0 0.5em;
cursor: pointer;
white-space: nowrap;
} }

@ -1,9 +1,13 @@
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; import {
AfterViewChecked,
Component,
ElementRef,
ViewChild
} from '@angular/core';
import {TagService} from "../../services/tag/tag.service"; import {TagService} from "../../services/tag/tag.service";
import {FileService} from "../../services/file/file.service"; import {FileService} from "../../services/file/file.service";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {COMMA, ENTER} from "@angular/cdk/keycodes"; import {COMMA} from "@angular/cdk/keycodes";
import {MatChipInputEvent} from "@angular/material/chips";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
import {map, startWith} from "rxjs/operators"; import {map, startWith} from "rxjs/operators";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
@ -14,7 +18,10 @@ import {TagQuery} from "../../models/TagQuery";
templateUrl: './file-search.component.html', templateUrl: './file-search.component.html',
styleUrls: ['./file-search.component.scss'] styleUrls: ['./file-search.component.scss']
}) })
export class FileSearchComponent { export class FileSearchComponent implements AfterViewChecked {
public ngAfterViewChecked(): void {
this.inputList.nativeElement.scrollLeft = this.inputList.nativeElement.scrollWidth;
}
public searchInputSeparators = [COMMA]; public searchInputSeparators = [COMMA];
public formControl = new FormControl(); public formControl = new FormControl();
@ -22,15 +29,19 @@ export class FileSearchComponent {
public suggestionTags: Observable<string[]>; public suggestionTags: Observable<string[]>;
private allTags: string[] = []; private allTags: string[] = [];
@ViewChild('tagInput') tagInput!: ElementRef<HTMLInputElement>; @ViewChild("tagInput") tagInput!: ElementRef<HTMLInputElement>;
@ViewChild("tagInputList") inputList!: ElementRef;
constructor(private tagService: TagService, private fileService: FileService) { constructor(private tagService: TagService, private fileService: FileService) {
this.tagService.tags.subscribe( this.tagService.tags.subscribe(
(tag) => this.allTags = tag.map(t => t.getNormalizedOutput())); (tag) => this.allTags = tag.map(t => t.getNormalizedOutput()));
this.suggestionTags = this.formControl.valueChanges.pipe(startWith(null), map( this.suggestionTags = this.formControl.valueChanges.pipe(startWith(null),
map(
(tag: string | null) => tag ? this.allTags.filter( (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))); (t: string) => t.includes(tag.replace(/^-/g, '')))
.map((t) => tag.startsWith("-") ? "-" + t : t)
.slice(0, 20) : this.allTags.slice(0, 20)));
} }
public async searchForFiles() { public async searchForFiles() {
@ -45,6 +56,11 @@ export class FileSearchComponent {
} }
} }
async removeAllSearchTags() {
this.searchTags = [];
await this.searchForFiles();
}
async removeSearchTag(tag: TagQuery) { async removeSearchTag(tag: TagQuery) {
const index = this.searchTags.indexOf(tag); const index = this.searchTags.indexOf(tag);
if (index >= 0) { if (index >= 0) {
@ -53,13 +69,15 @@ export class FileSearchComponent {
await this.searchForFiles(); await this.searchForFiles();
} }
async addSearchTagByChip(event: MatChipInputEvent) { async addSearchTagByInput(event: KeyboardEvent) {
const tag = event.value.trim(); 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.allTags.includes(tag.replace(/-/g, ''))) {
this.addSearchTag(tag); this.addSearchTag(tag);
event.chipInput?.clear();
this.formControl.setValue(null); this.formControl.setValue(null);
await this.searchForFiles(); } await this.searchForFiles();
}
}
} }
async addSearchTagByAutocomplete(event: MatAutocompleteSelectedEvent) { async addSearchTagByAutocomplete(event: MatAutocompleteSelectedEvent) {
@ -67,5 +85,6 @@ export class FileSearchComponent {
this.addSearchTag(tag); this.addSearchTag(tag);
this.formControl.setValue(null); this.formControl.setValue(null);
this.tagInput.nativeElement.value = ''; this.tagInput.nativeElement.value = '';
await this.searchForFiles(); } await this.searchForFiles();
}
} }

@ -1,8 +1,10 @@
<mat-drawer-container class="page"> <mat-drawer-container class="page">
<mat-drawer mode="side" opened> <mat-drawer mode="side" opened>
<div class="drawer-sidebar-inner"> <div fxLayout="column" class="drawer-sidebar-inner">
<div id="file-search-input" fxFlex="150px">
<app-file-search #filesearch></app-file-search> <app-file-search #filesearch></app-file-search>
<div id="file-tag-list"> </div>
<div id="file-tag-list" fxFlex fxFlexAlign="start" fxFlexFill>
<h1>Selection Tags</h1> <h1>Selection Tags</h1>
<mat-selection-list [multiple]="false" *ngIf="tags.length > 0" <mat-selection-list [multiple]="false" *ngIf="tags.length > 0"
(selectionChange)="addSearchTagFromList($event)"> (selectionChange)="addSearchTagFromList($event)">

@ -1,14 +1,28 @@
#file-tag-list {
height: calc(100% - 8em);
overflow: auto;
}
.drawer-sidebar-inner { .drawer-sidebar-inner {
height: 100%; height: 100%;
width: 100%; width: 100%;
display: block; display: block;
} }
#file-search-input {
width: 100%;
overflow: hidden;
}
app-file-search {
display: block;
width: 100%;
}
#file-tag-list {
height: 100%;
overflow-y: auto;
}
mat-selection-list {
height: 100%;
}
mat-drawer { mat-drawer {
height: 100%; height: 100%;
width: 25%; width: 25%;

@ -18,7 +18,7 @@ export class DataloaderService {
public async loadData() { public async loadData() {
try { try {
await this.tagService.loadTags(); await this.tagService.loadTags();
await this.fileService.getFiles(); await this.fileService.findFiles([]);
} catch (err) { } catch (err) {
this.erroBroker.showError(err); this.erroBroker.showError(err);
} }

@ -23,7 +23,14 @@ export class FileService {
} }
public async findFiles(tags: TagQuery[]) { public async findFiles(tags: TagQuery[]) {
let files = await invoke<File[]>("plugin:mediarepo|find_files", {tags}); const sortBy: any[] = [
{Namespace: {tag: "creator", direction: "Descending"}},
{Namespace: {tag: "series", direction: "Ascending"}},
{Namespace: {tag: "title", direction: "Ascending"}},
{Namespace: {tag: "page", direction: "Ascending"}},
{Namespace: {tag: "panel", direction: "Ascending"}},
];
let files = await invoke<File[]>("plugin:mediarepo|find_files", {tags, sortBy});
this.displayedFiles.next(files); this.displayedFiles.next(files);
} }

@ -2,6 +2,7 @@
@use "~@angular/material" as mat; @use "~@angular/material" as mat;
@use 'src/app/app.component-theme' as app; @use 'src/app/app.component-theme' as app;
@use 'src/app/components/file-grid/file-grid-entry/file-grid-entry.component-theme' as file-grid-entry; @use 'src/app/components/file-grid/file-grid-entry/file-grid-entry.component-theme' as file-grid-entry;
@use 'src/app/components/file-search/file-search.component-theme' as file-search;
@include mat.core(); @include mat.core();
$theme: mat.define-dark-theme(( $theme: mat.define-dark-theme((
@ -9,7 +10,7 @@ $theme: mat.define-dark-theme((
primary: mat.define-palette(mat.$deep-purple-palette), primary: mat.define-palette(mat.$deep-purple-palette),
accent: mat.define-palette(mat.$green-palette), accent: mat.define-palette(mat.$green-palette),
warn: mat.define-palette(mat.$red-palette), warn: mat.define-palette(mat.$red-palette),
background: mat.define-palette(mat.$grey-palette) background: mat.define-palette(mat.$blue-grey-palette)
), ),
typography: mat.define-typography-config( typography: mat.define-typography-config(
$font-family: 'Noto Sans', $font-family: 'Noto Sans',
@ -19,3 +20,4 @@ $theme: mat.define-dark-theme((
@include mat.all-component-themes($theme); @include mat.all-component-themes($theme);
@include app.theme($theme); @include app.theme($theme);
@include file-grid-entry.theme($theme); @include file-grid-entry.theme($theme);
@include file-search.theme($theme);

@ -264,6 +264,13 @@
dependencies: dependencies:
tslib "^2.2.0" tslib "^2.2.0"
"@angular/flex-layout@^12.0.0-beta.35":
version "12.0.0-beta.35"
resolved "https://registry.yarnpkg.com/@angular/flex-layout/-/flex-layout-12.0.0-beta.35.tgz#b52c3c82608cbb92a119f8dcde2a5b98186fe558"
integrity sha512-nPi2MGDFuCacwWHqxF/G7lUJd2X99HbLjjUvKXnyLwyCIVgH1sfS52su2wYbVYWJRqAVAB2/VMlrtW8Khr8hDA==
dependencies:
tslib "^2.1.0"
"@angular/forms@~12.2.0": "@angular/forms@~12.2.0":
version "12.2.9" version "12.2.9"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-12.2.9.tgz#6d39b03413e420d3c3914ac018bc6f6f90ead39a" resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-12.2.9.tgz#6d39b03413e420d3c3914ac018bc6f6f90ead39a"

Loading…
Cancel
Save