From 1b1451d910f766ff87f6cb43cb4cbc79ee1e3b0c Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 16 Oct 2021 17:07:30 +0200 Subject: [PATCH] Add lightboxes and fix memory leak problems Signed-off-by: trivernis --- mediarepo-ui/angular.json | 1 + mediarepo-ui/package.json | 2 ++ mediarepo-ui/src/app/app.module.ts | 2 ++ .../file-grid-entry.component.html | 2 +- .../file-grid-entry.component.ts | 24 +++++++++++++--- .../file-grid/file-grid.component.html | 3 +- .../file-grid/file-grid.component.ts | 4 ++- .../src/app/pages/home/home.component.html | 2 +- .../src/app/pages/home/home.component.ts | 28 ++++++++++++++++++- .../repository-card.component.ts | 1 - .../src/app/services/file/file.service.ts | 12 ++++---- mediarepo-ui/yarn.lock | 25 +++++++++++++++++ 12 files changed, 91 insertions(+), 15 deletions(-) diff --git a/mediarepo-ui/angular.json b/mediarepo-ui/angular.json index 7f49e8e..05c406c 100644 --- a/mediarepo-ui/angular.json +++ b/mediarepo-ui/angular.json @@ -35,6 +35,7 @@ "src/assets" ], "styles": [ + "./node_modules/ngx-lightbox/lightbox.css", "src/styles.scss" ], "scripts": [] diff --git a/mediarepo-ui/package.json b/mediarepo-ui/package.json index 66073b1..0b68184 100644 --- a/mediarepo-ui/package.json +++ b/mediarepo-ui/package.json @@ -23,6 +23,7 @@ "@angular/platform-browser-dynamic": "~12.2.0", "@angular/router": "~12.2.0", "@tauri-apps/api": "^1.0.0-beta.8", + "ngx-lightbox": "^2.5.1", "primeicons": "^4.1.0", "primeng": "^12.2.1", "rxjs": "~6.6.0", @@ -39,6 +40,7 @@ "@angular/cli": "~12.2.9", "@angular/compiler-cli": "~12.2.0", "@tauri-apps/cli": "^1.0.0-beta.10", + "@types/file-saver": "^2.0.3", "@types/jasmine": "~3.8.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "4.28.2", diff --git a/mediarepo-ui/src/app/app.module.ts b/mediarepo-ui/src/app/app.module.ts index 72dc4f3..eb488da 100644 --- a/mediarepo-ui/src/app/app.module.ts +++ b/mediarepo-ui/src/app/app.module.ts @@ -23,6 +23,7 @@ import { FileGridEntryComponent } from './components/file-grid/file-grid-entry/f import {MatProgressBarModule} from "@angular/material/progress-bar"; import {MatPaginatorModule} from "@angular/material/paginator"; import {ScrollingModule} from "@angular/cdk/scrolling"; +import {LightboxModule} from "ngx-lightbox"; @NgModule({ declarations: [ @@ -51,6 +52,7 @@ import {ScrollingModule} from "@angular/cdk/scrolling"; MatProgressBarModule, MatPaginatorModule, ScrollingModule, + LightboxModule ], providers: [], bootstrap: [AppComponent] 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 0f9e73a..6a9f2d3 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.ts b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts index 33efb6e..6cec473 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 @@ -1,26 +1,29 @@ import { - AfterContentChecked, Component, Input, OnInit, ViewChild, - ElementRef, AfterViewInit + ElementRef, Output, EventEmitter, OnDestroy } from '@angular/core'; import {File} from "../../../models/File"; import {FileService} from "../../../services/file/file.service"; import {ErrorBrokerService} from "../../../services/error-broker/error-broker.service"; import {SafeResourceUrl} from "@angular/platform-browser"; import {MatCard} from "@angular/material/card"; +import {Thumbnail} from "../../../models/Thumbnail"; @Component({ selector: 'app-file-grid-entry', templateUrl: './file-grid-entry.component.html', styleUrls: ['./file-grid-entry.component.scss'] }) -export class FileGridEntryComponent implements OnInit { +export class FileGridEntryComponent implements OnInit, OnDestroy { @ViewChild("card") card!: ElementRef; @Input() file!: File; + @Output() clickEvent = new EventEmitter(); + @Output() dblClickEvent = new EventEmitter(); + selectedThumbnail: Thumbnail | undefined; contentUrl: SafeResourceUrl | undefined; constructor(private fileService: FileService, private errorBroker: ErrorBrokerService) { } @@ -29,11 +32,24 @@ export class FileGridEntryComponent implements OnInit { await this.loadImage(); } + public ngOnDestroy(): void { + if (this.contentUrl) { + const url = this.contentUrl; + this.contentUrl = undefined; + URL?.revokeObjectURL(url as string); + } + } + async loadImage() { try { const thumbnails = await this.fileService.getThumbnails(this.file.hash); let thumbnail = thumbnails.find(t => (t.height > 250 || t.width > 250) && (t.height < 500 && t.width < 500)); - this.contentUrl = await this.fileService.readThumbnail(thumbnail!!); + this.selectedThumbnail = thumbnail; + if (!thumbnail) { + console.log("Thumbnail is empty?!", thumbnails); + } else { + this.contentUrl = await this.fileService.readThumbnail(thumbnail!!); + } } catch (err) { this.errorBroker.showError(err); } 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 5bca9ec..f1d4237 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,8 @@
- +
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 c6a47aa..d113a1e 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,4 +1,4 @@ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {File} from "../../models/File"; import {FileService} from "../../services/file/file.service"; @@ -10,6 +10,8 @@ import {FileService} from "../../services/file/file.service"; export class FileGridComponent { @Input() fileRows: File[][] = []; + @Output() fileDblClickEvent = new EventEmitter(); + @Output() fileClickEvent = new EventEmitter(); constructor() { } } diff --git a/mediarepo-ui/src/app/pages/home/home.component.html b/mediarepo-ui/src/app/pages/home/home.component.html index f3df0d8..d1cd08a 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.html +++ b/mediarepo-ui/src/app/pages/home/home.component.html @@ -7,7 +7,7 @@

