Separate import and files tab state

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/14/head
trivernis 2 years ago
parent 3f37fb4e5e
commit 642222e0c4
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -14,8 +14,8 @@
</div>
</ng-template>
<ng-template matTabContent>
<app-files-tab *ngIf="tab.category === 'Files'" [state]="tab"></app-files-tab>
<app-import-tab *ngIf="tab.category === 'Import'" [state]="tab"></app-import-tab>
<app-files-tab *ngIf="tab.category === 'Files'" [state]="tab.filesTab()"></app-files-tab>
<app-import-tab *ngIf="tab.category === 'Import'" [state]="tab.importTab()"></app-import-tab>
</ng-template>
</mat-tab>
<mat-tab *ngIf="this.newTab" label="New Tab">

@ -4,10 +4,10 @@ import {RepositoryService} from "../../services/repository/repository.service";
import {MatTabChangeEvent, MatTabGroup} from "@angular/material/tabs";
import {TagService} from "../../services/tag/tag.service";
import {TabService} from "../../services/tab/tab.service";
import {TabCategory} from "../../models/TabCategory";
import {TabState} from "../../models/TabState";
import {AppState} from "../../models/AppState";
import {TabCategory} from "../../models/state/TabCategory";
import {AppState} from "../../models/state/AppState";
import {StateService} from "../../services/state/state.service";
import {TabState} from "../../models/state/TabState";
@Component({
selector: "app-core",

@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Output} from "@angular/core";
import {TabCategory} from "../../../models/TabCategory";
import {TabCategory} from "../../../models/state/TabCategory";
type TabCategoryName = "files" | "import";

@ -5,7 +5,7 @@ import {File} from "../../../../../api/models/File";
import {FileSearchComponent} from "../../../shared/sidebar/file-search/file-search.component";
import {RepositoryService} from "../../../../services/repository/repository.service";
import {TagEditComponent} from "../../../shared/sidebar/tag-edit/tag-edit.component";
import {TabState} from "../../../../models/TabState";
import {FilesTabState} from "../../../../models/state/FilesTabState";
@Component({
selector: "app-files-tab-sidebar",
@ -14,7 +14,7 @@ import {TabState} from "../../../../models/TabState";
})
export class FilesTabSidebarComponent implements OnInit, OnChanges {
@Input() state!: TabState;
@Input() state!: FilesTabState;
@Input() selectedFiles: File[] = [];
@Output() searchStartEvent = new EventEmitter<void>();
@Output() searchEndEvent = new EventEmitter<void>();

@ -1,9 +1,9 @@
import {Component, Input, OnInit} from "@angular/core";
import {File} from "../../../../api/models/File";
import {TabState} from "../../../models/TabState";
import {FilesTabState} from "../../../models/state/FilesTabState";
import {RepositoryMetadata} from "../../../../api/api-types/repo";
import {RepositoryService} from "../../../services/repository/repository.service";
import {TabCategory} from "../../../models/TabCategory";
import {TabCategory} from "../../../models/state/TabCategory";
@Component({
selector: "app-files-tab",
@ -12,7 +12,7 @@ import {TabCategory} from "../../../models/TabCategory";
})
export class FilesTabComponent implements OnInit {
@Input() state!: TabState;
@Input() state!: FilesTabState;
files: File[] = [];
contentLoading = false;

@ -1,6 +1,6 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit} from "@angular/core";
import {File} from "../../../../api/models/File";
import {TabState} from "../../../models/TabState";
import {ImportTabState} from "../../../models/state/ImportTabState";
@Component({
selector: "app-import-tab",
@ -10,7 +10,7 @@ import {TabState} from "../../../models/TabState";
})
export class ImportTabComponent implements OnInit {
@Input() state!: TabState;
@Input() state!: ImportTabState;
public files: File[] = [];
public selectedFiles: File[] = [];

@ -6,7 +6,7 @@ import {FileActionBaseComponent} from "../../app-base/file-action-base/file-acti
import {MatDialog} from "@angular/material/dialog";
import {LoggingService} from "../../../../services/logging/logging.service";
import {FileService} from "../../../../services/file/file.service";
import {TabState} from "../../../../models/TabState";
import {FilesTabState} from "../../../../models/state/FilesTabState";
@Component({
selector: "app-file-multiview",
@ -17,7 +17,7 @@ export class FileMultiviewComponent extends FileActionBaseComponent implements A
@Input() files!: File[];
@Input() mode: "grid" | "gallery" = "grid";
@Input() tabState!: TabState;
@Input() tabState!: FilesTabState;
@Output() fileOpenEvent = new EventEmitter<File>();
@Output() fileSelectEvent = new EventEmitter<File[]>();

@ -15,7 +15,7 @@ import {LoggingService} from "../../../../services/logging/logging.service";
import {FilterDialogComponent} from "./filter-dialog/filter-dialog.component";
import {Tag} from "../../../../../api/models/Tag";
import {clipboard} from "@tauri-apps/api";
import {TabState} from "../../../../models/TabState";
import {FilesTabState} from "../../../../models/state/FilesTabState";
import {FilterQueryBuilder} from "../../../../../api/models/FilterQueryBuilder";
import {SearchFilters} from "../../../../../api/models/SearchFilters";
import {FileStatus, FilterExpression,} from "../../../../../api/api-types/files";
@ -37,7 +37,7 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
@Input() availableTags: Tag[] = [];
@Input() contextTags: Tag[] = [];
@Input() state!: TabState;
@Input() state!: FilesTabState;
@Input() tagsLoading = false;
@Output() searchStartEvent = new EventEmitter<void>();

@ -1,53 +0,0 @@
import {TabState} from "./TabState";
import {FileService} from "../services/file/file.service";
import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
export class AppState {
public tabs = new BehaviorSubject<TabState[]>([]);
public selectedTab = new BehaviorSubject<number | undefined>(undefined);
public repoName: string | undefined;
private tabIdCounter = 0;
private readonly fileService: FileService;
constructor(fileService: FileService) {
this.fileService = fileService;
}
public static deserializeJson(stateString: string, fileService: FileService): AppState {
let state = JSON.parse(stateString);
let appState = new AppState(fileService);
const tabs = state.tabs.map((tab: any) => TabState.fromDTO(tab, fileService));
appState.tabs.next(tabs);
appState.tabIdCounter = state.tabIdCounter;
appState.selectedTab.next(state.selectedTab);
appState.repoName = state.repoName;
return appState;
}
public addTab(category: TabCategory): TabState {
const state = new TabState(this.tabIdCounter++, category, this.fileService);
this.tabs.next([...this.tabs.value, state]);
return state;
}
public async closeTab(uuid: number) {
const index = this.tabs.value.findIndex(t => t.uuid === uuid);
const tabs = this.tabs.value;
tabs.splice(index, 1);
this.tabs.next(tabs);
}
public serializeJson(): string {
const tabDTOs = this.tabs.value.map(tab => tab.getDTO());
return JSON.stringify({
repoName: this.repoName,
tabs: tabDTOs,
tabIdCounter: this.tabIdCounter,
selectedTab: this.selectedTab.value,
});
}
}

@ -1,96 +0,0 @@
import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
import {FileService} from "../services/file/file.service";
import {File} from "../../api/models/File";
import {SortKey} from "../../api/models/SortKey";
import {debounceTime} from "rxjs/operators";
import {mapNew} from "../../api/models/adaptors";
import {SearchFilters} from "../../api/models/SearchFilters";
import {SortingPreset} from "../../api/models/SortingPreset";
export class TabState {
public uuid: number;
public category: TabCategory;
public mode = new BehaviorSubject<"grid" | "gallery">("grid");
public selectedCD = new BehaviorSubject<string | undefined>(undefined);
public loading = new BehaviorSubject<boolean>(false);
public files = new BehaviorSubject<File[]>([]);
public filters = new BehaviorSubject<SearchFilters>(new SearchFilters([]));
public sortingPreset = new BehaviorSubject<SortingPreset>(SortingPreset.fromValues(
-1,
[SortKey.fromValues(
"FileImportedTime",
"Ascending",
undefined
)]
));
private fileService: FileService;
constructor(
uuid: number,
category: TabCategory,
fileService: FileService
) {
this.category = category;
this.uuid = uuid;
this.fileService = fileService;
if (this.category === TabCategory.Files) {
this.filters.pipe(debounceTime(500))
.subscribe(async () => await this.findFiles());
this.sortingPreset.pipe(debounceTime(100))
.subscribe(async () => await this.findFiles());
}
}
public static fromDTO(
dto: any,
fileService: FileService
): TabState {
const state = new TabState(
dto.uuid,
dto.category,
fileService
);
state.filters.next(new SearchFilters(dto.filters ?? []));
state.sortingPreset.next(new SortingPreset(dto.sortingPreset));
state.mode.next(dto.mode ?? "grid");
state.selectedCD.next(dto.selectedFileHash);
state.files.next((dto.files ?? []).map(mapNew(File)));
return state;
}
public async findFiles() {
this.loading.next(true);
const files = await this.fileService.findFiles(
this.filters.value,
this.sortingPreset.value.sortKeys
);
this.files.next(files);
this.loading.next(false);
}
public setTagFilters(filters: SearchFilters) {
this.filters.next(filters);
}
public setSortingPreset(preset: SortingPreset) {
this.sortingPreset.next(preset);
}
public getDTO(): any {
return {
uuid: this.uuid,
category: this.category,
filters: this.filters.value.getFilters(),
sortingPreset: this.sortingPreset.value.rawData,
mode: this.mode.value,
selectedFileHash: this.selectedCD.value,
files: this.category === TabCategory.Import ? this.files.value.map(
f => f.rawData) : [],
};
}
}

@ -0,0 +1,75 @@
import {FilesTabSaveState, FilesTabState} from "./FilesTabState";
import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
import {TabSaveState, TabState} from "./TabState";
import {StateServices} from "./StateServices";
import {ImportTabSaveState, ImportTabState} from "./ImportTabState";
export class AppState {
public tabs = new BehaviorSubject<TabState[]>([]);
public selectedTab = new BehaviorSubject<number | undefined>(undefined);
public repoName: string | undefined;
private tabIdCounter = 0;
private readonly services: StateServices;
constructor(services: StateServices) {
this.services = services;
}
public static deserializeJson(stateString: string, services: StateServices): AppState {
let state = JSON.parse(stateString);
let appState = new AppState(services);
const tabs = state.tabs.map((saveState: TabSaveState) => {
let tab;
if (saveState.category === TabCategory.Files) {
tab = new FilesTabState(saveState.uuid, services);
tab.restoreSaveState(saveState as FilesTabSaveState);
} else if (saveState.category === TabCategory.Import) {
tab = new ImportTabState(saveState.uuid, services);
tab.restoreSaveState(saveState as ImportTabSaveState);
}
return tab;
});
appState.tabs.next(tabs);
appState.tabIdCounter = state.tabIdCounter;
appState.selectedTab.next(state.selectedTab);
appState.repoName = state.repoName;
return appState;
}
public addTab(category: TabCategory): TabState {
let state;
if (category == TabCategory.Files) {
state = new FilesTabState(this.tabIdCounter++, this.services);
} else {
state = new ImportTabState(this.tabIdCounter++, this.services);
}
this.tabs.next([...this.tabs.value, state]);
return state;
}
public async closeTab(uuid: number) {
const index = this.tabs.value.findIndex(t => t.uuid === uuid);
const tabs = this.tabs.value;
tabs.splice(index, 1);
this.tabs.next(tabs);
}
public serializeJson(): string {
const tabDTOs = this.tabs.value.map(tab => tab.toSaveState());
return JSON.stringify({
repoName: this.repoName,
tabs: tabDTOs,
tabIdCounter: this.tabIdCounter,
selectedTab: this.selectedTab.value,
});
}
}

@ -0,0 +1,97 @@
import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
import {File} from "../../../api/models/File";
import {SortKey} from "../../../api/models/SortKey";
import {debounceTime} from "rxjs/operators";
import {mapNew} from "../../../api/models/adaptors";
import {SearchFilters} from "../../../api/models/SearchFilters";
import {SortingPreset} from "../../../api/models/SortingPreset";
import {TabSaveState, TabState} from "./TabState";
import {StateServices} from "./StateServices";
import {FileBasicData, FilterExpression} from "../../../api/api-types/files";
import {SortingPresetData} from "../../../api/api-types/presets";
import {SaveState} from "./SaveState";
export type FilesTabSaveState = {
mode: "gallery" | "grid",
selectedCd: string | undefined,
files: FileBasicData[],
filters: FilterExpression[],
sortingPreset: SortingPresetData,
} & TabSaveState;
export class FilesTabState extends TabState implements SaveState<FilesTabSaveState> {
public mode = new BehaviorSubject<"grid" | "gallery">("grid");
public selectedCD = new BehaviorSubject<string | undefined>(undefined);
public loading = new BehaviorSubject<boolean>(false);
public files = new BehaviorSubject<File[]>([]);
public filters = new BehaviorSubject<SearchFilters>(new SearchFilters([]));
public sortingPreset = new BehaviorSubject<SortingPreset>(SortingPreset.fromValues(
-1,
[SortKey.fromValues(
"FileImportedTime",
"Ascending",
undefined
)]
));
constructor(
uuid: number,
services: StateServices,
) {
super(uuid, TabCategory.Files, services);
this.subscribe();
}
public restoreSaveState(
state: FilesTabSaveState,
) {
super.restoreSaveState(state);
this.filters = new BehaviorSubject(new SearchFilters(state.filters ?? []));
this.sortingPreset = new BehaviorSubject(new SortingPreset(state.sortingPreset));
this.mode = new BehaviorSubject(state.mode ?? "grid");
this.selectedCD = new BehaviorSubject(state.selectedCd);
this.files = new BehaviorSubject((state.files ?? []).map(mapNew(File)));
this.subscribe();
}
public async findFiles() {
this.loading.next(true);
const files = await this.services.fileService.findFiles(
this.filters.value,
this.sortingPreset.value.sortKeys
);
this.files.next(files);
this.loading.next(false);
}
public setTagFilters(filters: SearchFilters) {
this.filters.next(filters);
}
public setSortingPreset(preset: SortingPreset) {
this.sortingPreset.next(preset);
}
public toSaveState(): FilesTabSaveState {
return {
uuid: this.uuid,
category: this.category,
filters: this.filters.value.getFilters(),
sortingPreset: this.sortingPreset.value.rawData,
mode: this.mode.value,
selectedCd: this.selectedCD.value,
files: this.category === TabCategory.Import ? this.files.value.map(
f => f.rawData) : [],
};
}
private subscribe() {
this.filters.pipe(debounceTime(500))
.subscribe(async () => await this.findFiles());
this.sortingPreset.pipe(debounceTime(100))
.subscribe(async () => await this.findFiles());
}
}

@ -0,0 +1,44 @@
import {TabSaveState, TabState} from "./TabState";
import {SaveState} from "./SaveState";
import {StateServices} from "./StateServices";
import {TabCategory} from "./TabCategory";
import {BehaviorSubject} from "rxjs";
import {File} from "../../../api/models/File";
import {FileBasicData} from "../../../api/api-types/files";
import {mapNew} from "../../../api/models/adaptors";
export type ImportTabSaveState = {
selectedCd: string | undefined,
mode: "grid" | "gallery",
files: FileBasicData[],
} & TabSaveState;
export class ImportTabState extends TabState implements SaveState<ImportTabSaveState> {
public mode = new BehaviorSubject<"grid" | "gallery">("grid");
public selectedCD = new BehaviorSubject<string | undefined>(undefined);
public files = new BehaviorSubject<File[]>([]);
constructor(uuid: number, services: StateServices) {
super(uuid, TabCategory.Import, services);
}
public restoreSaveState(state: ImportTabSaveState) {
super.restoreSaveState(state);
this.selectedCD = new BehaviorSubject<string | undefined>(state.selectedCd);
this.files = new BehaviorSubject<File[]>(state.files.map(mapNew(File)));
this.mode = new BehaviorSubject<"grid" | "gallery">(state.mode);
return self;
}
public toSaveState(): ImportTabSaveState {
return {
uuid: this.uuid,
category: this.category,
selectedCd: this.selectedCD.value,
files: this.files.value.map(f => f.rawData),
mode: this.mode.value
};
}
}

@ -0,0 +1,5 @@
export interface SaveState<State> {
restoreSaveState(state: State): void;
toSaveState(): State;
}

@ -0,0 +1,6 @@
import {FileService} from "../../services/file/file.service";
export class StateServices {
constructor(public fileService: FileService) {
}
}

@ -0,0 +1,49 @@
import {TabCategory} from "./TabCategory";
import {StateServices} from "./StateServices";
import {SaveState} from "./SaveState";
import {FilesTabState} from "./FilesTabState";
import {ImportTabState} from "./ImportTabState";
export type TabSaveState = {
uuid: number,
category: TabCategory,
}
export class TabState implements SaveState<TabSaveState> {
constructor(
public uuid: number,
public category: TabCategory,
protected services: StateServices
) {
}
public toSaveState(): TabSaveState {
return {
uuid: this.uuid,
category: this.category
};
}
public restoreSaveState(state: TabSaveState): void {
this.uuid = state.uuid;
this.category = state.category;
}
/**
* Converts the state into a files tab state
* Warning: This should only be called when it's guaranteed that this tab is a files tab
* @returns {ImportTabState}
*/
public filesTab(): FilesTabState {
return this as unknown as FilesTabState;
}
/**
* Converts the state into an import tab state
* Warning: This should only be called when it's guaranteed that this tab is an import tab
* @returns {ImportTabState}
*/
public importTab(): ImportTabState {
return this as unknown as ImportTabState;
}
}

@ -1,11 +1,15 @@
import {Injectable} from "@angular/core";
import {BehaviorSubject, Subscription} from "rxjs";
import {AppState} from "../../models/AppState";
import {AppState} from "../../models/state/AppState";
import {FileService} from "../file/file.service";
import {RepositoryService} from "../repository/repository.service";
import {TabState} from "../../models/TabState";
import {FilesTabState} from "../../models/state/FilesTabState";
import {debounceTime} from "rxjs/operators";
import {MediarepoApi} from "../../../api/Api";
import {StateServices} from "../../models/state/StateServices";
import {ImportTabState} from "../../models/state/ImportTabState";
import {TabState} from "../../models/state/TabState";
import {TabCategory} from "../../models/state/TabCategory";
@Injectable({
providedIn: "root"
@ -19,12 +23,12 @@ export class StateService {
private stateChange = new BehaviorSubject<void>(undefined);
constructor(private fileService: FileService, private repoService: RepositoryService) {
this.state = new BehaviorSubject(new AppState(fileService));
this.state = new BehaviorSubject(new AppState(this.getServices()));
this.repoService.selectedRepository.subscribe(async (repo) => {
if (repo && (!this.state.value.repoName || this.state.value.repoName !== repo.name)) {
await this.loadState();
} else {
const state = new AppState(this.fileService);
const state = new AppState(this.getServices());
this.subscribeToState(state);
this.state.next(state);
}
@ -43,13 +47,13 @@ export class StateService {
if (stateString) {
try {
state = AppState.deserializeJson(stateString, this.fileService);
state = AppState.deserializeJson(stateString, this.getServices());
} catch (err) {
console.error("could not deserialize malformed state: ", err);
state = new AppState(this.fileService);
state = new AppState(this.getServices());
}
} else {
state = new AppState(this.fileService);
state = new AppState(this.getServices());
}
let selectedRepo = this.repoService.selectedRepository.value;
if (selectedRepo) {
@ -78,14 +82,34 @@ export class StateService {
}
private subscribeToTab(tab: TabState) {
if (tab.category === TabCategory.Files) {
this.subscribeToFilesTab(tab as FilesTabState);
} else if (tab.category === TabCategory.Import) {
this.subscribeToImportTab(tab as ImportTabState);
}
}
private subscribeToImportTab(tab: ImportTabState) {
this.tabSubscriptions.push(tab.mode
.subscribe(() => this.stateChange.next()));
}
private subscribeToFilesTab(tab: FilesTabState) {
this.tabSubscriptions.push(tab.filters
.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(tab.files
.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(tab.sortingPreset
.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(
tab.selectedCD.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(
tab.mode.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(tab.files.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(tab.mode
.subscribe(() => this.stateChange.next()));
}
private getServices(): StateServices {
return new StateServices(this.fileService);
}
}

Loading…
Cancel
Save