Add zoom to gallery view

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

@ -42,6 +42,7 @@ import {BlockUIModule} from "primeng/blockui";
import {PanelModule} from "primeng/panel"; import {PanelModule} from "primeng/panel";
import {DragDropModule} from "@angular/cdk/drag-drop"; import {DragDropModule} from "@angular/cdk/drag-drop";
import { ContentAwareImageComponent } from './components/content-aware-image/content-aware-image.component'; import { ContentAwareImageComponent } from './components/content-aware-image/content-aware-image.component';
import {MatSliderModule} from "@angular/material/slider";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -90,6 +91,7 @@ import { ContentAwareImageComponent } from './components/content-aware-image/con
BlockUIModule, BlockUIModule,
PanelModule, PanelModule,
DragDropModule, DragDropModule,
MatSliderModule,
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

@ -22,10 +22,8 @@ export class ContentAwareImageComponent {
public adjustSize(image: HTMLImageElement, imageContainer: HTMLDivElement): void { public adjustSize(image: HTMLImageElement, imageContainer: HTMLDivElement): void {
const containerHeight = Math.abs(imageContainer.clientHeight); const containerHeight = Math.abs(imageContainer.clientHeight);
const containerWidth = Math.abs(imageContainer.clientWidth); const containerWidth = Math.abs(imageContainer.clientWidth);
console.log(containerHeight, ',', containerWidth);
const imageRelativeHeight = image.height / containerHeight; const imageRelativeHeight = image.height / containerHeight;
const imageRelativeWidth = image.width / containerWidth; const imageRelativeWidth = image.width / containerWidth;
console.log(imageRelativeHeight, ',', imageRelativeWidth);
this.scaleWidth = imageRelativeWidth > imageRelativeHeight; this.scaleWidth = imageRelativeWidth > imageRelativeHeight;
} }
} }

@ -4,11 +4,22 @@
</button> </button>
<div class="file-full-view" fxFlex="80%" <div class="file-full-view" fxFlex="80%"
(dblclick)="this.selectedFile? this.fileDblClickEvent.emit(this.selectedFile.data) : null"> (dblclick)="this.selectedFile? this.fileDblClickEvent.emit(this.selectedFile.data) : null">
<div class="file-full-view-inner" #imageContainer> <div class="file-full-view-inner">
<div *ngIf="!this.fileContentUrl" class="url-loading-backdrop"> <div *ngIf="!this.fileContentUrl" class="url-loading-backdrop">
<mat-progress-spinner mode="indeterminate" color="primary"></mat-progress-spinner> <mat-progress-spinner mode="indeterminate" color="primary"></mat-progress-spinner>
</div> </div>
<app-content-aware-image *ngIf="this.fileContentUrl" [imageSrc]="this.fileContentUrl"></app-content-aware-image> <div class="zoom-slider">
<mat-slider min="0.5" max="4" [value]="this.imageZoom"
(input)="this.imageZoom=$event.value ?? 1" vertical step="0.1"></mat-slider>
<button mat-icon-button (click)="this.resetImage()">
<mat-icon>refresh</mat-icon>
</button>
</div>
<div #imageDragContainer [cdkDragFreeDragPosition]="this.imagePosition" (cdkDragMoved)="this.onDragMoved($event)" *ngIf="this.fileContentUrl" cdkDrag class="image-drag-container">
<div #scaledImage class="image-scale-container" [style]="{scale: this.imageZoom}">
<app-content-aware-image [imageSrc]="this.fileContentUrl"></app-content-aware-image>
</div>
</div>
</div> </div>
</div> </div>
<mat-divider fxFlex></mat-divider> <mat-divider fxFlex></mat-divider>

