diff --git a/mediarepo-ui/src/app/app.module.ts b/mediarepo-ui/src/app/app.module.ts index 62057b4..042f63c 100644 --- a/mediarepo-ui/src/app/app.module.ts +++ b/mediarepo-ui/src/app/app.module.ts @@ -1,12 +1,12 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; +import {NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { RepositoriesComponent } from './pages/repositories/repositories.component'; -import { HomeComponent } from './pages/home/home.component'; -import { RepositoryCardComponent } from './pages/repositories/repository-card/repository-card.component'; +import {AppRoutingModule} from './app-routing.module'; +import {AppComponent} from './app.component'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {RepositoriesComponent} from './pages/repositories/repositories.component'; +import {HomeComponent} from './pages/home/home.component'; +import {RepositoryCardComponent} from './pages/repositories/repository-card/repository-card.component'; import {MatCardModule} from "@angular/material/card"; import {MatListModule} from "@angular/material/list"; import {MatButtonModule} from "@angular/material/button"; @@ -15,11 +15,11 @@ import {MatSnackBarModule} from "@angular/material/snack-bar"; import {MatFormFieldModule} from "@angular/material/form-field"; import {MatInputModule} from "@angular/material/input"; import {ReactiveFormsModule} from "@angular/forms"; -import { RepoFormComponent } from './pages/repositories/repo-form/repo-form.component'; -import { FileGridComponent } from './components/file-grid/file-grid.component'; +import {RepoFormComponent} from './pages/repositories/repo-form/repo-form.component'; +import {FileGridComponent} from './components/file-grid/file-grid.component'; import {MatSidenavModule} from "@angular/material/sidenav"; import {MatGridListModule} from "@angular/material/grid-list"; -import { FileGridEntryComponent } from './components/file-grid/file-grid-entry/file-grid-entry.component'; +import {FileGridEntryComponent} from './components/file-grid/file-grid-entry/file-grid-entry.component'; import {MatProgressBarModule} from "@angular/material/progress-bar"; import {MatPaginatorModule} from "@angular/material/paginator"; import {ScrollingModule} from "@angular/cdk/scrolling"; @@ -27,11 +27,14 @@ import {LightboxModule} from "ngx-lightbox"; import {MatChipsModule} from "@angular/material/chips"; import {MatIconModule} from "@angular/material/icon"; 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 { 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"; +import {FilterDialogComponent} from './components/file-search/filter-dialog/filter-dialog.component'; +import {MatDialogModule} from "@angular/material/dialog"; +import {MatSelectModule} from "@angular/material/select"; @NgModule({ declarations: [ @@ -44,6 +47,7 @@ import {MatRippleModule} from "@angular/material/core"; FileGridEntryComponent, FileSearchComponent, SearchPageComponent, + FilterDialogComponent, ], imports: [ BrowserModule, @@ -69,9 +73,12 @@ import {MatRippleModule} from "@angular/material/core"; MatTabsModule, FlexModule, GridModule, - MatRippleModule + MatRippleModule, + MatDialogModule, + MatSelectModule, ], providers: [], bootstrap: [AppComponent] }) -export class AppModule { } +export class AppModule { +} diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts index 0dc7d5b..76c568c 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts +++ b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts @@ -45,7 +45,7 @@ export class FileGridEntryComponent implements OnInit, OnDestroy { try { const thumbnails = await this.fileService.getThumbnails(this.gridEntry.file.hash); let thumbnail = thumbnails.find(t => (t.height > 250 || t.width > 250) && (t.height < 500 && t.width < 500)); - this.selectedThumbnail = thumbnail; + this.selectedThumbnail = thumbnail ?? thumbnails[0]; if (!thumbnail) { console.log("Thumbnail is empty?!", thumbnails); diff --git a/mediarepo-ui/src/app/components/file-search/file-search.component-theme.scss b/mediarepo-ui/src/app/components/file-search/file-search.component-theme.scss index 7f06ba8..7afff2e 100644 --- a/mediarepo-ui/src/app/components/file-search/file-search.component-theme.scss +++ b/mediarepo-ui/src/app/components/file-search/file-search.component-theme.scss @@ -14,6 +14,10 @@ $warn-palette: map.get($color-config, 'warn'); mat-form-field:focus { background-color: dimgrey; } + + #sort-button { + background-color: darken(dimgrey, 5); + } } @mixin typography($theme) { diff --git a/mediarepo-ui/src/app/components/file-search/file-search.component.html b/mediarepo-ui/src/app/components/file-search/file-search.component.html index e76577f..fbb4360 100644 --- a/mediarepo-ui/src/app/components/file-search/file-search.component.html +++ b/mediarepo-ui/src/app/components/file-search/file-search.component.html @@ -1,7 +1,8 @@
-
{{tag.getNormalizedTag()}}
+
{{tag.getNormalizedTag()}}
diff --git a/mediarepo-ui/src/app/components/file-search/file-search.component.scss b/mediarepo-ui/src/app/components/file-search/file-search.component.scss index 88db06f..15a087f 100644 --- a/mediarepo-ui/src/app/components/file-search/file-search.component.scss +++ b/mediarepo-ui/src/app/components/file-search/file-search.component.scss @@ -29,6 +29,16 @@ top: 0; } +#sort-button { + width: 100%; +} + +::ng-deep #sort-button { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + .tag-input-list-inner { display: inline-flex; flex-direction: row; 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 ed2f5ee..04a56a6 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 @@ -7,11 +7,13 @@ import { import {TagService} from "../../services/tag/tag.service"; import {FileService} from "../../services/file/file.service"; import {FormControl} from "@angular/forms"; -import {COMMA} from "@angular/cdk/keycodes"; import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; import {map, startWith} from "rxjs/operators"; import {Observable} from "rxjs"; import {TagQuery} from "../../models/TagQuery"; +import {SortKey} from "../../models/SortKey"; +import {MatDialog} from "@angular/material/dialog"; +import {FilterDialogComponent} from "./filter-dialog/filter-dialog.component"; @Component({ selector: 'app-file-search', @@ -23,7 +25,8 @@ export class FileSearchComponent implements AfterViewChecked { this.inputList.nativeElement.scrollLeft = this.inputList.nativeElement.scrollWidth; } - public searchInputSeparators = [COMMA]; + public sortExpression: SortKey[] = [new SortKey("FileImportedTime", + "Ascending", undefined)]; public formControl = new FormControl(); public searchTags: TagQuery[] = []; public suggestionTags: Observable; @@ -32,7 +35,7 @@ export class FileSearchComponent implements AfterViewChecked { @ViewChild("tagInput") tagInput!: ElementRef; @ViewChild("tagInputList") inputList!: ElementRef; - constructor(private tagService: TagService, private fileService: FileService) { + constructor(private tagService: TagService, private fileService: FileService, public dialog: MatDialog) { this.tagService.tags.subscribe( (tag) => this.allTags = tag.map(t => t.getNormalizedOutput())); @@ -45,7 +48,7 @@ export class FileSearchComponent implements AfterViewChecked { } public async searchForFiles() { - await this.fileService.findFiles(this.searchTags); + await this.fileService.findFiles(this.searchTags, this.sortExpression); } public addSearchTag(tag: string) { @@ -63,7 +66,7 @@ export class FileSearchComponent implements AfterViewChecked { async removeAllSearchTags() { this.searchTags = []; - await this.searchForFiles(); + await this.searchForFiles(); } async removeSearchTag(tag: TagQuery) { @@ -92,4 +95,18 @@ export class FileSearchComponent implements AfterViewChecked { this.tagInput.nativeElement.value = ''; await this.searchForFiles(); } + + openSortDialog() { + 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: { + sortEntries, + }, + }); + openedDialog.afterClosed().subscribe(async (sortExpression) => { + this.sortExpression = sortExpression; + await this.searchForFiles(); + }); + } } diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.html b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.html new file mode 100644 index 0000000..0c1febe --- /dev/null +++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.html @@ -0,0 +1,37 @@ +

Sort Entries

+
+
+
+ + Key + + Namespace + File Name + File Size + Time Imported + Time Created + Time Changed + File Type + Number of Tags + + + + Namespace Name + + +
+ + Direction + + Ascending + Descending + + + + +
+
+
+
+ +
diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.scss b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.scss new file mode 100644 index 0000000..a66069b --- /dev/null +++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.scss @@ -0,0 +1,24 @@ +mat-dialog-content { + height: 100%; +} + +.sort-input-list { + display: table; + width: 100%; +} + +.sort-input-row { + display: table-row; + width: 100%; +} + +mat-form-field, .filler { + display: table-cell; + padding: 0 1em; +} + +.dialog-actions { + align-items: center; + text-align: center; + width: 100%; +} diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.spec.ts b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.spec.ts new file mode 100644 index 0000000..7c3bedb --- /dev/null +++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FilterDialogComponent } from './filter-dialog.component'; + +describe('FilterDialogComponent', () => { + let component: FilterDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FilterDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FilterDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.ts b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.ts new file mode 100644 index 0000000..0b4ce10 --- /dev/null +++ b/mediarepo-ui/src/app/components/file-search/filter-dialog/filter-dialog.component.ts @@ -0,0 +1,31 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {SortKey} from "../../../models/SortKey"; + +@Component({ + selector: 'app-filter-dialog', + templateUrl: './filter-dialog.component.html', + styleUrls: ['./filter-dialog.component.scss'] +}) +export class FilterDialogComponent { + + public sortEntries: SortKey[] = [] + + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) data: any) { + this.sortEntries = data.sortEntries; + } + + addNewSortKey() { + const sortKey = new SortKey("FileName", "Ascending", undefined); + this.sortEntries.push(sortKey) + } + + public removeSortKey(sortKey: SortKey): void { + const index = this.sortEntries.indexOf(sortKey); + this.sortEntries.splice(index, 1); + } + + public confirmSort(): void { + this.dialogRef.close(this.sortEntries); + } +} diff --git a/mediarepo-ui/src/app/models/SortKey.ts b/mediarepo-ui/src/app/models/SortKey.ts new file mode 100644 index 0000000..0616dbd --- /dev/null +++ b/mediarepo-ui/src/app/models/SortKey.ts @@ -0,0 +1,29 @@ +export class SortKey { + + + constructor( + public sortType: "Namespace" | "FileName" | "FileSize" | "FileImportedTime" | "FileCreatedTime" | "FileChangeTime" | "FileType" | "NumTags", + public sortDirection: "Ascending" | "Descending", + public namespaceName: string | undefined + ) {} + + public toString(): string { + if (this.sortType == "Namespace") { + return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}` + } else { + return `${this.sortType} ${this.sortDirection}` + } + } + + public toBackendType(): any { + + if (this.sortType == "Namespace") { + return {"Namespace": {direction: this.sortDirection, tag: this.namespaceName}} + } else { + let returnObj: any = {}; + returnObj[this.sortType] = this.sortDirection; + + return returnObj; + } + } +} diff --git a/mediarepo-ui/src/app/pages/home/search-page/search-page.component.html b/mediarepo-ui/src/app/pages/home/search-page/search-page.component.html index a47c733..3359c08 100644 --- a/mediarepo-ui/src/app/pages/home/search-page/search-page.component.html +++ b/mediarepo-ui/src/app/pages/home/search-page/search-page.component.html @@ -1,7 +1,7 @@
-
+
diff --git a/mediarepo-ui/src/app/pages/home/search-page/search-page.component.ts b/mediarepo-ui/src/app/pages/home/search-page/search-page.component.ts index b4e6fe6..93f2988 100644 --- a/mediarepo-ui/src/app/pages/home/search-page/search-page.component.ts +++ b/mediarepo-ui/src/app/pages/home/search-page/search-page.component.ts @@ -7,6 +7,7 @@ import {FileService} from "../../../services/file/file.service"; import {TagService} from "../../../services/tag/tag.service"; import {Lightbox, LIGHTBOX_EVENT, LightboxEvent} from "ngx-lightbox"; import {MatSelectionListChange} from "@angular/material/list"; +import {SortKey} from "../../../models/SortKey"; @Component({ selector: 'app-search-page', @@ -31,6 +32,7 @@ export class SearchPageComponent implements OnInit { async ngOnInit() { this.fileService.displayedFiles.subscribe((files) => this.files = files); + await this.fileService.findFiles([], [new SortKey("FileImportedTime", "Ascending", undefined)]) } async onFileMultiSelect(files: File[]) { diff --git a/mediarepo-ui/src/app/services/dataloader/dataloader.service.ts b/mediarepo-ui/src/app/services/dataloader/dataloader.service.ts index ecd78ef..ddf6d82 100644 --- a/mediarepo-ui/src/app/services/dataloader/dataloader.service.ts +++ b/mediarepo-ui/src/app/services/dataloader/dataloader.service.ts @@ -12,13 +12,11 @@ export class DataloaderService { constructor( private erroBroker: ErrorBrokerService, - private fileService: FileService, private tagService: TagService) { } public async loadData() { try { await this.tagService.loadTags(); - await this.fileService.findFiles([]); } catch (err) { this.erroBroker.showError(err); } diff --git a/mediarepo-ui/src/app/services/file/file.service.ts b/mediarepo-ui/src/app/services/file/file.service.ts index 833b50c..a782dd2 100644 --- a/mediarepo-ui/src/app/services/file/file.service.ts +++ b/mediarepo-ui/src/app/services/file/file.service.ts @@ -5,6 +5,7 @@ import {invoke} from "@tauri-apps/api/tauri"; import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; import {Thumbnail} from "../../models/Thumbnail"; import {TagQuery} from "../../models/TagQuery"; +import {SortKey} from "../../models/SortKey"; @Injectable({ providedIn: 'root' @@ -22,15 +23,8 @@ export class FileService { this.displayedFiles.next(all_files); } - public async findFiles(tags: TagQuery[]) { - 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("plugin:mediarepo|find_files", {tags, sortBy}); + public async findFiles(tags: TagQuery[], sortBy: SortKey[]) { + let files = await invoke("plugin:mediarepo|find_files", {tags, sortBy: sortBy.map(k => k.toBackendType())}); this.displayedFiles.next(files); }