make sure expired images dissapear in the frontend
This commit is contained in:
parent
6621a167e7
commit
941e0deb0a
|
@ -34,7 +34,7 @@ export class ImageManagerModule implements OnModuleInit, OnModuleDestroy {
|
||||||
this.interval = setInterval(
|
this.interval = setInterval(
|
||||||
// Run demoManagerService.execute() every interval
|
// Run demoManagerService.execute() every interval
|
||||||
this.imageManagerCron.bind(this),
|
this.imageManagerCron.bind(this),
|
||||||
1000 * 60 * 60,
|
1000 * 60,
|
||||||
);
|
);
|
||||||
await this.imageManagerCron();
|
await this.imageManagerCron();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<ng-container *ngIf="images !== null && images.length > 0">
|
<ng-container *ngIf="images !== null && images.length > 0">
|
||||||
|
<h1>Your Images</h1>
|
||||||
|
|
||||||
<masonry [columns]="columns">
|
<masonry [columns]="columns">
|
||||||
<div *ngFor="let image of images" class="m-2" masonry-item>
|
<div *ngFor="let image of images" class="m-2" masonry-item>
|
||||||
<mat-card>
|
<mat-card>
|
||||||
|
|
|
@ -4,6 +4,15 @@ import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||||
import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
|
import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
|
||||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types/failable';
|
import { HasFailed } from 'picsur-shared/dist/types/failable';
|
||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
merge,
|
||||||
|
Observable,
|
||||||
|
switchMap,
|
||||||
|
timer
|
||||||
|
} from 'rxjs';
|
||||||
import { ImageService } from 'src/app/services/api/image.service';
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
import { UserService } from 'src/app/services/api/user.service';
|
import { UserService } from 'src/app/services/api/user.service';
|
||||||
import { Logger } from 'src/app/services/logger/logger.service';
|
import { Logger } from 'src/app/services/logger/logger.service';
|
||||||
|
@ -18,9 +27,18 @@ import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||||
export class ImagesComponent implements OnInit {
|
export class ImagesComponent implements OnInit {
|
||||||
private readonly logger: Logger = new Logger(ImagesComponent.name);
|
private readonly logger: Logger = new Logger(ImagesComponent.name);
|
||||||
|
|
||||||
images: EImage[] | null = null;
|
imagesSub = new BehaviorSubject<EImage[] | null>(null);
|
||||||
columns = 1;
|
columns = 1;
|
||||||
|
|
||||||
|
public get images() {
|
||||||
|
const value = this.imagesSub.value;
|
||||||
|
return (
|
||||||
|
value?.filter(
|
||||||
|
(i) => i.expires_at === null || i.expires_at > new Date(),
|
||||||
|
) ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
page: number = 1;
|
page: number = 1;
|
||||||
pages: number = 1;
|
pages: number = 1;
|
||||||
|
|
||||||
|
@ -47,6 +65,7 @@ export class ImagesComponent implements OnInit {
|
||||||
|
|
||||||
this.subscribeMobile();
|
this.subscribeMobile();
|
||||||
this.subscribeUser();
|
this.subscribeUser();
|
||||||
|
this.subscribeImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoUnsubscribe()
|
@AutoUnsubscribe()
|
||||||
|
@ -64,14 +83,42 @@ export class ImagesComponent implements OnInit {
|
||||||
|
|
||||||
@AutoUnsubscribe()
|
@AutoUnsubscribe()
|
||||||
private subscribeUser() {
|
private subscribeUser() {
|
||||||
return this.userService.live.subscribe(async () => {
|
return this.userService.live.subscribe(async (user) => {
|
||||||
|
if (user === null) return;
|
||||||
|
|
||||||
const list = await this.imageService.ListMyImages(24, this.page - 1);
|
const list = await this.imageService.ListMyImages(24, this.page - 1);
|
||||||
if (HasFailed(list)) {
|
if (HasFailed(list)) {
|
||||||
return this.logger.error(list.getReason());
|
return this.logger.error(list.getReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pages = list.pages;
|
this.pages = list.pages;
|
||||||
this.images = list.results;
|
this.imagesSub.next(list.results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoUnsubscribe()
|
||||||
|
private subscribeImages() {
|
||||||
|
// Make sure we only get populated images
|
||||||
|
const filteredImagesSub: Observable<EImage[]> = this.imagesSub.pipe(
|
||||||
|
filter((images) => images !== null),
|
||||||
|
) as Observable<EImage[]>;
|
||||||
|
|
||||||
|
const mappedImagesSub: Observable<EImage> = filteredImagesSub.pipe(
|
||||||
|
// Everytime we get a new array, we want merge a mapping of that array
|
||||||
|
// In this mapping, each image will emit itself on the expire date
|
||||||
|
switchMap((images: EImage[]) =>
|
||||||
|
merge(
|
||||||
|
...images
|
||||||
|
.filter((i) => i.expires_at !== null)
|
||||||
|
.map((i) => timer(i.expires_at!).pipe(map(() => i))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) as Observable<EImage>;
|
||||||
|
|
||||||
|
return mappedImagesSub.subscribe((image) => {
|
||||||
|
this.imagesSub.next(
|
||||||
|
this.images?.filter((i) => i.id !== image.id) ?? null,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +156,9 @@ export class ImagesComponent implements OnInit {
|
||||||
return this.errorService.showFailure(result, this.logger);
|
return this.errorService.showFailure(result, this.logger);
|
||||||
|
|
||||||
this.errorService.success('Image deleted');
|
this.errorService.success('Image deleted');
|
||||||
this.images = this.images?.filter((i) => i.id !== image.id) ?? null;
|
this.imagesSub.next(
|
||||||
|
this.images?.filter((i) => i.id !== image.id) ?? null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<speed-dial
|
||||||
|
icon="menu"
|
||||||
|
icon-hover="download"
|
||||||
|
tooltip="Download the image"
|
||||||
|
[open-on-hover]="true"
|
||||||
|
(main-click)="download()"
|
||||||
|
>
|
||||||
|
<button mat-mini-fab matTooltip="Share image" (click)="share()">
|
||||||
|
<mat-icon fontSet="material-icons-outlined"> share </mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-mini-fab matTooltip="Customize Image" (click)="customize()">
|
||||||
|
<mat-icon fontSet="material-icons-outlined"> tune </mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="canManage"
|
||||||
|
mat-mini-fab
|
||||||
|
matTooltip="Edit image"
|
||||||
|
(click)="editImage()"
|
||||||
|
>
|
||||||
|
<mat-icon fontSet="material-icons-outlined"> edit </mat-icon>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
*ngIf="canManage"
|
||||||
|
mat-mini-fab
|
||||||
|
matTooltip="Delete image"
|
||||||
|
(click)="deleteImage()"
|
||||||
|
>
|
||||||
|
<mat-icon fontSet="material-icons-outlined"> delete </mat-icon>
|
||||||
|
</button>
|
||||||
|
</speed-dial>
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||||
|
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
|
import { ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
|
||||||
|
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
||||||
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
|
import { PermissionService } from 'src/app/services/api/permission.service';
|
||||||
|
import { UserService } from 'src/app/services/api/user.service';
|
||||||
|
import { Logger } from 'src/app/services/logger/logger.service';
|
||||||
|
import { DialogService } from 'src/app/util/dialog-manager/dialog.service';
|
||||||
|
import { DownloadService } from 'src/app/util/download-manager/download.service';
|
||||||
|
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||||
|
import { UtilService } from 'src/app/util/util.service';
|
||||||
|
import {
|
||||||
|
CustomizeDialogComponent,
|
||||||
|
CustomizeDialogData
|
||||||
|
} from '../customize-dialog/customize-dialog.component';
|
||||||
|
import {
|
||||||
|
EditDialogComponent,
|
||||||
|
EditDialogData
|
||||||
|
} from '../edit-dialog/edit-dialog.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'view-speeddial',
|
||||||
|
templateUrl: './view-speeddial.component.html',
|
||||||
|
styleUrls: ['./view-speeddial.component.scss'],
|
||||||
|
})
|
||||||
|
export class ViewSpeeddialComponent implements OnInit {
|
||||||
|
private readonly logger = new Logger(ViewSpeeddialComponent.name);
|
||||||
|
|
||||||
|
public canManage: boolean = false;
|
||||||
|
|
||||||
|
@Input() public metadata: ImageMetaResponse | null = null;
|
||||||
|
@Output() public metadataChange = new EventEmitter<ImageMetaResponse>();
|
||||||
|
|
||||||
|
@Input() public selectedFormat: string = ImageFileType.JPEG;
|
||||||
|
|
||||||
|
public get image(): EImage | null {
|
||||||
|
return this.metadata?.image ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get user(): EUser | null {
|
||||||
|
return this.metadata?.user ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly permissionService: PermissionService,
|
||||||
|
private readonly downloadService: DownloadService,
|
||||||
|
private readonly errorService: ErrorService,
|
||||||
|
private readonly dialogService: DialogService,
|
||||||
|
private readonly imageService: ImageService,
|
||||||
|
private readonly utilService: UtilService,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
private readonly router: Router,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.subscribePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoUnsubscribe()
|
||||||
|
private subscribePermissions() {
|
||||||
|
this.updatePermissions(this.permissionService.snapshot);
|
||||||
|
return this.permissionService.live.subscribe(
|
||||||
|
this.updatePermissions.bind(this),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updatePermissions(permissions: string[]) {
|
||||||
|
if (permissions.includes(Permission.ImageAdmin)) {
|
||||||
|
this.canManage = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user === null) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
permissions.includes(Permission.ImageManage) &&
|
||||||
|
this.user.id === this.userService.snapshot?.id
|
||||||
|
) {
|
||||||
|
this.canManage = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canManage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
this.downloadService.downloadFile(
|
||||||
|
this.imageService.CreateImageLinksFromID(
|
||||||
|
this.image?.id,
|
||||||
|
this.selectedFormat,
|
||||||
|
).source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
this.downloadService.shareFile(
|
||||||
|
this.imageService.CreateImageLinksFromID(
|
||||||
|
this.image?.id,
|
||||||
|
this.selectedFormat,
|
||||||
|
).source,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteImage() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
const pressedButton = await this.dialogService.showDialog({
|
||||||
|
title: `Are you sure you want to delete the image?`,
|
||||||
|
description: 'This action cannot be undone.',
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
name: 'cancel',
|
||||||
|
text: 'Cancel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: 'warn',
|
||||||
|
name: 'delete',
|
||||||
|
text: 'Delete',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pressedButton === 'delete') {
|
||||||
|
const result = await this.imageService.DeleteImage(this.image.id);
|
||||||
|
if (HasFailed(result))
|
||||||
|
return this.errorService.showFailure(result, this.logger);
|
||||||
|
|
||||||
|
this.errorService.success('Image deleted');
|
||||||
|
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async customize() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
const options: CustomizeDialogData = {
|
||||||
|
imageID: this.image.id,
|
||||||
|
selectedFormat: this.selectedFormat,
|
||||||
|
formatOptions: this.utilService.getBaseFormatOptions(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.selectedFormat === 'original') {
|
||||||
|
options.selectedFormat = ImageFileType.JPEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.dialogService.showCustomDialog(
|
||||||
|
CustomizeDialogComponent,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async editImage() {
|
||||||
|
if (this.image === null) return;
|
||||||
|
|
||||||
|
const options: EditDialogData = {
|
||||||
|
image: { ...this.image },
|
||||||
|
};
|
||||||
|
|
||||||
|
const res: EImage | null = await this.dialogService.showCustomDialog(
|
||||||
|
EditDialogComponent,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res && this.metadata !== null) {
|
||||||
|
this.metadataChange.emit({ ...this.metadata, image: res });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,14 +2,13 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h1>
|
<h1>
|
||||||
{{ image?.file_name ?? 'image' | truncate }} uploaded by
|
{{ image?.file_name ?? 'image' | truncate }}
|
||||||
{{ imageUser?.username }}
|
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12" *ngIf="image !== null">
|
<div class="col-12" *ngIf="image !== null">
|
||||||
<h3>
|
<h3>
|
||||||
Uploaded {{ image.created | amTimeAgo }}
|
Uploaded {{ image.created | amTimeAgo }} by {{ user?.username }}
|
||||||
{{
|
{{
|
||||||
image.expires_at === null
|
image.expires_at === null
|
||||||
? ''
|
? ''
|
||||||
|
@ -29,10 +28,7 @@
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<mat-form-field appearance="outline" color="accent">
|
<mat-form-field appearance="outline" color="accent">
|
||||||
<mat-label>Image Format</mat-label>
|
<mat-label>Image Format</mat-label>
|
||||||
<mat-select
|
<mat-select [(value)]="selectedFormat">
|
||||||
(valueChange)="selectedFormat($event)"
|
|
||||||
[value]="setSelectedFormat"
|
|
||||||
>
|
|
||||||
<mat-option *ngFor="let format of formatOptions" [value]="format.key">
|
<mat-option *ngFor="let format of formatOptions" [value]="format.key">
|
||||||
{{ format.value }}
|
{{ format.value }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@ -59,33 +55,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<speed-dial
|
<view-speeddial
|
||||||
icon="menu"
|
[selectedFormat]="selectedFormat"
|
||||||
icon-hover="download"
|
[metadata]="metadata"
|
||||||
tooltip="Download the image"
|
(metadataChange)="OnMetadata = $event"
|
||||||
[open-on-hover]="true"
|
></view-speeddial>
|
||||||
(main-click)="download()"
|
|
||||||
>
|
|
||||||
<button mat-mini-fab matTooltip="Share image" (click)="share()">
|
|
||||||
<mat-icon fontSet="material-icons-outlined"> share </mat-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-mini-fab matTooltip="Customize Image" (click)="customize()">
|
|
||||||
<mat-icon fontSet="material-icons-outlined"> tune </mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="canManage"
|
|
||||||
mat-mini-fab
|
|
||||||
matTooltip="Edit image"
|
|
||||||
(click)="editImage()"
|
|
||||||
>
|
|
||||||
<mat-icon fontSet="material-icons-outlined"> edit </mat-icon>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
*ngIf="canManage"
|
|
||||||
mat-mini-fab
|
|
||||||
matTooltip="Delete image"
|
|
||||||
(click)="deleteImage()"
|
|
||||||
>
|
|
||||||
<mat-icon fontSet="material-icons-outlined"> delete </mat-icon>
|
|
||||||
</button>
|
|
||||||
</speed-dial>
|
|
||||||
|
|
|
@ -1,249 +1,166 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit
|
||||||
|
} from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
import { ImageMetaResponse } from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
|
import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
|
||||||
import {
|
import {
|
||||||
AnimFileType,
|
AnimFileType,
|
||||||
FileType,
|
|
||||||
ImageFileType,
|
ImageFileType,
|
||||||
SupportedFileTypeCategory
|
SupportedFileTypeCategory
|
||||||
} from 'picsur-shared/dist/dto/mimes.dto';
|
} from 'picsur-shared/dist/dto/mimes.dto';
|
||||||
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
|
|
||||||
|
|
||||||
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
import { EImage } from 'picsur-shared/dist/entities/image.entity';
|
||||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
import { HasFailed, HasSuccess } from 'picsur-shared/dist/types';
|
|
||||||
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
|
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
|
||||||
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
|
import { ParseFileType } from 'picsur-shared/dist/util/parse-mime';
|
||||||
|
import { Subscription, timer } from 'rxjs';
|
||||||
import { ImageService } from 'src/app/services/api/image.service';
|
import { ImageService } from 'src/app/services/api/image.service';
|
||||||
import { PermissionService } from 'src/app/services/api/permission.service';
|
|
||||||
import { UserService } from 'src/app/services/api/user.service';
|
|
||||||
import { Logger } from 'src/app/services/logger/logger.service';
|
import { Logger } from 'src/app/services/logger/logger.service';
|
||||||
import { DialogService } from 'src/app/util/dialog-manager/dialog.service';
|
|
||||||
import { DownloadService } from 'src/app/util/download-manager/download.service';
|
|
||||||
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
import { ErrorService } from 'src/app/util/error-manager/error.service';
|
||||||
import { UtilService } from 'src/app/util/util.service';
|
import { UtilService } from 'src/app/util/util.service';
|
||||||
|
|
||||||
import {
|
|
||||||
CustomizeDialogComponent,
|
|
||||||
CustomizeDialogData
|
|
||||||
} from './customize-dialog/customize-dialog.component';
|
|
||||||
import {
|
|
||||||
EditDialogComponent,
|
|
||||||
EditDialogData
|
|
||||||
} from './edit-dialog/edit-dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './view.component.html',
|
templateUrl: './view.component.html',
|
||||||
styleUrls: ['./view.component.scss'],
|
styleUrls: ['./view.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ViewComponent implements OnInit {
|
export class ViewComponent implements OnInit, OnDestroy {
|
||||||
private readonly logger = new Logger(ViewComponent.name);
|
private readonly logger = new Logger(ViewComponent.name);
|
||||||
|
|
||||||
|
private expires_timeout: Subscription | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly imageService: ImageService,
|
private readonly imageService: ImageService,
|
||||||
private readonly permissionService: PermissionService,
|
|
||||||
private readonly userService: UserService,
|
|
||||||
|
|
||||||
private readonly errorService: ErrorService,
|
private readonly errorService: ErrorService,
|
||||||
private readonly downloadService: DownloadService,
|
|
||||||
private readonly dialogService: DialogService,
|
|
||||||
private readonly utilService: UtilService,
|
private readonly utilService: UtilService,
|
||||||
|
private readonly changeDetector: ChangeDetectorRef,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private id: string;
|
private id: string = '';
|
||||||
private hasOriginal: boolean = false;
|
public metadata: ImageMetaResponse | null = null;
|
||||||
private masterFileType: FileType = {
|
public set OnMetadata(metadata: ImageMetaResponse) {
|
||||||
identifier: ImageFileType.JPEG,
|
this.metadata = metadata;
|
||||||
category: SupportedFileTypeCategory.Image,
|
this.subscribeTimeout(metadata.image.expires_at);
|
||||||
};
|
|
||||||
private currentSelectedFormat: string = ImageFileType.JPEG;
|
this.changeDetector.markForCheck();
|
||||||
|
}
|
||||||
|
|
||||||
public formatOptions: {
|
public formatOptions: {
|
||||||
value: string;
|
value: string;
|
||||||
key: string;
|
key: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
public setSelectedFormat: string = ImageFileType.JPEG;
|
public selectedFormat: string = ImageFileType.JPEG;
|
||||||
|
|
||||||
public previewLink = '';
|
public get image(): EImage | null {
|
||||||
public imageLinks = new ImageLinks();
|
return this.metadata?.image ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
public image: EImage | null = null;
|
public get user(): EUser | null {
|
||||||
public imageUser: EUser | null = null;
|
return this.metadata?.user ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
public canManage: boolean = false;
|
public get hasOriginal(): boolean {
|
||||||
|
if (this.metadata === null) return false;
|
||||||
|
return this.metadata.fileTypes.original !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
public get previewLink(): string {
|
||||||
this.subscribePermissions();
|
if (this.metadata === null) return '';
|
||||||
|
|
||||||
// Extract and verify params
|
|
||||||
const params = this.route.snapshot.paramMap;
|
|
||||||
|
|
||||||
this.id = params.get('id') ?? '';
|
|
||||||
if (!UUIDRegex.test(this.id)) {
|
|
||||||
return this.errorService.quitError('Invalid image link', this.logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get metadata
|
|
||||||
const metadata = await this.imageService.GetImageMeta(this.id);
|
|
||||||
if (HasFailed(metadata))
|
|
||||||
return this.errorService.quitFailure(metadata, this.logger);
|
|
||||||
|
|
||||||
// Get width of screen in pixels
|
// Get width of screen in pixels
|
||||||
const width = window.innerWidth * window.devicePixelRatio;
|
const width = window.innerWidth * window.devicePixelRatio;
|
||||||
|
|
||||||
// Populate fields with metadata
|
return (
|
||||||
this.previewLink =
|
this.imageService.GetImageURL(this.id, this.metadata.fileTypes.master) +
|
||||||
this.imageService.GetImageURL(this.id, metadata.fileTypes.master) +
|
(width > 1 ? `?width=${width}&shrinkonly=yes` : '')
|
||||||
(width > 1 ? `?width=${width}&shrinkonly=yes` : '');
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.hasOriginal = metadata.fileTypes.original !== undefined;
|
public get imageLinks(): ImageLinks {
|
||||||
|
const format = this.selectedFormat;
|
||||||
|
return this.imageService.CreateImageLinksFromID(
|
||||||
|
this.id,
|
||||||
|
format === 'original' ? null : format,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.imageUser = metadata.user;
|
async ngOnInit() {
|
||||||
this.image = metadata.image;
|
// Extract and verify params
|
||||||
|
{
|
||||||
|
const params = this.route.snapshot.paramMap;
|
||||||
|
|
||||||
|
this.id = params.get('id') ?? '';
|
||||||
|
if (!UUIDRegex.test(this.id)) {
|
||||||
|
return this.errorService.quitError('Invalid image link', this.logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get metadata
|
||||||
|
{
|
||||||
|
const metadata = await this.imageService.GetImageMeta(this.id);
|
||||||
|
if (HasFailed(metadata))
|
||||||
|
return this.errorService.quitFailure(metadata, this.logger);
|
||||||
|
|
||||||
|
if (metadata.image.expires_at !== null) {
|
||||||
|
if (metadata.image.expires_at <= new Date())
|
||||||
|
return this.errorService.quitWarn('Image not found', this.logger);
|
||||||
|
|
||||||
|
this.subscribeTimeout(metadata.image.expires_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
// Populate default selected format
|
// Populate default selected format
|
||||||
const masterFiletype = ParseFileType(metadata.fileTypes.master);
|
{
|
||||||
if (HasSuccess(masterFiletype)) {
|
let masterFiletype = ParseFileType(this.metadata.fileTypes.master);
|
||||||
this.masterFileType = masterFiletype;
|
if (HasFailed(masterFiletype)) {
|
||||||
|
masterFiletype = {
|
||||||
|
identifier: ImageFileType.JPEG,
|
||||||
|
category: SupportedFileTypeCategory.Image,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (masterFiletype.category) {
|
||||||
|
case SupportedFileTypeCategory.Image:
|
||||||
|
this.selectedFormat = ImageFileType.JPEG;
|
||||||
|
break;
|
||||||
|
case SupportedFileTypeCategory.Animation:
|
||||||
|
this.selectedFormat = AnimFileType.GIF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.selectedFormat = this.metadata.fileTypes.master;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.masterFileType.category === SupportedFileTypeCategory.Image) {
|
|
||||||
this.setSelectedFormat = ImageFileType.JPEG;
|
|
||||||
} else if (
|
|
||||||
this.masterFileType.category === SupportedFileTypeCategory.Animation
|
|
||||||
) {
|
|
||||||
this.setSelectedFormat = AnimFileType.GIF;
|
|
||||||
} else {
|
|
||||||
this.setSelectedFormat = metadata.fileTypes.master;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedFormat(this.setSelectedFormat);
|
|
||||||
this.updateFormatOptions();
|
this.updateFormatOptions();
|
||||||
this.updatePermissions();
|
this.changeDetector.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedFormat(format: string) {
|
ngOnDestroy() {
|
||||||
this.currentSelectedFormat = format;
|
if (this.expires_timeout !== null) this.expires_timeout.unsubscribe();
|
||||||
if (format === 'original') {
|
|
||||||
this.imageLinks = this.imageService.CreateImageLinksFromID(this.id, null);
|
|
||||||
} else {
|
|
||||||
this.imageLinks = this.imageService.CreateImageLinksFromID(
|
|
||||||
this.id,
|
|
||||||
format,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
download() {
|
|
||||||
this.downloadService.downloadFile(this.imageLinks.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
share() {
|
|
||||||
this.downloadService.shareFile(this.imageLinks.source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goBackHome() {
|
goBackHome() {
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteImage() {
|
|
||||||
const pressedButton = await this.dialogService.showDialog({
|
|
||||||
title: `Are you sure you want to delete the image?`,
|
|
||||||
description: 'This action cannot be undone.',
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
name: 'cancel',
|
|
||||||
text: 'Cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
color: 'warn',
|
|
||||||
name: 'delete',
|
|
||||||
text: 'Delete',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pressedButton === 'delete') {
|
|
||||||
const result = await this.imageService.DeleteImage(this.id);
|
|
||||||
if (HasFailed(result))
|
|
||||||
return this.errorService.showFailure(result, this.logger);
|
|
||||||
|
|
||||||
this.errorService.success('Image deleted');
|
|
||||||
|
|
||||||
this.router.navigate(['/']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async customize() {
|
|
||||||
const options: CustomizeDialogData = {
|
|
||||||
imageID: this.id,
|
|
||||||
selectedFormat: this.currentSelectedFormat,
|
|
||||||
formatOptions: this.utilService.getBaseFormatOptions(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.selectedFormat === 'original') {
|
|
||||||
options.selectedFormat = this.masterFileType.identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.dialogService.showCustomDialog(
|
|
||||||
CustomizeDialogComponent,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async editImage() {
|
|
||||||
if (this.image === null) return;
|
|
||||||
|
|
||||||
const options: EditDialogData = {
|
|
||||||
image: { ...this.image },
|
|
||||||
};
|
|
||||||
|
|
||||||
const res: EImage | null = await this.dialogService.showCustomDialog(
|
|
||||||
EditDialogComponent,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
this.image = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoUnsubscribe()
|
|
||||||
private subscribePermissions() {
|
|
||||||
return this.permissionService.live.subscribe(
|
|
||||||
this.updatePermissions.bind(this),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updatePermissions() {
|
|
||||||
const permissions = this.permissionService.snapshot;
|
|
||||||
if (permissions.includes(Permission.ImageAdmin)) {
|
|
||||||
this.canManage = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.imageUser === null) return;
|
|
||||||
|
|
||||||
if (
|
|
||||||
permissions.includes(Permission.ImageManage) &&
|
|
||||||
this.imageUser.id === this.userService.snapshot?.id
|
|
||||||
) {
|
|
||||||
this.canManage = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.canManage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateFormatOptions() {
|
private updateFormatOptions() {
|
||||||
let newOptions: {
|
let newOptions: {
|
||||||
value: string;
|
value: string;
|
||||||
key: string;
|
key: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
if (this.hasOriginal) {
|
if (this.hasOriginal) {
|
||||||
newOptions.push({
|
newOptions.push({
|
||||||
value: 'Original',
|
value: 'Original',
|
||||||
|
@ -255,4 +172,14 @@ export class ViewComponent implements OnInit {
|
||||||
|
|
||||||
this.formatOptions = newOptions;
|
this.formatOptions = newOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private subscribeTimeout(expires_at: Date | null) {
|
||||||
|
if (this.expires_timeout !== null) this.expires_timeout.unsubscribe();
|
||||||
|
|
||||||
|
if (expires_at === null) return;
|
||||||
|
|
||||||
|
this.expires_timeout = timer(expires_at).subscribe(() => {
|
||||||
|
this.errorService.quitWarn('Image expired', this.logger);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,17 @@ import { DownloadManagerModule } from 'src/app/util/download-manager/dialog-mana
|
||||||
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
|
import { ErrorManagerModule } from 'src/app/util/error-manager/error-manager.module';
|
||||||
import { CustomizeDialogComponent } from './customize-dialog/customize-dialog.component';
|
import { CustomizeDialogComponent } from './customize-dialog/customize-dialog.component';
|
||||||
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
|
import { EditDialogComponent } from './edit-dialog/edit-dialog.component';
|
||||||
|
import { ViewSpeeddialComponent } from './view-speeddial/view-speeddial.component';
|
||||||
import { ViewComponent } from './view.component';
|
import { ViewComponent } from './view.component';
|
||||||
import { ViewRoutingModule } from './view.routing.module';
|
import { ViewRoutingModule } from './view.routing.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ViewComponent, CustomizeDialogComponent, EditDialogComponent],
|
declarations: [
|
||||||
|
ViewComponent,
|
||||||
|
ViewSpeeddialComponent,
|
||||||
|
CustomizeDialogComponent,
|
||||||
|
EditDialogComponent,
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ErrorManagerModule,
|
ErrorManagerModule,
|
||||||
|
|
Loading…
Reference in a new issue