@ -13,6 +13,7 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
user-select: none;
} }
app-file-gallery-entry, .file-item { app-file-gallery-entry, .file-item {
@ -28,6 +29,7 @@ app-file-gallery-entry {
.file-full-view { .file-full-view {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
} }
.file-full-view-inner { .file-full-view-inner {
@ -63,3 +65,30 @@ app-content-aware-image {
margin: auto; margin: auto;
} }
} }
.image-drag-container, .image-scale-container {
height: 100%;
width: 100%;
}
.image-scale-container {
display: block;
}
.zoom-slider {
position: absolute;
display: flex;
flex-direction: column;
right: 1em;
bottom: 1em;
z-index: 10;
opacity: 0.5;
padding: 1em 0.5em;
transition-duration: 0.2s;
}
.zoom-slider:hover {
opacity: 1;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 1em;
}

@ -11,6 +11,7 @@ import {FileService} from "../../services/file/file.service";
import {SafeResourceUrl} from "@angular/platform-browser"; import {SafeResourceUrl} from "@angular/platform-browser";
import {Selectable} from "../../models/Selectable"; import {Selectable} from "../../models/Selectable";
import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
import {CdkDrag, CdkDragMove, DragRef, Point} from "@angular/cdk/drag-drop";
@Component({ @Component({
selector: 'app-file-gallery', selector: 'app-file-gallery',
@ -27,10 +28,13 @@ export class FileGalleryComponent implements OnChanges, OnInit {
entries: Selectable<File>[] = []; entries: Selectable<File>[] = [];
@ViewChild("virtualScroll") virtualScroll!: CdkVirtualScrollViewport; @ViewChild("virtualScroll") virtualScroll!: CdkVirtualScrollViewport;
@ViewChild("scaledImage") scaledImage: ElementRef<HTMLDivElement> | undefined;
@ViewChild("imageDragContainer") imageDragContainer: ElementRef<HTMLDivElement> | undefined;
public selectedFile: Selectable<File> | undefined; public selectedFile: Selectable<File> | undefined;
fileContentUrl: SafeResourceUrl | undefined; fileContentUrl: SafeResourceUrl | undefined;
scaleWidth = false; public imageZoom = 1;
public imagePosition = {x: 0, y: 0};
constructor(private fileService: FileService) { constructor(private fileService: FileService) {
} }
@ -42,6 +46,7 @@ export class FileGalleryComponent implements OnChanges, OnInit {
*/ */
async onEntrySelect(entry: Selectable<File>) { async onEntrySelect(entry: Selectable<File>) {
if (entry) { if (entry) {
this.resetImage();
this.selectedFile?.unselect(); this.selectedFile?.unselect();
entry.select(); entry.select();
this.selectedFile = entry; this.selectedFile = entry;
@ -58,7 +63,8 @@ export class FileGalleryComponent implements OnChanges, OnInit {
async loadSelectedFile() { async loadSelectedFile() {
if (this.selectedFile) { if (this.selectedFile) {
this.fileContentUrl = undefined; this.fileContentUrl = undefined;
this.fileContentUrl = await this.fileService.readFile(this.selectedFile.data); this.fileContentUrl = await this.fileService.readFile(
this.selectedFile.data);
} }
} }
@ -69,8 +75,10 @@ export class FileGalleryComponent implements OnChanges, OnInit {
} }
public async ngOnChanges(changes: SimpleChanges): Promise<void> { public async ngOnChanges(changes: SimpleChanges): Promise<void> {
this.entries = this.files.map(f => new Selectable(f, f.hash == this.selectedFile?.data.hash)); this.entries = this.files.map(
const selectedIndex = this.files.findIndex(f => f.hash === this.selectedFile?.data.hash); f => new Selectable(f, f.hash == this.selectedFile?.data.hash));
const selectedIndex = this.files.findIndex(
f => f.hash === this.selectedFile?.data.hash);
if (!this.selectedFile || selectedIndex < 0) { if (!this.selectedFile || selectedIndex < 0) {
await this.onEntrySelect(this.getPreselectedEntry() ?? this.entries[0]) await this.onEntrySelect(this.getPreselectedEntry() ?? this.entries[0])
@ -111,6 +119,11 @@ export class FileGalleryComponent implements OnChanges, OnInit {
} }
} }
public resetImage() {
this.imageZoom = 1;
this.imagePosition = {x: 0, y: 0};
}
@HostListener("window:keydown", ["$event"]) @HostListener("window:keydown", ["$event"])
private async handleKeydownEvent(event: KeyboardEvent) { private async handleKeydownEvent(event: KeyboardEvent) {
switch (event.key) { switch (event.key) {
@ -120,12 +133,33 @@ export class FileGalleryComponent implements OnChanges, OnInit {
case "ArrowLeft": case "ArrowLeft":
await this.previousItem(); await this.previousItem();
break; break;
case "Escape":
this.resetImage();
break;
}
}
@HostListener("mousewheel", ["$event"])
private handleScroll(event: any) {
const delta = event.wheelDelta ?? event.detail
if (delta > 0) {
this.imageZoom += 0.2
if (this.imageZoom > 4) {
this.imageZoom = 4;
}
} else if (delta < 0) {
this.imageZoom -= 0.2
if (this.imageZoom < 0.5) {
this.imageZoom = 0.5;
}
} }
} }
private getPreselectedEntry(): Selectable<File> | undefined { private getPreselectedEntry(): Selectable<File> | undefined {
if (this.preselectedFile) { if (this.preselectedFile) {
const entry = this.entries.find(e => e.data.hash == this.preselectedFile?.hash); const entry = this.entries.find(
e => e.data.hash == this.preselectedFile?.hash);
if (entry) { if (entry) {
return entry; return entry;
} }
@ -133,10 +167,8 @@ export class FileGalleryComponent implements OnChanges, OnInit {
return undefined; return undefined;
} }
public adjustImageSize(fullImage: HTMLImageElement, imageContainer: HTMLDivElement): void { public onDragMoved($event: CdkDragMove<HTMLDivElement>): void {
const containerRatio = imageContainer.clientHeight / imageContainer.clientWidth; this.imagePosition.x += $event.delta.x;
const imageAdjHeight = fullImage.height / containerRatio; this.imagePosition.y += $event.delta.y;
const imageAdjWidth = fullImage.width * containerRatio;
this.scaleWidth = imageAdjWidth > imageAdjHeight;
} }
} }

@ -26,7 +26,7 @@
(fileMultiselectEvent)="onFileMultiSelect($event)" (fileMultiselectEvent)="onFileMultiSelect($event)"
></app-file-grid> ></app-file-grid>
<app-file-gallery *ngIf="this.showGallery" [files]="files" (fileSelectEvent)="onFileSelect($event)" <app-file-gallery *ngIf="this.showGallery" [files]="files" (fileSelectEvent)="onFileSelect($event)"
(fileDblClickEvent)="openFile($event)" [preselectedFile]="this.preselectedFile" [preselectedFile]="this.preselectedFile"
(closeEvent)="this.closeGallery($event.selectedFile?.data)"></app-file-gallery> (closeEvent)="this.closeGallery($event.selectedFile?.data)"></app-file-gallery>
</mat-drawer-content> </mat-drawer-content>
</mat-drawer-container> </mat-drawer-container>

@ -86,19 +86,6 @@ export class SearchPageComponent implements OnInit {
event.source.deselectAll(); event.source.deselectAll();
} }
async openFile(file: File) {
if (this.openingLightbox) {
return;
}
this.openingLightbox = true;
try {
await this.openLightbox(file);
} catch (err) {
this.errorBroker.showError(err);
}
this.openingLightbox = false;
}
async openGallery(preselectedFile: File) { async openGallery(preselectedFile: File) {
this.preselectedFile = preselectedFile; this.preselectedFile = preselectedFile;
this.showGallery = true; this.showGallery = true;
@ -108,29 +95,4 @@ export class SearchPageComponent implements OnInit {
this.preselectedFile = preselectedFile; this.preselectedFile = preselectedFile;
this.showGallery = false; this.showGallery = false;
} }
private async openLightbox(file: File): Promise<void> {
let url = await this.fileService.readFile(file);
let albums = [
{
src: url as string,
caption: file.name ?? file.comment,
thumb: url as string,
}
];
this.lightbox.open(albums, 0, {
disableScrolling: true,
showImageNumberLabel: false,
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);
}
})
}
} }

Loading…
Cancel
Save