Propagate changes to file status

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/3/head
trivernis 3 years ago
parent 6073a6517f
commit d3700932db
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -68,7 +68,7 @@
"optimization": { "optimization": {
"fonts": false, "fonts": false,
"styles": false, "styles": false,
"scripts": true "scripts": false
}, },
"vendorChunk": true, "vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,

@ -2,7 +2,10 @@
[ngClass]="{'selected': entry.selected}"> [ngClass]="{'selected': entry.selected}">
<mat-card-content> <mat-card-content>
<app-busy-indicator [busy]="this.loading"> <app-busy-indicator [busy]="this.loading">
<app-file-thumbnail *ngIf="!loading" [file]="this.entry.data" class=".entry-image"></app-file-thumbnail> <app-file-thumbnail *ngIf="!loading"
[fileChanged]="this.fileChanged"
[file]="this.entry.data"
class=".entry-image"></app-file-thumbnail>
</app-busy-indicator> </app-busy-indicator>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

@ -16,6 +16,7 @@ import {
import {File} from "../../../../../api/models/File"; import {File} from "../../../../../api/models/File";
import {Selectable} from "../../../../models/Selectable"; import {Selectable} from "../../../../models/Selectable";
import {SchedulingService} from "../../../../services/scheduling/scheduling.service"; import {SchedulingService} from "../../../../services/scheduling/scheduling.service";
import {BehaviorSubject} from "rxjs";
const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING"; const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING";
@ -28,6 +29,7 @@ const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING";
export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked { export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterViewChecked {
@ViewChild("card") card!: ElementRef; @ViewChild("card") card!: ElementRef;
@Input() public entry!: Selectable<File>; @Input() public entry!: Selectable<File>;
@Input() public fileChanged: BehaviorSubject<void> = new BehaviorSubject<void>(undefined);
@Output() clickEvent = new EventEmitter<FileCardComponent>(); @Output() clickEvent = new EventEmitter<FileCardComponent>();
@Output() dblClickEvent = new EventEmitter<FileCardComponent>(); @Output() dblClickEvent = new EventEmitter<FileCardComponent>();
public loading = false; public loading = false;
@ -49,6 +51,9 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy, AfterVie
this.cachedId = this.entry.data.id; this.cachedId = this.entry.data.id;
this.setImageDelayed(); this.setImageDelayed();
} }
if (changes["fileChanged"]) {
this.fileChanged.subscribe(() => this.changeDetector.markForCheck());
}
} }
public ngAfterViewChecked(): void { public ngAfterViewChecked(): void {

@ -1,12 +1,12 @@
<app-context-menu #contextMenu> <app-context-menu #contextMenu>
<ng-content select="[content-before]"></ng-content> <ng-content select="[content-before]"></ng-content>
<ng-container *ngIf="this.files"> <ng-container *ngIf="this.files">
<button (click)="this.updateStatus(this.files, 'Archived')" *ngIf="actionArchive" mat-menu-item>Archive <button (click)="this.changeFileStatus('Archived')" *ngIf="actionArchive" mat-menu-item>Archive
</button> </button>
<button (click)="this.updateStatus(this.files, 'Imported')" *ngIf="actionImported" mat-menu-item>Back to <button (click)="this.changeFileStatus('Imported')" *ngIf="actionImported" mat-menu-item>Back to
imported imported
</button> </button>
<button (click)="this.updateStatus(this.files, 'Deleted')" <button (click)="this.changeFileStatus('Deleted')"
*ngIf="actionDelete" *ngIf="actionDelete"
mat-menu-item>Delete mat-menu-item>Delete
</button> </button>
@ -14,7 +14,7 @@
*ngIf="actionDeletePermantently" *ngIf="actionDeletePermantently"
mat-menu-item>Delete permanently mat-menu-item>Delete permanently
</button> </button>
<button (click)="this.updateStatus(this.files, 'Archived')" *ngIf="actionRestore" mat-menu-item>Restore</button> <button (click)="this.changeFileStatus('Archived')" *ngIf="actionRestore" mat-menu-item>Restore</button>
<!-- everything that only applies to a single file --> <!-- everything that only applies to a single file -->
<ng-container> <ng-container>

@ -7,6 +7,7 @@ import {MatDialog, MatDialogRef} from "@angular/material/dialog";
import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component"; import {BusyDialogComponent} from "../../app-common/busy-dialog/busy-dialog.component";
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {FileActionBaseComponent} from "../../app-base/file-action-base/file-action-base.component"; import {FileActionBaseComponent} from "../../app-base/file-action-base/file-action-base.component";
import {FileStatus} from "../../../../../api/api-types/files";
type ProgressDialogContext = { type ProgressDialogContext = {
dialog: MatDialogRef<BusyDialogComponent>, dialog: MatDialogRef<BusyDialogComponent>,
@ -31,6 +32,7 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements
@ViewChild("contextMenu") contextMenu!: ContextMenuComponent; @ViewChild("contextMenu") contextMenu!: ContextMenuComponent;
@Output() fileDeleted = new EventEmitter<File[]>(); @Output() fileDeleted = new EventEmitter<File[]>();
@Output() fileStatusChange = new EventEmitter<File[]>();
constructor(fileService: FileService, errorBroker: ErrorBrokerService, dialog: MatDialog) { constructor(fileService: FileService, errorBroker: ErrorBrokerService, dialog: MatDialog) {
super(dialog, errorBroker, fileService); super(dialog, errorBroker, fileService);
@ -56,6 +58,11 @@ export class FileContextMenuComponent extends FileActionBaseComponent implements
} }
} }
public async changeFileStatus(status: FileStatus) {
await this.updateStatus(this.files, status);
this.fileStatusChange.emit(this.files);
}
private applyStatus() { private applyStatus() {
this.actionDeletePermantently = true; this.actionDeletePermantently = true;
this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false; this.actionDelete = this.actionArchive = this.actionImported = this.actionRestore = false;

@ -15,9 +15,11 @@
minBufferPx="1000" orientation="horizontal"> minBufferPx="1000" orientation="horizontal">
<div *cdkVirtualFor="let entry of entries; trackBy: trackByFileId" class="file-item"> <div *cdkVirtualFor="let entry of entries; trackBy: trackByFileId" class="file-item">
<app-file-card (clickEvent)="onEntrySelect($event.entry)" <app-file-card (clickEvent)="onEntrySelect($event.entry)"
[entry]="entry"></app-file-card> [entry]="entry" [fileChanged]="this.fileChanged"></app-file-card>
</div> </div>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
</div> </div>
</div> </div>
<app-file-context-menu #fileContextMenu (fileDeleted)="this.fileDeleted.emit($event)"></app-file-context-menu> <app-file-context-menu #fileContextMenu
(fileDeleted)="this.fileDeleted.emit($event)"
(fileStatusChange)="this.onFileStatusChange()"></app-file-context-menu>

@ -17,6 +17,7 @@ import {Selectable} from "../../../../../models/Selectable";
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 {Key} from "w3c-keys"; import {Key} from "w3c-keys";
import {BehaviorSubject} from "rxjs";
@Component({ @Component({
selector: "app-file-gallery", selector: "app-file-gallery",
@ -40,11 +41,15 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
public entries: Selectable<File>[] = []; public entries: Selectable<File>[] = [];
public selectedFile: Selectable<File> | undefined; public selectedFile: Selectable<File> | undefined;
public fileContentUrl: SafeResourceUrl | undefined; public fileContentUrl: SafeResourceUrl | undefined;
public fileChanged = new BehaviorSubject<void>(undefined);
private scrollTimeout: number | undefined; private scrollTimeout: number | undefined;
private escapeCount = 0; private escapeCount = 0;
constructor(private tabService: TabService, private fileService: FileService) { constructor(
private tabService: TabService,
private fileService: FileService
) {
tabService.selectedTab.subscribe(() => this.adjustElementSizes()); tabService.selectedTab.subscribe(() => this.adjustElementSizes());
} }
@ -177,6 +182,10 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
return item.data.id; return item.data.id;
} }
public onFileStatusChange(): void {
this.fileChanged.next();
}
private scrollToSelection(): void { private scrollToSelection(): void {
if (this.selectedFile) { if (this.selectedFile) {
const selectedIndex = this.entries.indexOf(this.selectedFile); const selectedIndex = this.entries.indexOf(this.selectedFile);
@ -194,7 +203,6 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
} }
private getPreselectedEntry(): Selectable<File> | undefined { private getPreselectedEntry(): Selectable<File> | undefined {
if (this.preselectedFile) { if (this.preselectedFile) {
const entry = this.entries.find( const entry = this.entries.find(

@ -12,13 +12,13 @@
(contextmenu)="this.selectEntryWhenNotSelected(gridEntry); fileContextMenu.onContextMenu($event, this.getSelectedFiles())" (contextmenu)="this.selectEntryWhenNotSelected(gridEntry); fileContextMenu.onContextMenu($event, this.getSelectedFiles())"
(dblClickEvent)="fileOpen.emit($event.entry.data)" (dblClickEvent)="fileOpen.emit($event.entry.data)"
*ngFor="let gridEntry of rowEntry; trackBy: trackByFileId" *ngFor="let gridEntry of rowEntry; trackBy: trackByFileId"
[entry]="gridEntry"></app-file-card> [entry]="gridEntry" [fileChanged]="this.fileChanged"></app-file-card>
</div> </div>
</div> </div>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
</div> </div>
<app-file-context-menu #fileContextMenu (fileDeleted)="this.fileDeleted.emit($event)"> <app-file-context-menu #fileContextMenu (fileDeleted)="this.fileDeleted.emit($event)" (fileStatusChange)="this.onFileStatusChange()">
<button (click)="this.fileOpen.emit(fileContextMenu.files[0])" <button (click)="this.fileOpen.emit(fileContextMenu.files[0])"
*ngIf="fileContextMenu.files.length === 1" *ngIf="fileContextMenu.files.length === 1"
content-before="" content-before=""

@ -18,6 +18,7 @@ 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"; import {Selectable} from "../../../../../models/Selectable";
import {Key} from "w3c-keys"; import {Key} from "w3c-keys";
import {BehaviorSubject} from "rxjs";
@Component({ @Component({
selector: "app-file-grid", selector: "app-file-grid",
@ -38,8 +39,10 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
@ViewChild("virtualScrollGrid") virtualScroll!: CdkVirtualScrollViewport; @ViewChild("virtualScrollGrid") virtualScroll!: CdkVirtualScrollViewport;
@ViewChild("inner") inner!: ElementRef<HTMLDivElement>; @ViewChild("inner") inner!: ElementRef<HTMLDivElement>;
selectedEntries: Selectable<File>[] = []; public fileChanged = new BehaviorSubject<void>(undefined);
partitionedGridEntries: Selectable<File>[][] = []; public selectedEntries: Selectable<File>[] = [];
public partitionedGridEntries: Selectable<File>[][] = [];
private shiftClicked = false; private shiftClicked = false;
private ctrlClicked = false; private ctrlClicked = false;
private gridEntries: Selectable<File>[] = []; private gridEntries: Selectable<File>[] = [];
@ -179,6 +182,10 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
return item.data.id; return item.data.id;
} }
public onFileStatusChange(): void {
this.fileChanged.next();
}
private setPartitionedGridEntries() { private setPartitionedGridEntries() {
this.partitionedGridEntries = []; this.partitionedGridEntries = [];
let scrollToIndex = -1; let scrollToIndex = -1;

@ -1,10 +1,10 @@
<app-content-aware-image *ngIf="this.getThumbnailSupported() && this.thumbUrl" [imageSrc]="this.thumbUrl" <app-content-aware-image *ngIf="this.thumbnailSupported && this.thumbUrl" [imageSrc]="this.thumbUrl"
borderRadius="0.25em"></app-content-aware-image> borderRadius="0.25em"></app-content-aware-image>
<div *ngIf="this.getThumbnailSupported() && this.thumbUrl" class="file-icon-overlay"> <div *ngIf="this.thumbnailSupported && this.thumbUrl" class="file-icon-overlay">
<ng-icon *ngIf="fileType === 'video'" name="mat-movie"></ng-icon> <ng-icon *ngIf="fileType === 'video'" name="mat-movie"></ng-icon>
<ng-icon *ngIf="this.file.mimeType === 'image/gif'" class="gif-icon" name="mat-gif"></ng-icon> <ng-icon *ngIf="this.file.mimeType === 'image/gif'" class="gif-icon" name="mat-gif"></ng-icon>
</div> </div>
<div *ngIf="!this.getThumbnailSupported() || !this.thumbUrl" class="file-type-icon"> <div *ngIf="!this.thumbnailSupported || !this.thumbUrl" class="file-type-icon">
<ng-icon *ngIf="fileType === 'image'" name="mat-image"></ng-icon> <ng-icon *ngIf="fileType === 'image'" name="mat-image"></ng-icon>
<ng-icon *ngIf="fileType === 'video'" name="mat-movie"></ng-icon> <ng-icon *ngIf="fileType === 'video'" name="mat-movie"></ng-icon>
<ng-icon *ngIf="fileType === 'audio'" name="mat-audiotrack"></ng-icon> <ng-icon *ngIf="fileType === 'audio'" name="mat-audiotrack"></ng-icon>

@ -1,25 +1,38 @@
import {AfterViewInit, Component, Input, OnChanges, SimpleChanges} from "@angular/core"; import {
AfterViewChecked,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnChanges,
SimpleChanges
} from "@angular/core";
import {File} from "../../../../../api/models/File"; import {File} from "../../../../../api/models/File";
import {FileService} from "../../../../services/file/file.service"; import {FileService} from "../../../../services/file/file.service";
import {FileHelper} from "../../../../services/file/file.helper"; import {FileHelper} from "../../../../services/file/file.helper";
import {SafeResourceUrl} from "@angular/platform-browser"; import {SafeResourceUrl} from "@angular/platform-browser";
import {BehaviorSubject} from "rxjs";
@Component({ @Component({
selector: "app-file-thumbnail", selector: "app-file-thumbnail",
templateUrl: "./file-thumbnail.component.html", templateUrl: "./file-thumbnail.component.html",
styleUrls: ["./file-thumbnail.component.scss"] styleUrls: ["./file-thumbnail.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class FileThumbnailComponent implements OnChanges, AfterViewInit { export class FileThumbnailComponent implements OnChanges, AfterViewInit, AfterViewChecked {
@Input() file!: File; @Input() file!: File;
@Input() public fileChanged: BehaviorSubject<void> = new BehaviorSubject<void>(undefined);
public thumbUrl: SafeResourceUrl | undefined; public thumbUrl: SafeResourceUrl | undefined;
public fileType!: string; public fileType!: string;
public thumbnailSupported: boolean = false; public thumbnailSupported: boolean = false;
private supportedThumbnailTypes = ["image", "video"]; private supportedThumbnailTypes = ["image", "video"];
private previousStatus = "imported";
constructor(private fileService: FileService) { constructor(private changeDetector: ChangeDetectorRef, private fileService: FileService) {
} }
public async ngAfterViewInit() { public async ngAfterViewInit() {
@ -28,6 +41,13 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit {
} }
} }
public ngAfterViewChecked(): void {
if (this.file && this.file.status != this.previousStatus) {
this.previousStatus = this.file.status;
this.changeDetector.markForCheck();
}
}
public async ngOnChanges(changes: SimpleChanges) { public async ngOnChanges(changes: SimpleChanges) {
if (changes["file"]) { if (changes["file"]) {
this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, this.thumbUrl = this.fileService.buildThumbnailUrl(this.file,
@ -36,16 +56,19 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit {
this.fileType = this.getFileType(); this.fileType = this.getFileType();
this.thumbnailSupported = this.getThumbnailSupported(); this.thumbnailSupported = this.getThumbnailSupported();
} }
if (changes["fileChanged"]) {
this.fileChanged.subscribe(() => this.changeDetector.markForCheck());
}
} }
public getThumbnailSupported(): boolean { private getThumbnailSupported(): boolean {
const mimeParts = FileHelper.parseMime(this.file.mimeType); const mimeParts = FileHelper.parseMime(this.file.mimeType);
return !!mimeParts && this.supportedThumbnailTypes.includes( return !!mimeParts && this.supportedThumbnailTypes.includes(
mimeParts[0]); mimeParts[0]);
} }
public getFileType(): string { private getFileType(): string {
const mimeParts = FileHelper.parseMime(this.file.mimeType); const mimeParts = FileHelper.parseMime(this.file.mimeType);
return (mimeParts && mimeParts[0]) ?? "other"; return (mimeParts && mimeParts[0]) ?? "other";
} }

Loading…
Cancel
Save