Fix weird issues with file thumbnail loading

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

@ -50,6 +50,11 @@ export class CoreComponent {
} }
state.tabs.subscribe(tabs => { state.tabs.subscribe(tabs => {
this.tabs = tabs; this.tabs = tabs;
const selectedIndex = state.selectedTab.value;
if (selectedIndex) {
this.tabGroup.selectedIndex = selectedIndex;
}
if (this.tabs.length === 0) { if (this.tabs.length === 0) {
this.addTab(); this.addTab();
@ -64,6 +69,9 @@ export class CoreComponent {
public onTabSelectionChange(event: MatTabChangeEvent): void { public onTabSelectionChange(event: MatTabChangeEvent): void {
this.tabService.setSelectedTab(event.index); this.tabService.setSelectedTab(event.index);
if (event.index > 0 && event.index <= this.tabs.length) {
this.appState.selectedTab.next(event.index);
}
} }
public addFilesTab(): void { public addFilesTab(): void {

@ -57,7 +57,7 @@ export class FilesTabSidebarComponent implements OnInit, OnChanges {
if (this.fileSearch) { if (this.fileSearch) {
await this.fileSearch.searchForFiles(); await this.fileSearch.searchForFiles();
} }
if (this.tags.length === 0) { if (this.tags.length === 0 && this.selectedFiles.length === 0) {
this.tags = this.tagsOfFiles; this.tags = this.tagsOfFiles;
} }
} }
@ -102,7 +102,7 @@ export class FilesTabSidebarComponent implements OnInit, OnChanges {
} }
private showAllTagsFallback() { private showAllTagsFallback() {
if (this.tags.length === 0) { if (this.tags.length === 0 && this.selectedFiles.length === 0) {
this.tags = this.tagsOfFiles.sort( this.tags = this.tagsOfFiles.sort(
(a, b) => a.getNormalizedOutput() (a, b) => a.getNormalizedOutput()
.localeCompare(b.getNormalizedOutput())); .localeCompare(b.getNormalizedOutput()));

@ -4,6 +4,7 @@ import {
EventEmitter, EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
Output, Output,
SimpleChanges, SimpleChanges,
@ -11,24 +12,28 @@ import {
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../models/File";
import {Selectable} from "../../../../models/Selectable"; import {Selectable} from "../../../../models/Selectable";
import {
SchedulingService
} from "../../../../services/scheduling/scheduling.service";
const LOADING_WORK_KEY = "FILE_THUMBNAIL_LOADING";
@Component({ @Component({
selector: "app-file-card", selector: "app-file-card",
templateUrl: "./file-card.component.html", templateUrl: "./file-card.component.html",
styleUrls: ["./file-card.component.scss"] styleUrls: ["./file-card.component.scss"]
}) })
export class FileCardComponent implements OnInit, OnChanges { export class FileCardComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild("card") card!: ElementRef; @ViewChild("card") card!: ElementRef;
@Input() public entry!: Selectable<File>; @Input() public entry!: Selectable<File>;
@Output() clickEvent = new EventEmitter<FileCardComponent>(); @Output() clickEvent = new EventEmitter<FileCardComponent>();
@Output() dblClickEvent = new EventEmitter<FileCardComponent>(); @Output() dblClickEvent = new EventEmitter<FileCardComponent>();
private cachedId: number | undefined; private cachedId: number | undefined;
private urlSetTimeout: number | undefined; private workId: number | undefined;
public loading = false; public loading = false;
constructor() { constructor(private schedulingService: SchedulingService) {
} }
async ngOnInit() { async ngOnInit() {
@ -43,10 +48,21 @@ export class FileCardComponent implements OnInit, OnChanges {
} }
} }
public ngOnDestroy(): void {
if (this.workId) {
this.schedulingService.cancelWork(LOADING_WORK_KEY, this.workId);
}
}
private setImageDelayed() { private setImageDelayed() {
if (this.workId) {
this.schedulingService.cancelWork(LOADING_WORK_KEY, this.workId);
}
this.loading = true; this.loading = true;
clearTimeout(this.urlSetTimeout); this.workId = this.schedulingService.addWork(LOADING_WORK_KEY,
this.urlSetTimeout = setTimeout( async () => {
() => this.loading = false, 200); await this.schedulingService.delay(1);
this.loading = false
});
} }
} }

@ -1,4 +1,5 @@
import { import {
AfterViewChecked, AfterViewInit,
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
@ -15,7 +16,7 @@ import {FileGridComponent} from "./file-grid/file-grid.component";
templateUrl: "./file-multiview.component.html", templateUrl: "./file-multiview.component.html",
styleUrls: ["./file-multiview.component.scss"] styleUrls: ["./file-multiview.component.scss"]
}) })
export class FileMultiviewComponent { export class FileMultiviewComponent implements AfterViewInit {
@Input() files!: File[]; @Input() files!: File[];
@Input() mode: "grid" | "gallery" = "grid"; @Input() mode: "grid" | "gallery" = "grid";
@ -33,6 +34,13 @@ export class FileMultiviewComponent {
constructor() { constructor() {
} }
public ngAfterViewInit(): void {
if (this.preselectedFile) {
this.fileSelectEvent.emit([this.preselectedFile])
this.selectedFiles = [this.preselectedFile];
}
}
public onFileSelect(files: File[]): void { public onFileSelect(files: File[]): void {
this.selectedFiles = files; this.selectedFiles = files;
this.preselectedFile = files[0]; this.preselectedFile = files[0];

@ -1,21 +1,24 @@
import { import {
AfterViewInit,
Component, Component,
Input, Input,
OnChanges, OnChanges,
OnInit,
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../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 {
SchedulingService
} from "../../../../services/scheduling/scheduling.service";
@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"]
}) })
export class FileThumbnailComponent implements OnInit, OnChanges { export class FileThumbnailComponent implements OnChanges, AfterViewInit {
@Input() file!: File; @Input() file!: File;
@ -23,17 +26,17 @@ export class FileThumbnailComponent implements OnInit, OnChanges {
private supportedThumbnailTypes = ["image", "video"] private supportedThumbnailTypes = ["image", "video"]
constructor(private fileService: FileService) { constructor( private fileService: FileService) {
} }
public ngOnInit(): void { public async ngAfterViewInit() {
this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, 250); this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, 250);
} }
public ngOnChanges(changes: SimpleChanges): void { public async ngOnChanges(changes: SimpleChanges) {
if (changes["file"]) { if (changes["file"]) {
this.thumbUrl = this.fileService.buildThumbnailUrl(this.file, 250, this.thumbUrl = this.fileService.buildThumbnailUrl(this.file,
250) 250, 250);
} }
} }

@ -33,4 +33,5 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<app-busy-indicator *ngIf="this.loading" [busy]="this.loading" [blurBackground]="true" [darkenBackground]="true"></app-busy-indicator>
</div> </div>

@ -74,3 +74,12 @@ cdk-virtual-scroll-viewport {
mat-divider { mat-divider {
width: 100%; width: 100%;
} }
app-busy-indicator {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 99;
}

@ -10,6 +10,7 @@ import {File} from "../../../../models/File";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../models/Tag";
import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling"; import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
import {TagService} from "../../../../services/tag/tag.service"; import {TagService} from "../../../../services/tag/tag.service";
import {delay} from "rxjs/operators";
@Component({ @Component({
selector: "app-tag-edit", selector: "app-tag-edit",
@ -26,6 +27,8 @@ export class TagEditComponent implements OnInit, OnChanges {
@ViewChild("tagScroll") tagScroll!: CdkVirtualScrollViewport; @ViewChild("tagScroll") tagScroll!: CdkVirtualScrollViewport;
private fileTags: { [key: number]: Tag[] } = {}; private fileTags: { [key: number]: Tag[] } = {};
public loading = false;
constructor( constructor(
private tagService: TagService, private tagService: TagService,
) { ) {
@ -44,6 +47,7 @@ export class TagEditComponent implements OnInit, OnChanges {
} }
public async editTag(tag: string): Promise<void> { public async editTag(tag: string): Promise<void> {
this.loading = true;
if (tag.length > 0) { if (tag.length > 0) {
let tagInstance = this.allTags.find( let tagInstance = this.allTags.find(
t => t.getNormalizedOutput() === tag); t => t.getNormalizedOutput() === tag);
@ -64,6 +68,7 @@ export class TagEditComponent implements OnInit, OnChanges {
break; break;
} }
} }
this.loading = false;
} }
async toggleTag(tag: Tag) { async toggleTag(tag: Tag) {
@ -87,7 +92,7 @@ export class TagEditComponent implements OnInit, OnChanges {
async addTag(tag: Tag) { async addTag(tag: Tag) {
for (const file of this.files) { for (const file of this.files) {
if (this.fileTags[file.id].findIndex(t => t.id === tag.id) < 0) { if ((this.fileTags[file.id] ?? []).findIndex(t => t.id === tag.id) < 0) {
this.fileTags[file.id] = await this.tagService.changeFileTags( this.fileTags[file.id] = await this.tagService.changeFileTags(
file.id, file.id,
[tag.id], []); [tag.id], []);
@ -99,6 +104,7 @@ export class TagEditComponent implements OnInit, OnChanges {
} }
public async removeTag(tag: Tag) { public async removeTag(tag: Tag) {
this.loading = true;
for (const file of this.files) { for (const file of this.files) {
if (this.fileTags[file.id].findIndex(t => t.id === tag.id) >= 0) { if (this.fileTags[file.id].findIndex(t => t.id === tag.id) >= 0) {
this.fileTags[file.id] = await this.tagService.changeFileTags( this.fileTags[file.id] = await this.tagService.changeFileTags(
@ -107,14 +113,23 @@ export class TagEditComponent implements OnInit, OnChanges {
} }
} }
this.mapFileTagsToTagList(); this.mapFileTagsToTagList();
this.loading = false;
} }
private async loadFileTags() { private async loadFileTags() {
for (const file of this.files) { this.loading = true;
const promises = [];
const loadFn = async (file: File) => {
this.fileTags[file.id] = await this.tagService.getTagsForFiles( this.fileTags[file.id] = await this.tagService.getTagsForFiles(
[file.hash]); [file.hash]);
} }
for (const file of this.files) {
promises.push(loadFn(file));
}
await Promise.all(promises);
this.mapFileTagsToTagList(); this.mapFileTagsToTagList();
this.loading = false;
} }
private mapFileTagsToTagList() { private mapFileTagsToTagList() {

@ -7,6 +7,7 @@ export class AppState {
private tabIdCounter = 0; private tabIdCounter = 0;
public tabs = new BehaviorSubject<TabState[]>([]); public tabs = new BehaviorSubject<TabState[]>([]);
public selectedTab = new BehaviorSubject<number | undefined>(undefined);
private readonly fileService: FileService private readonly fileService: FileService
@ -34,6 +35,7 @@ export class AppState {
appState.tabs.next(tabs); appState.tabs.next(tabs);
appState.tabIdCounter = state.tabIdCounter; appState.tabIdCounter = state.tabIdCounter;
appState.selectedTab.next(state.selectedTab);
return appState return appState
} }
@ -43,6 +45,7 @@ export class AppState {
return JSON.stringify({ return JSON.stringify({
tabs: tabDTOs, tabs: tabDTOs,
tabIdCounter: this.tabIdCounter, tabIdCounter: this.tabIdCounter,
selectedTab: this.selectedTab.value,
}); });
} }
} }

@ -59,6 +59,14 @@ export class RepositoryService {
} else { } else {
await this.disconnectSelectedRepository(); await this.disconnectSelectedRepository();
} }
} else {
try {
// just to make sure because sometimes there's some weird issues
await this.disconnectSelectedRepository();
} catch (err) {
console.warn(err);
}
} }
await invoke("plugin:mediarepo|select_repository", {name: repo.name}); await invoke("plugin:mediarepo|select_repository", {name: repo.name});
await this.loadRepositories(); await this.loadRepositories();

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SchedulingService } from './scheduling.service';
describe('SchedulingService', () => {
let service: SchedulingService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(SchedulingService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,58 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject} from "rxjs";
import {filter} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class SchedulingService {
private workQueue: { [key: string]: {id: number, cancelled: boolean, cb: Function}[] } = {}
private lastWorkId = 0;
constructor() {
}
public addWork(key: string, cb: Function): number {
if (!this.workQueue[key]) {
this.workQueue[key] = [];
setTimeout(() => this.startWork(key), 0); // start in the next tick
}
const id = this.lastWorkId++;
this.workQueue[key].push({id, cb, cancelled: false});
return id;
}
public cancelWork(key: string, id: number) {
const work = this.workQueue[key]?.find(w => w.id === id);
if (work) {
work.cancelled = true;
}
}
private async startWork(key: string) {
while (true) {
if (this.workQueue[key]?.length > 0) {
let work = this.workQueue[key].shift();
let count = 0;
while (work?.cancelled && count++ < 100) {
work = this.workQueue[key].shift();
}
if (work) {
try {
await work.cb();
} catch (err) {
console.error(err);
}
}
}
await this.delay(1);
}
}
public async delay(time: number) {
return new Promise((res, rej) => {
setTimeout(res, time);
})
}
}
Loading…
Cancel
Save