Drawer

- + diff --git a/mediarepo-ui/src/app/pages/home/home.component.ts b/mediarepo-ui/src/app/pages/home/home.component.ts index d7d030a..5a0783e 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.ts +++ b/mediarepo-ui/src/app/pages/home/home.component.ts @@ -2,6 +2,8 @@ import { Component, OnInit } from '@angular/core'; import {FileService} from "../../services/file/file.service"; 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"; @Component({ selector: 'app-home', @@ -14,7 +16,7 @@ export class HomeComponent implements OnInit { page: number = 0; pageSize: number = 25; - constructor(private fileService: FileService) { } + constructor(private fileService: FileService, private lightbox: Lightbox, private lightboxEvent: LightboxEvent) { } async ngOnInit() { this.fileService.displayedFiles.subscribe((files) => this.setFileRows(files)); @@ -29,4 +31,28 @@ export class HomeComponent implements OnInit { } console.log(this.fileRows); } + + async openFile(file: File) { + let url = await this.fileService.readFile(file.hash, file.mime_type ?? "image/png"); + + 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); + } + }) + } } diff --git a/mediarepo-ui/src/app/pages/repositories/repository-card/repository-card.component.ts b/mediarepo-ui/src/app/pages/repositories/repository-card/repository-card.component.ts index a98006b..e491129 100644 --- a/mediarepo-ui/src/app/pages/repositories/repository-card/repository-card.component.ts +++ b/mediarepo-ui/src/app/pages/repositories/repository-card/repository-card.component.ts @@ -2,7 +2,6 @@ import {Component, Input, OnInit} from '@angular/core'; import {Repository} from "../../../models/Repository"; import {RepositoryService} from "../../../services/repository/repository.service"; import {Router} from "@angular/router"; -import {MatSnackBar} from "@angular/material/snack-bar"; import {ErrorBrokerService} from "../../../services/error-broker/error-broker.service"; @Component({ diff --git a/mediarepo-ui/src/app/services/file/file.service.ts b/mediarepo-ui/src/app/services/file/file.service.ts index 3bb2e64..4c85cbf 100644 --- a/mediarepo-ui/src/app/services/file/file.service.ts +++ b/mediarepo-ui/src/app/services/file/file.service.ts @@ -2,7 +2,6 @@ import {Inject, Injectable, Sanitizer} from '@angular/core'; import {BehaviorSubject} from "rxjs"; import {File} from "../../models/File"; import {invoke} from "@tauri-apps/api/tauri"; -import {DOCUMENT} from "@angular/common"; import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; import {Thumbnail} from "../../models/Thumbnail"; @@ -26,19 +25,22 @@ export class FileService { const data = await invoke("read_file_by_hash", {hash}); const blob = new Blob([new Uint8Array(data)], {type: mime_type}); - const url = URL?.createObjectURL(blob); - return this.sanitizer.bypassSecurityTrustResourceUrl(url); + return this.createSafeObjectUrl(blob); } public async readThumbnail(thumbnail: Thumbnail): Promise { let data = await invoke("read_thumbnail", {hash: thumbnail.hash}); const blob = new Blob([new Uint8Array(data)], {type: thumbnail.mime}); - const url = URL?.createObjectURL(blob); - return this.sanitizer.bypassSecurityTrustResourceUrl(url); + return this.createSafeObjectUrl(blob); } public async getThumbnails(hash: string): Promise { return await invoke("get_thumbnails", {hash}); } + + createSafeObjectUrl(blob: Blob): SafeResourceUrl { + const url = URL?.createObjectURL(blob); + return this.sanitizer.bypassSecurityTrustResourceUrl(url); + } } diff --git a/mediarepo-ui/yarn.lock b/mediarepo-ui/yarn.lock index 468774b..1621deb 100644 --- a/mediarepo-ui/yarn.lock +++ b/mediarepo-ui/yarn.lock @@ -1572,6 +1572,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/file-saver@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.3.tgz#b734c4f5a04d20615eaed3dc106e2ab321082009" + integrity sha512-MBIou8pd/41jkff7s97B47bc9+p0BszqqDJsO51yDm49uUxeKzrfuNl5fSLC6BpLEWKA8zlwyqALVmXrFwoBHQ== + "@types/glob@^7.1.1": version "7.1.4" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" @@ -4469,6 +4474,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-type@5.2.0, file-type@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" @@ -6882,6 +6892,21 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +ngx-filesaver@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/ngx-filesaver/-/ngx-filesaver-8.1.0.tgz#f82a567f70c4ee81fc67b25bf594f3959c371b19" + integrity sha512-xAwThsB/ap9qs7BRgW+mJPWBK60uaqkfPnw3vzbykTdfIvGf62U/6osDCq4TCPl/POWLFui6b3Bu+gfDMqoZhw== + dependencies: + tslib "^1.9.0" + +ngx-lightbox@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/ngx-lightbox/-/ngx-lightbox-2.5.1.tgz#1d16b7445296b541c428c71c71b9f763ddc1cef8" + integrity sha512-/EOKxiT8s8pS7CIwAJDrUWt4DsNGdj7fj5ygzZZ4Jdl0iKcfwhGtnpGfrmGZlCeoFDYg5+u1PalFbvYJs0naSQ== + dependencies: + file-saver "^2.0.5" + ngx-filesaver "8.1.0" + nice-napi@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b"