Switch file preview strip in gallery for static rendered entries

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/14/head
trivernis 3 years ago
parent 3674200c28
commit 4d56e37db2
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -130,7 +130,7 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit {
"Opening repository..."); "Opening repository...");
let dialog = this.dialog.open(BusyDialogComponent, { let dialog = this.dialog.open(BusyDialogComponent, {
data: { data: {
title: `Opening repository ${repository.name}`, title: `Opening repository '${repository.name}'`,
message: dialogMessage, message: dialogMessage,
allowCancel: true, allowCancel: true,
}, disableClose: true, }, disableClose: true,

@ -1,4 +1,8 @@
<div #inner (keyDownEvent)="handleKeydownEvent($event)" appInputReceiver class="gallery-container"> <div #inner
(keyDownEvent)="handleKeydownEvent($event)"
(window:resize)="this.onResize()"
appInputReceiver
class="gallery-container">
<button (click)="this.appClose.emit(this)" class="close-button" mat-icon-button> <button (click)="this.appClose.emit(this)" class="close-button" mat-icon-button>
<ng-icon name="mat-close"></ng-icon> <ng-icon name="mat-close"></ng-icon>
</button> </button>
@ -15,14 +19,12 @@
</div> </div>
<div <div
class="file-scroll-view"> class="file-scroll-view">
<mat-divider></mat-divider> <div #previewStripContainer class="file-preview-strip-container">
<cdk-virtual-scroll-viewport #virtualScroll class="file-scroll-viewport" itemSize="260" maxBufferPx="3000" <div *ngFor="let entry of this.previewedEntries; trackBy: trackByFileId" class="file-item">
minBufferPx="1000" orientation="horizontal"> <app-file-card (clickEvent)="onEntrySelect($event.entry)" *ngIf="entry"
<div *cdkVirtualFor="let entry of entries; trackBy: trackByFileId" class="file-item">
<app-file-card (clickEvent)="onEntrySelect($event.entry)"
[entry]="entry" [fileChanged]="this.fileChanged"></app-file-card> [entry]="entry" [fileChanged]="this.fileChanged"></app-file-card>
</div> </div>
</cdk-virtual-scroll-viewport> </div>
</div> </div>
</div> </div>
<app-file-context-menu #fileContextMenu <app-file-context-menu #fileContextMenu

@ -1,3 +1,5 @@
@import "src/colors";
.file-scroll-viewport { .file-scroll-viewport {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -17,16 +19,6 @@
overflow-y: hidden; overflow-y: hidden;
} }
app-file-gallery-entry, .file-item {
width: 250px;
height: calc(100% - 10px);
padding: 5px;
}
app-file-gallery-entry {
display: block;
}
.file-full-view { .file-full-view {
position: relative; position: relative;
width: 100%; width: 100%;
@ -43,15 +35,33 @@ app-file-gallery-entry {
transition-duration: 1s; transition-duration: 1s;
height: 20%; height: 20%;
.file-scroll-viewport {
height: calc(100% - 4px);
}
mat-divider { mat-divider {
height: 2px; height: 2px;
} }
} }
.file-preview-strip-container {
display: flex;
width: 100%;
height: 100%;
border-top: 2px solid $background-darker-05;
.file-item {
width: 100%;
overflow: hidden;
height: calc(100%);
::ng-deep mat-card {
border-left: 2px solid $background;
border-radius: 0;
}
}
.file-item:first-child {
border-left: none;
}
}
app-content-viewer { app-content-viewer {
height: 100%; height: 100%;
width: 100%; width: 100%;

@ -1,5 +1,8 @@
import { import {
AfterViewChecked,
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
@ -14,7 +17,6 @@ import {File} from "../../../../../../api/models/File";
import {FileService} from "../../../../../services/file/file.service"; 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 {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"; import {BehaviorSubject} from "rxjs";
@ -22,9 +24,10 @@ import {BehaviorSubject} from "rxjs";
@Component({ @Component({
selector: "app-file-gallery", selector: "app-file-gallery",
templateUrl: "./file-gallery.component.html", templateUrl: "./file-gallery.component.html",
styleUrls: ["./file-gallery.component.scss"] styleUrls: ["./file-gallery.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit { export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit, AfterViewChecked {
@Input() files: File[] = []; @Input() files: File[] = [];
@Input() preselectedFile: File | undefined; @Input() preselectedFile: File | undefined;
@ -34,9 +37,8 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
@Output() fileDelete = new EventEmitter<File>(); @Output() fileDelete = new EventEmitter<File>();
@Output() fileDeleted = new EventEmitter<File[]>(); @Output() fileDeleted = new EventEmitter<File[]>();
@ViewChild("virtualScroll") virtualScroll!: CdkVirtualScrollViewport;
@ViewChild("inner") inner!: ElementRef<HTMLDivElement>; @ViewChild("inner") inner!: ElementRef<HTMLDivElement>;
@ViewChild("previewStripContainer") stripContainer!: ElementRef<HTMLDivElement>;
public entries: Selectable<File>[] = []; public entries: Selectable<File>[] = [];
public selectedFile: Selectable<File> | undefined; public selectedFile: Selectable<File> | undefined;
@ -45,15 +47,16 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
public imageViewHeightPercent = 80; public imageViewHeightPercent = 80;
public previewStripVisible = true; public previewStripVisible = true;
public previewedEntries: (Selectable<File> | undefined)[] = [];
private scrollTimeout: number | undefined; private previewStripCount = 5;
private escapeCount = 0; private escapeCount = 0;
constructor( constructor(
private changeDetector: ChangeDetectorRef,
private tabService: TabService, private tabService: TabService,
private fileService: FileService private fileService: FileService
) { ) {
tabService.selectedTab.subscribe(() => this.adjustElementSizes());
} }
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@ -61,6 +64,8 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
this.selectedFile.data) < 0) { this.selectedFile.data) < 0) {
await this.onEntrySelect( await this.onEntrySelect(
this.getPreselectedEntry() ?? this.entries[0]); this.getPreselectedEntry() ?? this.entries[0]);
} else {
this.buildPreviewedFiles();
} }
} }
@ -84,6 +89,10 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
} }
public ngAfterViewChecked(): void {
this.calculatePreviewCount();
}
/** /**
* Called when a new entry is selected * Called when a new entry is selected
* @param {Selectable<File>} entry * @param {Selectable<File>} entry
@ -96,15 +105,8 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
this.selectedFile = entry; this.selectedFile = entry;
await this.loadSelectedFile(); await this.loadSelectedFile();
if (this.virtualScroll) {
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(
() => this.scrollToSelection(),
0
); // we need to make sure the viewport has rendered
}
this.fileSelect.emit(this.selectedFile.data); this.fileSelect.emit(this.selectedFile.data);
this.buildPreviewedFiles();
} }
} }
@ -151,13 +153,6 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
} }
public adjustElementSizes(): void {
if (this.virtualScroll) {
this.virtualScroll.checkViewportSize();
this.scrollToSelection();
}
}
public focus() { public focus() {
this.inner.nativeElement.focus(); this.inner.nativeElement.focus();
} }
@ -181,8 +176,8 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
} }
public trackByFileId(index: number, item: Selectable<File>) { public trackByFileId(index: number, item?: Selectable<File>) {
return item.data.id; return item?.data.id;
} }
public onFileStatusChange(): void { public onFileStatusChange(): void {
@ -199,23 +194,24 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
} }
private scrollToSelection(): void { public calculatePreviewCount() {
if (this.selectedFile) { if (this.stripContainer && this.stripContainer.nativeElement) {
const selectedIndex = this.entries.indexOf(this.selectedFile); const width = Math.abs(this.stripContainer.nativeElement.clientWidth);
const viewportSize = this.virtualScroll.getViewportSize(); const height = Math.abs(this.stripContainer.nativeElement.clientHeight);
const indexAdjustment = (viewportSize / 260) / 2; // adjustment to have the selected item centered
this.virtualScroll.scrollToIndex( const count = Math.floor(Math.floor(width / height) / 2) * 2 + 1;
Math.max(selectedIndex - indexAdjustment, 0), "smooth");
if (selectedIndex > indexAdjustment) { if (count != this.previewStripCount) {
this.virtualScroll.scrollToOffset( this.previewStripCount = count;
this.virtualScroll.measureScrollOffset("left") + 130, this.buildPreviewedFiles();
"smooth"
);
} }
} }
} }
public onResize(): void {
this.changeDetector.markForCheck();
}
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(
@ -235,4 +231,26 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
setTimeout(() => this.escapeCount--, 500); setTimeout(() => this.escapeCount--, 500);
} }
} }
private buildPreviewedFiles() {
if (!this.selectedFile) {
if (this.entries) {
this.onEntrySelect(this.entries[0]).catch(console.error);
}
return;
}
const selectedIndex = this.entries.indexOf(this.selectedFile!);
const previewCountLR = Math.floor(this.previewStripCount / 2);
const previewedEntries = [];
for (let i = selectedIndex - previewCountLR; i <= selectedIndex + previewCountLR; i++) {
if (i >= 0 && i < this.entries.length) {
previewedEntries.push(this.entries[i]);
} else {
previewedEntries.push(undefined);
}
}
this.previewedEntries = previewedEntries;
this.changeDetector.markForCheck();
}
} }

@ -1,7 +1,7 @@
<div #inner <div #inner
(keyDownEvent)="handleKeydownEvent($event)" (keyDownEvent)="handleKeydownEvent($event)"
(keyUpEvent)="handleKeyupEvent($event)" (keyUpEvent)="handleKeyupEvent($event)"
(window:resize)="this.calculateColumnCount()" (window:resize)="this.onResize()"
appInputReceiver appInputReceiver
class="file-grid-inner"> class="file-grid-inner">
<cdk-virtual-scroll-viewport #virtualScrollGrid class="file-scroll" itemSize="260" maxBufferPx="2000" <cdk-virtual-scroll-viewport #virtualScrollGrid class="file-scroll" itemSize="260" maxBufferPx="2000"

@ -4,11 +4,14 @@ app-file-card {
height: 250px; height: 250px;
width: 100%; width: 100%;
padding: 5px; padding: 5px;
display: block;
overflow: hidden;
} }
.file-scroll { .file-scroll {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow-x: hidden;
} }
.file-grid-inner { .file-grid-inner {
@ -23,4 +26,5 @@ app-file-card {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
overflow-x: hidden;
} }

@ -213,6 +213,10 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit, Afte
} }
} }
public onResize(): void {
this.changeDetector.markForCheck();
}
private setPartitionedGridEntries() { private setPartitionedGridEntries() {
this.partitionedGridEntries = []; this.partitionedGridEntries = [];
let scrollToIndex = -1; let scrollToIndex = -1;

Loading…
Cancel
Save