diff --git a/mediarepo-ui/src-tauri/tauri.conf.json b/mediarepo-ui/src-tauri/tauri.conf.json index 57a804c..fed9168 100644 --- a/mediarepo-ui/src-tauri/tauri.conf.json +++ b/mediarepo-ui/src-tauri/tauri.conf.json @@ -54,8 +54,8 @@ "windows": [ { "title": "Mediarepo", - "width": 800, - "height": 600, + "width": 1920, + "height": 1080, "resizable": true, "fullscreen": false } @@ -64,4 +64,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" } } -} \ No newline at end of file +} diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component-theme.scss b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component-theme.scss new file mode 100644 index 0000000..e673d08 --- /dev/null +++ b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component-theme.scss @@ -0,0 +1,27 @@ +@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'); + + mat-card.selected { + background-color: mat.get-color-from-palette($primary-palette); + } +} + +@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); + } +} 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 6a9f2d3..ed5d444 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,4 +1,4 @@ - + {{file?.name}} File Image diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.scss b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.scss index 4af898b..636b936 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.scss +++ b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.scss @@ -1,6 +1,8 @@ mat-card { height: calc(100% - 32px); width: calc(100% - 32px); + user-select: none; + cursor: pointer; } mat-card-content { 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 6cec473..1369762 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 @@ -20,9 +20,10 @@ import {Thumbnail} from "../../../models/Thumbnail"; export class FileGridEntryComponent implements OnInit, OnDestroy { @ViewChild("card") card!: ElementRef; - @Input() file!: File; - @Output() clickEvent = new EventEmitter(); - @Output() dblClickEvent = new EventEmitter(); + @Input() public file!: File; + @Output() clickEvent = new EventEmitter(); + @Output() dblClickEvent = new EventEmitter(); + public selected: boolean = false; selectedThumbnail: Thumbnail | undefined; contentUrl: SafeResourceUrl | undefined; diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid.component.html b/mediarepo-ui/src/app/components/file-grid/file-grid.component.html index f1d4237..039bb9e 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid.component.html +++ b/mediarepo-ui/src/app/components/file-grid/file-grid.component.html @@ -1,7 +1,7 @@
-
diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts b/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts index d113a1e..b51dff3 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts +++ b/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts @@ -1,6 +1,14 @@ -import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import { + Component, + EventEmitter, + HostListener, + Input, + OnInit, + Output, QueryList, ViewChildren +} from '@angular/core'; import {File} from "../../models/File"; import {FileService} from "../../services/file/file.service"; +import {FileGridEntryComponent} from "./file-grid-entry/file-grid-entry.component"; @Component({ selector: 'app-file-grid', @@ -11,7 +19,69 @@ export class FileGridComponent { @Input() fileRows: File[][] = []; @Output() fileDblClickEvent = new EventEmitter(); - @Output() fileClickEvent = new EventEmitter(); + @Output() filesSelectEvent = new EventEmitter(); + + @ViewChildren(FileGridEntryComponent) childQuery!: QueryList; + + selectedEntries: FileGridEntryComponent[] = []; + + private shiftClicked = false; + private ctrlClicked = false; constructor() { } + + /** + * File selector logic + * @param {FileGridEntryComponent} entry + */ + setSelectedFile(entry: FileGridEntryComponent) { + if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) { + this.selectedEntries.forEach(entry => entry.selected = false); + this.selectedEntries = []; + } + // shift selector (forwards and backwards) + if (this.shiftClicked && this.selectedEntries.length > 0) { + const lastEntry = this.selectedEntries[this.selectedEntries.length - 1]; + let found = false; + + // TODO: change to use wrapped entry files instead because of reused components + for (const child of this.childQuery) { + + if (found) { + child.selected = true; + this.selectedEntries.push(child); + if (child === entry || child == lastEntry) { + break; + } + } else if (child === lastEntry || child === entry) { + found = true; + if (child === entry) { + child.selected = true; + this.selectedEntries.push(child); + } + } + + } + } else { + entry.selected = true; + this.selectedEntries.push(entry); + } + this.filesSelectEvent.emit(this.selectedEntries.map(entry => entry.file)); + } + + @HostListener("window:keydown", ["$event"]) + private handleKeydownEvent(event: KeyboardEvent) { + switch (event.key) { + case "Shift": this.shiftClicked = true; break; + case "Control": this.ctrlClicked = true; break; + } + } + + @HostListener("window:keyup", ["$event"]) + private handleKeyupEvent(event: KeyboardEvent) { + switch (event.key) { + case "Shift": this.shiftClicked = false; break; + case "Control": this.ctrlClicked = false; break; + } + } } diff --git a/mediarepo-ui/src/app/pages/home/home.component.scss b/mediarepo-ui/src/app/pages/home/home.component.scss index c6d00f6..d11bdf8 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.scss +++ b/mediarepo-ui/src/app/pages/home/home.component.scss @@ -20,7 +20,7 @@ mat-drawer-content { } mat-drawer-container { - height: 100%; + height: calc(100% - 64px); } app-file-grid { diff --git a/mediarepo-ui/src/app/pages/home/home.component.ts b/mediarepo-ui/src/app/pages/home/home.component.ts index 5a0783e..9ffc107 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.ts +++ b/mediarepo-ui/src/app/pages/home/home.component.ts @@ -4,6 +4,7 @@ import {File} from "../../models/File"; import {PageEvent} from "@angular/material/paginator"; import {Lightbox, LIGHTBOX_EVENT, LightboxEvent} from "ngx-lightbox"; import {SafeResourceUrl} from "@angular/platform-browser"; +import {ErrorBrokerService} from "../../services/error-broker/error-broker.service"; @Component({ selector: 'app-home', @@ -13,10 +14,9 @@ import {SafeResourceUrl} from "@angular/platform-browser"; export class HomeComponent implements OnInit { fileRows: File[][] = []; - page: number = 0; - pageSize: number = 25; + private openingLightbox = false; - constructor(private fileService: FileService, private lightbox: Lightbox, private lightboxEvent: LightboxEvent) { } + constructor(private errorBroker: ErrorBrokerService, private fileService: FileService, private lightbox: Lightbox, private lightboxEvent: LightboxEvent) { } async ngOnInit() { this.fileService.displayedFiles.subscribe((files) => this.setFileRows(files)); @@ -33,7 +33,21 @@ export class HomeComponent implements OnInit { } async openFile(file: File) { - let url = await this.fileService.readFile(file.hash, file.mime_type ?? "image/png"); + if (this.openingLightbox) { + return; + } + this.openingLightbox = true; + try { + await this.openLightbox(file); + } catch(err) { + this.errorBroker.showError(err); + } + this.openingLightbox = false; + } + + private async openLightbox(file: File): Promise { + let url = await this.fileService.readFile(file.hash, + file.mime_type ?? "image/png"); let albums = [ { @@ -48,11 +62,12 @@ export class HomeComponent implements OnInit { showDownloadButton: true, centerVertically: true, }); - const lighboxSubscription = this.lightboxEvent.lightboxEvent$.subscribe((event: any) => { - if (event?.id == LIGHTBOX_EVENT.CLOSE) { - lighboxSubscription.unsubscribe(); - URL?.revokeObjectURL(url as string); - } - }) + const lighboxSubscription = this.lightboxEvent.lightboxEvent$.subscribe( + (event: any) => { + if (event?.id == LIGHTBOX_EVENT.CLOSE) { + lighboxSubscription.unsubscribe(); + URL?.revokeObjectURL(url as string); + } + }) } } diff --git a/mediarepo-ui/src/styles.scss b/mediarepo-ui/src/styles.scss index ca508d2..7d686ba 100644 --- a/mediarepo-ui/src/styles.scss +++ b/mediarepo-ui/src/styles.scss @@ -1,6 +1,7 @@ @use 'sass:map'; @use "~@angular/material" as mat; @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; @include mat.core(); $theme: mat.define-dark-theme(( @@ -17,4 +18,4 @@ $theme: mat.define-dark-theme(( @include mat.all-component-themes($theme); @include app.theme($theme); - +@include file-grid-entry.theme($theme);