Refactor file grid and gallery to use the same type of thumbnail container

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent 8728fcdc71
commit 62cba1ce56

@ -1,5 +1,5 @@
@use 'sass:map'; @use 'sass:map';
@use '../../../../../../../../node_modules/@angular/material/index' as mat; @use '../../../../../../node_modules/@angular/material/index' as mat;
@mixin color($theme) { @mixin color($theme) {
$color-config: mat.get-color-config($theme); $color-config: mat.get-color-config($theme);

@ -0,0 +1,8 @@
<mat-card #card (click)="clickEvent.emit(this)" (dblclick)="dblClickEvent.emit(this)"
[ngClass]="{'selected': entry.selected}">
<mat-card-content>
<app-busy-indicator [busy]="this.loading">
<app-file-thumbnail *ngIf="!loading" [file]="this.entry.data" class=".entry-image"></app-file-thumbnail>
</app-busy-indicator>
</mat-card-content>
</mat-card>

@ -10,6 +10,11 @@ mat-card-content {
width: 100%; width: 100%;
} }
app-busy-indicator {
height: 100%;
width: 100%;
}
.entry-image { .entry-image {
width: 100%; width: 100%;
height: 100%; height: 100%;

@ -1,20 +1,20 @@
import {ComponentFixture, TestBed} from "@angular/core/testing"; import {ComponentFixture, TestBed} from "@angular/core/testing";
import {FileGridEntryComponent} from "./file-grid-entry.component"; import {FileCardComponent} from "./file-card.component";
describe("FileGridEntryComponent", () => { describe("FileGridEntryComponent", () => {
let component: FileGridEntryComponent; let component: FileCardComponent;
let fixture: ComponentFixture<FileGridEntryComponent>; let fixture: ComponentFixture<FileCardComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [FileGridEntryComponent] declarations: [FileCardComponent]
}) })
.compileComponents(); .compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FileGridEntryComponent); fixture = TestBed.createComponent(FileCardComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

@ -0,0 +1,53 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from "@angular/core";
import {File} from "../../../../models/File";
import {SafeResourceUrl} from "@angular/platform-browser";
import {Selectable} from "../../../../models/Selectable";
@Component({
selector: "app-file-card",
templateUrl: "./file-card.component.html",
styleUrls: ["./file-card.component.scss"]
})
export class FileCardComponent implements OnInit, OnChanges {
@ViewChild("card") card!: ElementRef;
@Input() public entry!: Selectable<File>;
@Output() clickEvent = new EventEmitter<FileCardComponent>();
@Output() dblClickEvent = new EventEmitter<FileCardComponent>();
private cachedId: number | undefined;
private urlSetTimeout: number | undefined;
public loading = false;
constructor() {
}
async ngOnInit() {
this.cachedId = this.entry.data.id;
this.setImageDelayed();
}
async ngOnChanges(changes: SimpleChanges) {
if (changes["file"] && (this.cachedId === undefined || this.entry.data.id !== this.cachedId)) {
this.cachedId = this.entry.data.id;
this.setImageDelayed();
}
}
private setImageDelayed() {
this.loading = true;
clearTimeout(this.urlSetTimeout);
this.urlSetTimeout = setTimeout(
() => this.loading = false, 200);
}
}

@ -1,27 +0,0 @@
@use 'sass:map';
@use '../../../../../../../../node_modules/@angular/material/index' as mat;
@mixin color($theme) {
$color-config: mat.get-color-config($theme);
$primary-palette: map.get($color-config, 'primary');
div.image-wrapper.selected {
background-color: mat.get-color-from-palette($primary-palette, 'darker');
}
}
@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,3 +0,0 @@
<div (click)="fileSelectEvent.emit(this.file)" [class.selected]="this.file.selected" class="image-wrapper">
<app-file-thumbnail [file]="file.data"></app-file-thumbnail>
</div>

@ -1,18 +0,0 @@
app-file-thumbnail {
height: 100%;
width: 100%;
position: relative;
display: block;
}
.image-wrapper {
width: calc(100% - 20px);
height: calc(100% - 20px);
align-items: center;
text-align: center;
background-color: darken(dimgrey, 15);
padding: 10px;
border-radius: 5px;
cursor: pointer;
user-select: none;
}

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

@ -1,55 +0,0 @@
import {
Component,
EventEmitter,
Inject,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from "@angular/core";
import {File} from "../../../../../../models/File";
import {FileService} from "../../../../../../services/file/file.service";
import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser";
import {ErrorBrokerService} from "../../../../../../services/error-broker/error-broker.service";
import {Selectable} from "../../../../../../models/Selectable";
@Component({
selector: "app-file-gallery-entry",
templateUrl: "./file-gallery-entry.component.html",
styleUrls: ["./file-gallery-entry.component.scss"]
})
export class FileGalleryEntryComponent implements OnInit, OnChanges {
@Input() file!: Selectable<File>;
@Output() fileSelectEvent = new EventEmitter<Selectable<File>>();
contentUrl: SafeResourceUrl | undefined;
private cachedFile: File | undefined;
private urlSetTimeout: number | undefined;
constructor(@Inject(
DomSanitizer) private sanitizer: DomSanitizer, private fileService: FileService, private errorBroker: ErrorBrokerService) {
}
ngOnChanges(changes: SimpleChanges) {
if (changes["file"] && (!this.cachedFile || this.file.data.hash !== this.cachedFile!.hash)) { // handle changes to the file when the component is not destroyed
this.cachedFile = this.file.data;
this.setImageDelayed();
}
}
ngOnInit() {
this.cachedFile = this.file.data;
this.setImageDelayed();
}
private setImageDelayed() {
this.contentUrl = undefined;
clearTimeout(this.urlSetTimeout);
this.urlSetTimeout = setTimeout(
() => this.contentUrl = this.fileService.buildThumbnailUrl(
this.file.data,
250, 250), 200);
}
}

@ -14,8 +14,8 @@
<cdk-virtual-scroll-viewport #virtualScroll class="file-scroll-viewport" itemSize="260" maxBufferPx="3000" <cdk-virtual-scroll-viewport #virtualScroll class="file-scroll-viewport" itemSize="260" maxBufferPx="3000"
minBufferPx="1000" orientation="horizontal"> minBufferPx="1000" orientation="horizontal">
<div *cdkVirtualFor="let entry of entries" class="file-item"> <div *cdkVirtualFor="let entry of entries" class="file-item">
<app-file-gallery-entry (fileSelectEvent)="onEntrySelect($event)" <app-file-card (clickEvent)="onEntrySelect($event.entry)"
[file]="entry"></app-file-gallery-entry> [entry]="entry"></app-file-card>
</div> </div>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
</div> </div>

@ -1,6 +0,0 @@
import {File} from "../../../../../../models/File";
export type GridEntry = {
file: File,
selected: boolean,
}

@ -1,6 +0,0 @@
<mat-card #card (click)="clickEvent.emit(this)" (dblclick)="dblClickEvent.emit(this)"
[ngClass]="{'selected': gridEntry.selected}">
<mat-card-content *ngIf="contentUrl !== undefined">
<app-file-thumbnail [file]="this.gridEntry.file" class=".entry-image"></app-file-thumbnail>
</mat-card-content>
</mat-card>

@ -1,56 +0,0 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from "@angular/core";
import {File} from "../../../../../../models/File";
import {FileService} from "../../../../../../services/file/file.service";
import {SafeResourceUrl} from "@angular/platform-browser";
import {GridEntry} from "./GridEntry";
@Component({
selector: "app-file-grid-entry",
templateUrl: "./file-grid-entry.component.html",
styleUrls: ["./file-grid-entry.component.scss"]
})
export class FileGridEntryComponent implements OnInit, OnChanges {
@ViewChild("card") card!: ElementRef;
@Input() public gridEntry!: GridEntry;
@Output() clickEvent = new EventEmitter<FileGridEntryComponent>();
@Output() dblClickEvent = new EventEmitter<FileGridEntryComponent>();
contentUrl: SafeResourceUrl | undefined;
private cachedFile: File | undefined;
private urlSetTimeout: number | undefined;
constructor(private fileService: FileService) {
}
async ngOnInit() {
this.cachedFile = this.gridEntry.file;
this.setImageDelayed();
}
async ngOnChanges(changes: SimpleChanges) {
if (changes["file"] && (!this.cachedFile || this.gridEntry.file.hash !== this.cachedFile.hash)) {
this.cachedFile = this.gridEntry.file;
this.setImageDelayed();
}
}
private setImageDelayed() {
this.contentUrl = undefined;
clearTimeout(this.urlSetTimeout);
this.urlSetTimeout = setTimeout(
() => this.contentUrl = this.fileService.buildThumbnailUrl(
this.gridEntry.file,
250, 250), 200);
}
}

@ -3,12 +3,12 @@
minBufferPx="500"> minBufferPx="500">
<div *cdkVirtualFor="let rowEntry of partitionedGridEntries"> <div *cdkVirtualFor="let rowEntry of partitionedGridEntries">
<div class="file-row"> <div class="file-row">
<app-file-grid-entry <app-file-card
(clickEvent)="setSelectedFile($event.gridEntry)" (clickEvent)="setSelectedFile($event.entry)"
(contextmenu)="fileContextMenu.onContextMenu($event, gridEntry.file)" (contextmenu)="fileContextMenu.onContextMenu($event, gridEntry.data)"
(dblClickEvent)="fileOpenEvent.emit($event.gridEntry.file)" (dblClickEvent)="fileOpenEvent.emit($event.entry.data)"
*ngFor="let gridEntry of rowEntry" *ngFor="let gridEntry of rowEntry"
[gridEntry]="gridEntry"></app-file-grid-entry> [entry]="gridEntry"></app-file-card>
</div> </div>
</div> </div>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>

@ -1,4 +1,4 @@
app-file-grid-entry { app-file-card {
height: 250px; height: 250px;
width: 100%; width: 100%;
padding: 5px; padding: 5px;

@ -11,11 +11,11 @@ import {
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../../models/File"; import {File} from "../../../../../models/File";
import {FileGridEntryComponent} from "./file-grid-entry/file-grid-entry.component"; import {FileCardComponent} from "../../file-card/file-card.component";
import {GridEntry} from "./file-grid-entry/GridEntry";
import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
import {TabService} from "../../../../../services/tab/tab.service"; import {TabService} from "../../../../../services/tab/tab.service";
import {FileService} from "../../../../../services/file/file.service"; import {FileService} from "../../../../../services/file/file.service";
import {Selectable} from "../../../../../models/Selectable";
@Component({ @Component({
selector: "app-file-grid", selector: "app-file-grid",
@ -33,11 +33,11 @@ export class FileGridComponent implements OnChanges, OnInit {
@ViewChild("virtualScrollGrid") virtualScroll!: CdkVirtualScrollViewport; @ViewChild("virtualScrollGrid") virtualScroll!: CdkVirtualScrollViewport;
@ViewChild("galleryWrapper") galleryWrapper!: ElementRef<HTMLDivElement>; @ViewChild("galleryWrapper") galleryWrapper!: ElementRef<HTMLDivElement>;
selectedEntries: GridEntry[] = []; selectedEntries: Selectable<File>[] = [];
partitionedGridEntries: GridEntry[][] = []; partitionedGridEntries: Selectable<File>[][] = [];
private shiftClicked = false; private shiftClicked = false;
private ctrlClicked = false; private ctrlClicked = false;
private gridEntries: GridEntry[] = [] private gridEntries: Selectable<File>[] = []
constructor( constructor(
private tabService: TabService, private tabService: TabService,
@ -47,17 +47,15 @@ export class FileGridComponent implements OnChanges, OnInit {
} }
public ngOnInit(): void { public ngOnInit(): void {
this.gridEntries = this.files.map(file => { this.gridEntries = this.files.map(
return {file, selected: false} file => new Selectable<File>(file, false));
});
this.setPartitionedGridEntries(); this.setPartitionedGridEntries();
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes["files"]) { if (changes["files"]) {
this.gridEntries = this.files.map(file => { this.gridEntries = this.files.map(
return {file, selected: false} file => new Selectable<File>(file, false));
});
this.refreshFileSelections(); this.refreshFileSelections();
this.setPartitionedGridEntries(); this.setPartitionedGridEntries();
} }
@ -65,9 +63,9 @@ export class FileGridComponent implements OnChanges, OnInit {
/** /**
* File selector logic * File selector logic
* @param {FileGridEntryComponent} clickedEntry * @param {FileCardComponent} clickedEntry
*/ */
setSelectedFile(clickedEntry: GridEntry) { setSelectedFile(clickedEntry: Selectable<File>) {
if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) { if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) {
this.selectedEntries.forEach(entry => { this.selectedEntries.forEach(entry => {
if (entry !== clickedEntry) entry.selected = false if (entry !== clickedEntry) entry.selected = false
@ -87,7 +85,7 @@ export class FileGridComponent implements OnChanges, OnInit {
this.selectedEntries.push(clickedEntry); this.selectedEntries.push(clickedEntry);
} }
} }
this.fileSelectEvent.emit(this.selectedEntries.map(g => g.file)); this.fileSelectEvent.emit(this.selectedEntries.map(g => g.data));
} }
public adjustElementSizes(): void { public adjustElementSizes(): void {
@ -103,7 +101,7 @@ export class FileGridComponent implements OnChanges, OnInit {
private setPartitionedGridEntries() { private setPartitionedGridEntries() {
this.partitionedGridEntries = []; this.partitionedGridEntries = [];
let scrollToIndex = -1; let scrollToIndex = -1;
let selectedEntry: GridEntry | undefined = undefined; let selectedEntry: Selectable<File> | undefined = undefined;
for (let i = 0; i < (Math.ceil( for (let i = 0; i < (Math.ceil(
this.gridEntries.length / this.columns)); i++) { this.gridEntries.length / this.columns)); i++) {
@ -111,7 +109,7 @@ export class FileGridComponent implements OnChanges, OnInit {
Math.min(this.gridEntries.length, (i + 1) * this.columns)); Math.min(this.gridEntries.length, (i + 1) * this.columns));
this.partitionedGridEntries.push(entries); this.partitionedGridEntries.push(entries);
const preselectedEntry = entries.find( const preselectedEntry = entries.find(
e => e.file.hash == this.preselectedFile?.hash); e => e.data.hash == this.preselectedFile?.hash);
if (preselectedEntry) { if (preselectedEntry) {
scrollToIndex = i; scrollToIndex = i;
@ -132,14 +130,14 @@ export class FileGridComponent implements OnChanges, OnInit {
} }
private refreshFileSelections() { private refreshFileSelections() {
const newSelection: GridEntry[] = this.gridEntries.filter( const newSelection: Selectable<File>[] = this.gridEntries.filter(
entry => this.selectedEntries.findIndex( entry => this.selectedEntries.findIndex(
e => e.file.id == entry.file.id) >= 0); e => e.data.id == entry.data.id) >= 0);
newSelection.forEach(entry => entry.selected = true); newSelection.forEach(entry => entry.selected = true);
this.selectedEntries = newSelection; this.selectedEntries = newSelection;
} }
private handleShiftSelect(clickedEntry: GridEntry): void { private handleShiftSelect(clickedEntry: Selectable<File>): void {
const lastEntry = this.selectedEntries[this.selectedEntries.length - 1]; const lastEntry = this.selectedEntries[this.selectedEntries.length - 1];
let found = false; let found = false;
if (clickedEntry == lastEntry) { if (clickedEntry == lastEntry) {

@ -1,8 +1,8 @@
import { import {
Component, Component, EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnInit, OnInit, Output,
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../models/File";

@ -3,8 +3,7 @@ import { CommonModule } from "@angular/common";
import {FileMultiviewComponent} from "./file-multiview/file-multiview.component"; import {FileMultiviewComponent} from "./file-multiview/file-multiview.component";
import {FileGridComponent} from "./file-multiview/file-grid/file-grid.component"; import {FileGridComponent} from "./file-multiview/file-grid/file-grid.component";
import {FileGalleryComponent} from "./file-multiview/file-gallery/file-gallery.component"; import {FileGalleryComponent} from "./file-multiview/file-gallery/file-gallery.component";
import {FileGalleryEntryComponent} from "./file-multiview/file-gallery/file-gallery-entry/file-gallery-entry.component"; import {FileCardComponent} from "./file-card/file-card.component";
import {FileGridEntryComponent} from "./file-multiview/file-grid/file-grid-entry/file-grid-entry.component";
import {FileContextMenuComponent} from "./file-context-menu/file-context-menu.component"; import {FileContextMenuComponent} from "./file-context-menu/file-context-menu.component";
import {FileThumbnailComponent} from "./file-thumbnail/file-thumbnail.component"; import {FileThumbnailComponent} from "./file-thumbnail/file-thumbnail.component";
import {ContentViewerComponent} from "./content-viewer/content-viewer.component"; import {ContentViewerComponent} from "./content-viewer/content-viewer.component";
@ -30,8 +29,7 @@ import {MatCardModule} from "@angular/material/card";
FileMultiviewComponent, FileMultiviewComponent,
FileGridComponent, FileGridComponent,
FileGalleryComponent, FileGalleryComponent,
FileGalleryEntryComponent, FileCardComponent,
FileGridEntryComponent,
FileContextMenuComponent, FileContextMenuComponent,
FileThumbnailComponent, FileThumbnailComponent,
ContentViewerComponent, ContentViewerComponent,

@ -1,9 +1,8 @@
@use 'sass:map'; @use 'sass:map';
@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 'app/components/shared/file/file-multiview/file-grid/file-grid-entry/file-grid-entry.component-theme' as file-grid-entry; @use 'app/components/shared/file/file-card/file-card.component-theme' as file-card;
@use 'app/components/shared/sidebar/file-search/file-search.component-theme' as file-search; @use 'app/components/shared/sidebar/file-search/file-search.component-theme' as file-search;
@use "app/components/shared/file/file-multiview/file-gallery/file-gallery-entry/file-gallery-entry.component-theme" as gallery-entry;
@include mat.core(); @include mat.core();
@ -21,6 +20,5 @@ $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-card.theme($theme);
@include file-search.theme($theme); @include file-search.theme($theme);
@include gallery-entry.theme($theme);

Loading…
Cancel
Save