Refactor and update to new API types

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent ef0ba80917
commit 66fe9432fd

@ -1,52 +1,66 @@
{ {
"root": true, "root": true,
"ignorePatterns": [ "ignorePatterns": [
"projects/**/*" "projects/**/*"
], ],
"overrides": [ "overrides": [
{ {
"files": [ "files": [
"*.ts" "*.ts"
], ],
"parserOptions": { "parserOptions": {
"project": [ "project": [
"tsconfig.json" "tsconfig.json"
], ],
"createDefaultProgram": true "createDefaultProgram": true
}, },
"extends": [ "extends": [
"plugin:@angular-eslint/recommended", "plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates" "plugin:@angular-eslint/template/process-inline-templates"
], ],
"rules": { "rules": {
"@angular-eslint/directive-selector": [ "@angular-eslint/directive-selector": [
"error", "error",
{ {
"type": "attribute", "type": "attribute",
"prefix": "app", "prefix": "app",
"style": "camelCase" "style": "camelCase"
} }
], ],
"@angular-eslint/component-selector": [ "@angular-eslint/component-selector": [
"error", "error",
{ {
"type": "element", "type": "element",
"prefix": "app", "prefix": "app",
"style": "kebab-case" "style": "kebab-case"
} }
], ],
"quotes": ["warn", "double", {"avoidEscape": true}], "quotes": [
"indent": ["error", 4, {"SwitchCase": 1}] "warn",
} "double",
}, {
{ "avoidEscape": true
"files": [ }
"*.html" ],
], "indent": [
"extends": [ "error",
"plugin:@angular-eslint/template/recommended" 4,
], {
"rules": {} "SwitchCase": 1
} }
] ],
"no-unused-expressions": "warn",
"semi": "error"
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
} }

@ -1489,8 +1489,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.20.0" version = "0.24.1"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=0c897acfd959c776fc10bd8fabdd2eb22b437be3#0c897acfd959c776fc10bd8fabdd2eb22b437be3" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=91d8182548bfdb19f2de9afd8c29d5c8ebd48993#91d8182548bfdb19f2de9afd8c29d5c8ebd48993"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bromine", "bromine",

@ -25,7 +25,7 @@ features = [ "env-filter" ]
[dependencies.mediarepo-api] [dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git" git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "0c897acfd959c776fc10bd8fabdd2eb22b437be3" rev = "91d8182548bfdb19f2de9afd8c29d5c8ebd48993"
features = [ "tauri-plugin" ] features = [ "tauri-plugin" ]
[features] [features]

@ -0,0 +1,163 @@
import {FileBasicData, FileMetadata, FileOsMetadata} from "./api-types/files";
import {invoke} from "@tauri-apps/api/tauri";
import {ApiFunction} from "./api-types/functions";
import {
AddLocalFileREquest,
AddRepositoryRequest,
ChangeFileTagsRequest,
CheckDaemonRunningRequest,
CheckLocalRepositoryExistsRequest,
CreateTagsRequest,
DeleteRepositoryRequest,
DeleteThumbnailsRequest,
FindFilesRequest,
GetFileMetadataRequest,
GetSizeRequest,
GetTagsForFilesRequest,
InitRepositoryRequest,
ReadFileRequest,
RemoveRepositoryRequest,
ResolvePathsToFilesRequest,
SaveFileRequest,
SelectRepositoryRequest,
SetFrontendStateRequest,
StartDaemonRequest,
UpdateFileNameRequest
} from "./api-types/requests";
import {
RepositoryData,
RepositoryMetadata,
SizeMetadata
} from "./api-types/repo";
import {NamespaceData, TagData} from "./api-types/tags";
export class MediarepApi {
public static async hasExecutable(): Promise<boolean> {
return this.invokePlugin(ApiFunction.HasExecutable);
}
public static async getRepositories(): Promise<RepositoryData[]> {
return this.invokePlugin(ApiFunction.GetRepositories);
}
public static async selectRepository(request: SelectRepositoryRequest): Promise<void> {
return this.invokePlugin(ApiFunction.SelectRepository, request);
}
public static async disconnectRepository(): Promise<void> {
return this.invokePlugin(ApiFunction.DisconnectRepository);
}
public static async closeLocalRepository(): Promise<void> {
return this.invokePlugin(ApiFunction.CloseLocalRepository);
}
public static async addRepository(request: AddRepositoryRequest): Promise<RepositoryData[]> {
return this.invokePlugin(ApiFunction.AddRepository, request);
}
public static async checkDaemonRunning(request: CheckDaemonRunningRequest): Promise<boolean> {
return this.invokePlugin(ApiFunction.CheckDaemonRunning, request);
}
public static async checkLocalRepositoryExists(request: CheckLocalRepositoryExistsRequest): Promise<boolean> {
return this.invokePlugin(ApiFunction.CheckLocalRepositoryExists, request);
}
public static async removeRepository(request: RemoveRepositoryRequest): Promise<void> {
return this.invokePlugin(ApiFunction.RemoveRepository, request);
}
public static async deleteRepository(request: DeleteRepositoryRequest): Promise<void> {
return this.invokePlugin(ApiFunction.DeleteRepository, request);
}
public static async startDaemon(request: StartDaemonRequest): Promise<void> {
return this.invokePlugin(ApiFunction.StartDaemon, request);
}
public static async initRepository(request: InitRepositoryRequest): Promise<void> {
return this.invokePlugin(ApiFunction.InitRepository, request);
}
public static async getRepositoryMetadata(): Promise<RepositoryMetadata> {
return this.invokePlugin(ApiFunction.GetRepoMetadata);
}
public static async getSize(request: GetSizeRequest): Promise<SizeMetadata> {
return this.invokePlugin(ApiFunction.GetSize, request);
}
public static async getActiveRepository(): Promise<RepositoryData | undefined> {
return this.invokePlugin(ApiFunction.GetActiveRepository);
}
public static async getAllFiles(): Promise<FileBasicData[]> {
return this.invokePlugin(ApiFunction.GetAllFiles);
}
public static async findFiles(request: FindFilesRequest): Promise<FileBasicData[]> {
return this.invokePlugin(ApiFunction.FindFiles, request);
}
public static async getFileMetadata(request: GetFileMetadataRequest): Promise<FileMetadata> {
return this.invokePlugin(ApiFunction.GetFileMetadata, request);
}
public static async updateFileName(request: UpdateFileNameRequest): Promise<FileMetadata> {
return this.invokePlugin(ApiFunction.UpdateFileName, request);
}
public static async saveFileLocally(request: SaveFileRequest): Promise<void> {
return this.invokePlugin(ApiFunction.SaveFileLocally, request);
}
public static async deleteThumbnails(request: DeleteThumbnailsRequest): Promise<void> {
return this.invokePlugin(ApiFunction.DeleteThumbnails, request);
}
public static async readFile(request: ReadFileRequest): Promise<number[]> {
return this.invokePlugin(ApiFunction.ReadFile, request);
}
public static async getAllTags(): Promise<TagData[]> {
return this.invokePlugin(ApiFunction.GetAllTags);
}
public static async getAllNamespaces(): Promise<NamespaceData[]> {
return this.invokePlugin(ApiFunction.GetAllNamespace);
}
public static async getTagsForFiles(request: GetTagsForFilesRequest): Promise<TagData[]> {
return this.invokePlugin(ApiFunction.GetTagsForFiles, request);
}
public static async createTags(request: CreateTagsRequest): Promise<TagData[]> {
return this.invokePlugin(ApiFunction.CreateTags, request);
}
public static async changeFileTags(request: ChangeFileTagsRequest): Promise<TagData[]> {
return this.invokePlugin(ApiFunction.ChangeFileTags, request);
}
public static async resolvePathsToFiles(request: ResolvePathsToFilesRequest): Promise<FileOsMetadata[]> {
return this.invokePlugin(ApiFunction.ResolvePathsToFiles, request);
}
public static async addLocalFile(request: AddLocalFileREquest): Promise<FileBasicData> {
return this.invokePlugin(ApiFunction.AddLocalFile, request);
}
public static async getFrontendState(): Promise<string> {
return this.invokePlugin(ApiFunction.GetFrontendState);
}
public static async setFrontendState(request: SetFrontendStateRequest): Promise<void> {
return this.invokePlugin(ApiFunction.SetFrontendState, request);
}
private static async invokePlugin<T>(fn: ApiFunction, args?: any): Promise<T> {
return invoke<T>(`plugin:mediarepo|${fn}`, args);
}
}

@ -0,0 +1,48 @@
export type FilterExpression =
{ OrExpression: TagQuery[] }
| { Query: TagQuery };
export type TagQuery = {
negate: boolean,
tag: string,
};
export type SortKey = { Namespace: SortNamespace }
| { FileName: SortDirection }
| { FileSize: SortDirection }
| { FileImportedTime: SortDirection }
| { FileChangeTime: SortDirection }
| { FileType: SortDirection };
export type SortNamespace = {
name: string,
direction: SortDirection,
}
export type SortDirection = "Ascending" | "Descending";
export type FileBasicData = {
id: number,
status: FileStatus,
cd: string,
mime_type: string,
};
export type FileStatus = "Imported" | "Archived" | "Deleted";
export type FileMetadata = {
file_id: number,
name?: string,
comment?: string,
creation_time: Date,
change_time: Date,
import_time: Date,
};
export type FileOsMetadata = {
name: string,
path: string,
mime_type: string,
created_at: Date,
modified_at: Date,
};

@ -0,0 +1,38 @@
export enum ApiFunction {
// repository
HasExecutable = "has_executable",
GetRepositories = "get_repositories",
SelectRepository = "select_repository",
DisconnectRepository = "disconnect_repository",
CloseLocalRepository = "close_local_repository",
AddRepository = "add_repository",
CheckDaemonRunning = "check_daemon_running",
CheckLocalRepositoryExists = "check_local_repository_exists",
RemoveRepository = "remove_repository",
DeleteRepository = "delete_repository",
StartDaemon = "start_daemon",
InitRepository = "init_repository",
GetRepoMetadata = "get_repo_metadata",
GetSize = "get_size",
GetActiveRepository = "get_active_repository",
// files
GetAllFiles = "get_all_files",
FindFiles = "find_files",
GetFileMetadata = "get_file_metadata",
UpdateFileName = "update_file_name",
SaveFileLocally = "save_file_locally",
DeleteThumbnails = "delete_thumbnails",
ReadFile = "read_file",
// tags
GetAllTags = "get_all_tags",
GetAllNamespace = "get_all_namespaces",
GetTagsForFiles = "get_tags_for_files",
CreateTags = "create_tags",
ChangeFileTags = "change_file_tags",
// import
ResolvePathsToFiles = "resolve_paths_to_files",
AddLocalFile = "add_local_file",
// state
GetFrontendState = "get_frontend_state",
SetFrontendState = "set_frontend_state",
}

@ -0,0 +1,22 @@
export type RepositoryMetadata = {
version: string,
file_count: number,
tag_count: number,
namespace_count: number,
mapping_count: number,
hash_count: number,
};
export type SizeMetadata = {
size_type: SizeType,
size: number,
};
export type SizeType = "Total" | "FileFolder" | "ThumbFolder" | "DatabaseFile";
export type RepositoryData = {
name: string,
address?: string,
path?: string,
local: boolean,
}

@ -0,0 +1,94 @@
import {FileOsMetadata, FilterExpression, SortKey} from "./files";
import {RepositoryData, SizeType} from "./repo";
type NameIdentifierRequest = {
name: string
};
type IdIdentifierRequest = {
id: number
};
type RepoPathIdentifier = {
repoPath: string;
}
export type SelectRepositoryRequest = NameIdentifierRequest;
export type AddRepositoryRequest = RepositoryData;
export type CheckLocalRepositoryExistsRequest = {
path: string
};
export type RemoveRepositoryRequest = NameIdentifierRequest;
export type DeleteRepositoryRequest = NameIdentifierRequest;
export type CheckDaemonRunningRequest = {
address: string
};
export type StartDaemonRequest = RepoPathIdentifier;
export type InitRepositoryRequest = RepoPathIdentifier;
export type GetSizeRequest = {
sizeType: SizeType
};
export type FindFilesRequest = {
filters: FilterExpression[],
sortBy: SortKey[]
};
export type UpdateFileNameRequest = {
id: number,
name: string,
};
export type SaveFileRequest = {
id: number,
path: string,
};
export type DeleteThumbnailsRequest = IdIdentifierRequest;
export type ReadFileRequest = {
hash: string,
mimeType: string,
};
export type GetFileMetadataRequest = IdIdentifierRequest;
export type GetTagsForFilesRequest = {
cds: string[]
};
export type CreateTagsRequest = {
tags: string[]
};
export type ChangeFileTagsRequest = {
id: number,
addedTags: number[],
removedTags: number[],
};
export type ResolvePathsToFilesRequest = {
paths: string[],
};
export type AddLocalFileREquest = {
metadata: FileOsMetadata,
options: AddFileOptions,
}
type AddFileOptions = {
read_tags_from_txt: boolean,
delete_after_import: boolean,
};
export type SetFrontendStateRequest = {
state: string
};

@ -0,0 +1,10 @@
export type TagData = {
id: number,
namespace?: string,
name: string,
};
export type NamespaceData = {
id: number,
name: string,
};

@ -0,0 +1,32 @@
import {FileBasicData, FileStatus} from "../api-types/files";
export class File {
constructor(
private basicData: FileBasicData,
) {
}
public get rawData(): FileBasicData {
return this.basicData;
}
public get id(): number {
return this.basicData.id;
}
public get cd(): string {
return this.basicData.cd;
}
public get status(): FileStatus {
return this.basicData.status;
}
public get mimeType(): string {
return this.basicData.mime_type;
}
public set status(value: FileStatus) {
this.basicData.status = value;
}
}

@ -0,0 +1,14 @@
import {NamespaceData} from "../api-types/tags";
export class Namespace {
constructor(private data: NamespaceData) {
}
public get id(): number {
return this.data.id;
}
public get name(): string {
return this.data.name;
}
}

@ -0,0 +1,28 @@
import {RepositoryData} from "../api-types/repo";
export class Repository {
constructor(
private repoData: RepositoryData,
) {
}
public get name(): string {
return this.repoData.name;
}
public get address(): string | undefined {
return this.repoData.address;
}
public get path(): string | undefined {
return this.repoData.path;
}
public get local(): boolean {
return this.repoData.local;
}
public update(data: {name?: string, address?: string, path?: string, local?: boolean}) {
this.repoData = Object.assign(this.repoData, data);
}
}

@ -0,0 +1,30 @@
import {TagData} from "../api-types/tags";
export class Tag {
private normalizedTag?: string = undefined;
constructor(
private tagData: TagData,
) {
}
public get id(): number {
return this.tagData.id;
}
public get name(): string {
return this.tagData.name;
}
public get namespace(): string | undefined {
return this.tagData.namespace;
}
public getNormalizedOutput(): string {
if (!this.normalizedTag) {
this.normalizedTag = this.namespace ? this.namespace + ":" + this.name : this.name;
}
return this.normalizedTag;
}
}

@ -0,0 +1,11 @@
export function mapOptional<I, O>(mapFn: (value: I) => O): (value: I | undefined) => O | undefined {
return (value: I | undefined) => value ? mapFn(value) : undefined;
}
export function mapMany<I, O>(mapFn: (value: I) => O): (value: I[]) => O[] {
return (value: I[]) => value.map(mapFn);
}
export function mapNew<T, V>(classType: new (value: V) => T): (value: V) => T {
return (value: V) => new classType(value);
}

@ -1,7 +1,7 @@
<div id="content"> <div id="content">
<mat-tab-group #tabGroup (selectedTabChange)="this.onTabSelectionChange($event)" class="main-tab-group" <mat-tab-group #tabGroup (selectedTabChange)="this.onTabSelectionChange($event)" class="main-tab-group"
animationDuration="0"> animationDuration="0">
<mat-tab [label]="this.selectedRepository? 'Repository' : 'Repositories'"> <mat-tab [label]="this.selectedRepository? 'RepositoryData' : 'Repositories'">
<app-repositories-tab></app-repositories-tab> <app-repositories-tab></app-repositories-tab>
</mat-tab> </mat-tab>
<mat-tab *ngFor="let tab of tabs"> <mat-tab *ngFor="let tab of tabs">

@ -34,10 +34,11 @@ mat-tab-group {
float: right; float: right;
position: absolute; position: absolute;
right: 0; right: 0;
top: 4px; top: 0;
height: 100%;
ng-icon { ng-icon {
font-size: 1.5em; font-size: 1.5em;
margin-top: calc(-50% + 0.2em); margin-top: calc(-50%);
--ng-icon__size: 0.4em; --ng-icon__size: 0.4em;
} }
} }

@ -1,5 +1,5 @@
import {Component, ViewChild} from "@angular/core"; import {Component, ViewChild} from "@angular/core";
import {Repository} from "../../models/Repository"; import {Repository} from "../../../api/models/Repository";
import {RepositoryService} from "../../services/repository/repository.service"; import {RepositoryService} from "../../services/repository/repository.service";
import {MatTabChangeEvent, MatTabGroup} from "@angular/material/tabs"; import {MatTabChangeEvent, MatTabGroup} from "@angular/material/tabs";
import {TagService} from "../../services/tag/tag.service"; import {TagService} from "../../services/tag/tag.service";
@ -60,7 +60,7 @@ export class CoreComponent {
this.addTab(); this.addTab();
} }
}); });
}) });
} }
async loadRepoData() { async loadRepoData() {

@ -46,7 +46,9 @@ import {
RepositoryModule RepositoryModule
} from "../shared/repository/repository/repository.module"; } from "../shared/repository/repository/repository.module";
import {MatToolbarModule} from "@angular/material/toolbar"; import {MatToolbarModule} from "@angular/material/toolbar";
import { RepositoryDetailsViewComponent } from './repositories-tab/repository-details-view/repository-details-view.component'; import {
RepositoryDetailsViewComponent
} from "./repositories-tab/repository-details-view/repository-details-view.component";
@NgModule({ @NgModule({

@ -8,9 +8,9 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../../api/models/Tag";
import {TagService} from "../../../../services/tag/tag.service"; import {TagService} from "../../../../services/tag/tag.service";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
import { import {
FileSearchComponent FileSearchComponent
} from "../../../shared/sidebar/file-search/file-search.component"; } from "../../../shared/sidebar/file-search/file-search.component";
@ -53,7 +53,7 @@ export class FilesTabSidebarComponent implements OnInit, OnChanges {
this.state.files.subscribe(async (files) => { this.state.files.subscribe(async (files) => {
this.files = files; this.files = files;
await this.onDisplayedFilesChange(); await this.onDisplayedFilesChange();
}) });
if (this.fileSearch) { if (this.fileSearch) {
await this.fileSearch.searchForFiles(); await this.fileSearch.searchForFiles();
} }
@ -76,13 +76,13 @@ export class FilesTabSidebarComponent implements OnInit, OnChanges {
async loadTagsForDisplayedFiles() { async loadTagsForDisplayedFiles() {
this.tagsOfFiles = await this.tagService.getTagsForFiles( this.tagsOfFiles = await this.tagService.getTagsForFiles(
this.files.map(f => f.hash)); this.files.map(f => f.cd));
this.showAllTagsFallback(); this.showAllTagsFallback();
} }
async showFileDetails(files: File[]) { async showFileDetails(files: File[]) {
this.tagsOfSelection = await this.tagService.getTagsForFiles( this.tagsOfSelection = await this.tagService.getTagsForFiles(
files.map(f => f.hash)) files.map(f => f.cd));
this.tagsOfSelection = this.tagsOfSelection.sort( this.tagsOfSelection = this.tagsOfSelection.sort(
(a, b) => a.getNormalizedOutput() (a, b) => a.getNormalizedOutput()
.localeCompare(b.getNormalizedOutput())); .localeCompare(b.getNormalizedOutput()));

@ -1,5 +1,5 @@
import {Component, Input, OnInit} from "@angular/core"; import {Component, Input, OnInit} from "@angular/core";
import {File} from "../../../models/File"; import {File} from "../../../../api/models/File";
import {TabState} from "../../../models/TabState"; import {TabState} from "../../../models/TabState";
@Component({ @Component({
@ -26,17 +26,17 @@ export class FilesTabComponent implements OnInit {
async onFileSelect(files: File[]) { async onFileSelect(files: File[]) {
this.selectedFiles = files; this.selectedFiles = files;
if (files.length === 1) { if (files.length === 1) {
this.state.selectedFileHash.next(files[0].hash); this.state.selectedCD.next(files[0].cd);
} else { } else {
this.state.selectedFileHash.next(undefined); this.state.selectedCD.next(undefined);
} }
} }
public getStateSelectedFile(): File | undefined { public getStateSelectedFile(): File | undefined {
const hash = this.state.selectedFileHash.value; const hash = this.state.selectedCD.value;
if (hash) { if (hash) {
return this.files.find(f => f.hash === hash); return this.files.find(f => f.cd === hash);
} else { } else {
return undefined; return undefined;
} }
@ -45,7 +45,7 @@ export class FilesTabComponent implements OnInit {
public async onKeydown(event: KeyboardEvent) { public async onKeydown(event: KeyboardEvent) {
switch (event.key) { switch (event.key) {
case "F5": case "F5":
await this.state.findFiles() await this.state.findFiles();
break; break;
} }
} }

@ -1,5 +1,5 @@
import {Component, EventEmitter, Input, Output} from "@angular/core"; import {Component, EventEmitter, Input, Output} from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
@Component({ @Component({
selector: "app-import-tab-sidebar", selector: "app-import-tab-sidebar",

@ -1,5 +1,5 @@
import {Component, Input, OnInit} from "@angular/core"; import {Component, Input, OnInit} from "@angular/core";
import {File} from "../../../models/File"; import {File} from "../../../../api/models/File";
import {TabState} from "../../../models/TabState"; import {TabState} from "../../../models/TabState";
@Component({ @Component({
@ -47,17 +47,17 @@ export class ImportTabComponent implements OnInit {
public onFileSelect(files: File[]) { public onFileSelect(files: File[]) {
this.selectedFiles = files; this.selectedFiles = files;
if (files.length === 1) { if (files.length === 1) {
this.state.selectedFileHash.next(files[0].hash); this.state.selectedCD.next(files[0].cd);
} else { } else {
this.state.selectedFileHash.next(undefined); this.state.selectedCD.next(undefined);
} }
} }
public getSelectedFileFromState(): File | undefined { public getSelectedFileFromState(): File | undefined {
const selectedHash = this.state.selectedFileHash.value; const selectedHash = this.state.selectedCD.value;
if (selectedHash && this.files) { if (selectedHash && this.files) {
return this.files.find(f => f.hash === selectedHash); return this.files.find(f => f.cd === selectedHash);
} else { } else {
return undefined; return undefined;
} }

@ -20,6 +20,6 @@ export class DownloadDaemonDialogComponent {
} }
closeDialog(result: boolean) { closeDialog(result: boolean) {
this.dialogRef.close(result) this.dialogRef.close(result);
} }
} }

@ -1,5 +1,5 @@
import {AfterViewInit, Component, OnInit} from "@angular/core"; import {AfterViewInit, Component, OnInit} from "@angular/core";
import {Repository} from "../../../models/Repository"; import {Repository} from "../../../../api/models/Repository";
import { import {
RepositoryService RepositoryService
} from "../../../services/repository/repository.service"; } from "../../../services/repository/repository.service";

@ -1,5 +1,5 @@
import {Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core"; import {Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {Repository} from "../../../../models/Repository"; import {Repository} from "../../../../../api/models/Repository";
import { import {
RepositoryService RepositoryService
} from "../../../../services/repository/repository.service"; } from "../../../../services/repository/repository.service";
@ -52,7 +52,7 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
} }
public isSelectedRepository(): boolean { public isSelectedRepository(): boolean {
return this.repoService.selectedRepository.getValue()?.name === this.repository.name return this.repoService.selectedRepository.getValue()?.name === this.repository.name;
} }
public async removeRepository() { public async removeRepository() {
@ -124,7 +124,7 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
await this.repoService.startDaemon(this.repository.path!); await this.repoService.startDaemon(this.repository.path!);
this.daemonRunning = true; this.daemonRunning = true;
await new Promise((res, _) => { await new Promise((res, _) => {
setTimeout(res, 2000) // wait for the daemon to start setTimeout(res, 2000); // wait for the daemon to start
}); });
} }
await this.selectRepository(); await this.selectRepository();
@ -156,6 +156,6 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
data: { data: {
repository: this.repository repository: this.repository
} }
}) });
} }
} }

@ -1,25 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import {ComponentFixture, TestBed} from "@angular/core/testing";
import { RepositoryDetailsViewComponent } from './repository-details-view.component'; import {
RepositoryDetailsViewComponent
} from "./repository-details-view.component";
describe('RepositoryDetailsViewComponent', () => { describe("RepositoryDetailsViewComponent", () => {
let component: RepositoryDetailsViewComponent; let component: RepositoryDetailsViewComponent;
let fixture: ComponentFixture<RepositoryDetailsViewComponent>; let fixture: ComponentFixture<RepositoryDetailsViewComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ RepositoryDetailsViewComponent ] declarations: [ RepositoryDetailsViewComponent ]
}) })
.compileComponents(); .compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(RepositoryDetailsViewComponent); fixture = TestBed.createComponent(RepositoryDetailsViewComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it("should create", () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

@ -6,13 +6,12 @@ import {
OnInit, OnInit,
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import {Repository} from "../../../../models/Repository"; import {Repository} from "../../../../../api/models/Repository";
import { import {
RepositoryService RepositoryService
} from "../../../../services/repository/repository.service"; } from "../../../../services/repository/repository.service";
import {RepositoryMetadata} from "../../../../models/RepositoryMetadata"; import {RepositoryMetadata} from "../../../../models/RepositoryMetadata";
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {SizeType} from "../../../../models/SizeMetadata";
@Component({ @Component({
selector: "app-repository-details-view", selector: "app-repository-details-view",
@ -57,13 +56,13 @@ export class RepositoryDetailsViewComponent implements OnInit, OnChanges, OnDest
} }
public async getSizes() { public async getSizes() {
const totalSize = await this.repoService.getSize(SizeType.Total) const totalSize = await this.repoService.getSize("Total");
this.totalSize.next(this.formatByteSize(totalSize.size)); this.totalSize.next(this.formatByteSize(totalSize.size));
const fileSize = await this.repoService.getSize(SizeType.FileFolder); const fileSize = await this.repoService.getSize("FileFolder");
this.fileFolderSize.next(this.formatByteSize(fileSize.size)); this.fileFolderSize.next(this.formatByteSize(fileSize.size));
const thumbSize = await this.repoService.getSize(SizeType.ThumbFolder); const thumbSize = await this.repoService.getSize("ThumbFolder");
this.thumbFolderSize.next(this.formatByteSize(thumbSize.size)); this.thumbFolderSize.next(this.formatByteSize(thumbSize.size));
const databaseSize = await this.repoService.getSize(SizeType.DatabaseFile); const databaseSize = await this.repoService.getSize("DatabaseFile");
this.databaseFileSize.next(this.formatByteSize(databaseSize.size)); this.databaseFileSize.next(this.formatByteSize(databaseSize.size));
} }
@ -82,7 +81,7 @@ export class RepositoryDetailsViewComponent implements OnInit, OnChanges, OnDest
} else if (size >= kib) { } else if (size >= kib) {
return (size / kib).toFixed(2) + " KiB"; return (size / kib).toFixed(2) + " KiB";
} else { } else {
return size + " B" return size + " B";
} }
} }

@ -15,7 +15,9 @@ import {MatMenuModule} from "@angular/material/menu";
import { import {
ContentAwareImageComponent ContentAwareImageComponent
} from "./content-aware-image/content-aware-image.component"; } from "./content-aware-image/content-aware-image.component";
import { InputReceiverDirective } from "./input-receiver/input-receiver.directive"; import {
InputReceiverDirective
} from "./input-receiver/input-receiver.directive";
import { import {
MetadataEntryComponent MetadataEntryComponent
} from "./metadata-entry/metadata-entry.component"; } from "./metadata-entry/metadata-entry.component";

@ -22,7 +22,7 @@ export class BusyIndicatorComponent {
} }
public wrapOperation<T>(operation: Function): T | undefined { public wrapOperation<T>(operation: Function): T | undefined {
this.setBusy(true) this.setBusy(true);
try { try {
const result = operation(); const result = operation();
this.setBusy(false); this.setBusy(false);
@ -35,7 +35,7 @@ export class BusyIndicatorComponent {
} }
public async wrapAsyncOperation<T>(operation: Function): Promise<T | undefined> { public async wrapAsyncOperation<T>(operation: Function): Promise<T | undefined> {
this.setBusy(true) this.setBusy(true);
try { try {
const result = await operation(); const result = await operation();
this.setBusy(false); this.setBusy(false);

@ -1,8 +1,8 @@
import { InputReceiverDirective } from './input-receiver.directive'; import {InputReceiverDirective} from "./input-receiver.directive";
describe('InputReceiverDirective', () => { describe("InputReceiverDirective", () => {
it('should create an instance', () => { it("should create an instance", () => {
const directive = new InputReceiverDirective(); const directive = new InputReceiverDirective();
expect(directive).toBeTruthy(); expect(directive).toBeTruthy();
}); });
}); });

@ -3,7 +3,7 @@
<app-audio-viewer *ngIf="getContentType() === 'audio' && this.blobUrl" [blobUrl]="this.blobUrl!"></app-audio-viewer> <app-audio-viewer *ngIf="getContentType() === 'audio' && this.blobUrl" [blobUrl]="this.blobUrl!"></app-audio-viewer>
<div *ngIf="getContentType() === 'other'" class="download-prompt"> <div *ngIf="getContentType() === 'other'" class="download-prompt">
<span>Unsupported content type <b>{{this.file.mime_type}}</b></span> <span>Unsupported content type <b>{{this.file.mimeType}}</b></span>
<button (click)="this.downloadContent()" color="primary" mat-flat-button>Download</button> <button (click)="this.downloadContent()" color="primary" mat-flat-button>Download</button>
</div> </div>
<app-busy-indicator></app-busy-indicator> <app-busy-indicator></app-busy-indicator>

@ -8,7 +8,7 @@ import {
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {SafeResourceUrl} from "@angular/platform-browser"; import {SafeResourceUrl} from "@angular/platform-browser";
import {File} from "../../../../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 { import {
@ -64,10 +64,7 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr
} }
public getContentType(): ContentType { public getContentType(): ContentType {
if (!this.file.mime_type) { let mimeParts = this.file.mimeType.split("/");
return "other";
}
let mimeParts = this.file.mime_type.split("/");
const type = mimeParts.shift() ?? "other"; const type = mimeParts.shift() ?? "other";
const subtype = mimeParts.shift() ?? "*"; const subtype = mimeParts.shift() ?? "*";
@ -84,7 +81,7 @@ export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestr
} }
public async downloadContent() { public async downloadContent() {
const path = await FileHelper.getFileDownloadLocation(this.file) const path = await FileHelper.getFileDownloadLocation(this.file);
if (path) { if (path) {
try { try {

@ -54,12 +54,12 @@ export class ImageViewerComponent implements OnChanges {
const delta = event.wheelDelta ?? event.detail; const delta = event.wheelDelta ?? event.detail;
if (delta > 0) { if (delta > 0) {
this.imageZoom += 0.2 this.imageZoom += 0.2;
if (this.imageZoom > 4) { if (this.imageZoom > 4) {
this.imageZoom = 4; this.imageZoom = 4;
} }
} else if (delta < 0) { } else if (delta < 0) {
this.imageZoom -= 0.2 this.imageZoom -= 0.2;
if (this.imageZoom < 0.5) { if (this.imageZoom < 0.5) {
this.imageZoom = 0.5; this.imageZoom = 0.5;
} }

@ -10,7 +10,7 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
import {Selectable} from "../../../../models/Selectable"; import {Selectable} from "../../../../models/Selectable";
import { import {
SchedulingService SchedulingService
@ -62,7 +62,7 @@ export class FileCardComponent implements OnInit, OnChanges, OnDestroy {
this.workId = this.schedulingService.addWork(LOADING_WORK_KEY, this.workId = this.schedulingService.addWork(LOADING_WORK_KEY,
async () => { async () => {
await this.schedulingService.delay(1); await this.schedulingService.delay(1);
this.loading = false this.loading = false;
}); });
} }
} }

@ -1,5 +1,5 @@
import {Component, ViewChild} from "@angular/core"; import {Component, ViewChild} from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
import { import {
ContextMenuComponent ContextMenuComponent
} from "../../app-common/context-menu/context-menu.component"; } from "../../app-common/context-menu/context-menu.component";
@ -30,11 +30,11 @@ export class FileContextMenuComponent {
} }
public async copyFileHash(): Promise<void> { public async copyFileHash(): Promise<void> {
await clipboard.writeText(this.file.hash); await clipboard.writeText(this.file.cd);
} }
public async exportFile(): Promise<void> { public async exportFile(): Promise<void> {
const path = await FileHelper.getFileDownloadLocation(this.file) const path = await FileHelper.getFileDownloadLocation(this.file);
if (path) { if (path) {
try { try {

@ -1,8 +1,8 @@
import { import {
AfterContentInit, AfterViewInit, AfterViewInit,
Component, ElementRef, Component,
ElementRef,
EventEmitter, EventEmitter,
HostListener,
Input, Input,
OnChanges, OnChanges,
OnInit, OnInit,
@ -10,7 +10,7 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../../models/File"; 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";
@ -48,7 +48,7 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
if (!this.selectedFile || this.files.indexOf( if (!this.selectedFile || this.files.indexOf(
this.selectedFile.data) < 0) { this.selectedFile.data) < 0) {
await this.onEntrySelect( await this.onEntrySelect(
this.getPreselectedEntry() ?? this.entries[0]) this.getPreselectedEntry() ?? this.entries[0]);
} }
} }
@ -59,15 +59,15 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
public async ngOnChanges(changes: SimpleChanges): Promise<void> { public async ngOnChanges(changes: SimpleChanges): Promise<void> {
if (changes["files"]) { if (changes["files"]) {
this.entries = this.files.map( this.entries = this.files.map(
f => new Selectable(f, f.hash == this.selectedFile?.data.hash)); f => new Selectable(f, f.id == this.selectedFile?.data.id));
const selectedIndex = this.files.findIndex( const selectedIndex = this.files.findIndex(
f => f.hash === this.selectedFile?.data.hash); f => f.id === this.selectedFile?.data.id);
if (!this.selectedFile || selectedIndex < 0) { if (!this.selectedFile || selectedIndex < 0) {
await this.onEntrySelect( await this.onEntrySelect(
this.getPreselectedEntry() ?? this.entries[0]) this.getPreselectedEntry() ?? this.entries[0]);
} else { } else {
await this.onEntrySelect(this.entries[selectedIndex]) await this.onEntrySelect(this.entries[selectedIndex]);
} }
} }
} }
@ -101,7 +101,7 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
async loadSelectedFile() { async loadSelectedFile() {
if (this.selectedFile) { if (this.selectedFile) {
this.fileContentUrl = this.fileService.buildContentUrl( this.fileContentUrl = this.fileService.buildContentUrl(
this.selectedFile.data) this.selectedFile.data);
} }
} }
@ -117,7 +117,7 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
await this.onEntrySelect(this.entries[index]); await this.onEntrySelect(this.entries[index]);
} else { } else {
await this.onEntrySelect(this.entries[0]) await this.onEntrySelect(this.entries[0]);
} }
} }
@ -133,7 +133,7 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
} }
await this.onEntrySelect(this.entries[index]); await this.onEntrySelect(this.entries[index]);
} else { } else {
await this.onEntrySelect(this.entries[0]) await this.onEntrySelect(this.entries[0]);
} }
} }

@ -1,9 +1,8 @@
import { import {
AfterContentInit, AfterViewInit, AfterViewInit,
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
HostListener,
Input, Input,
OnChanges, OnChanges,
OnInit, OnInit,
@ -11,7 +10,7 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../../models/File"; import {File} from "../../../../../../api/models/File";
import {FileCardComponent} from "../../file-card/file-card.component"; import {FileCardComponent} from "../../file-card/file-card.component";
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";
@ -73,7 +72,7 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
setSelectedFile(clickedEntry: Selectable<File>) { setSelectedFile(clickedEntry: Selectable<File>) {
if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) { if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) {
this.selectedEntries.forEach(entry => { this.selectedEntries.forEach(entry => {
if (entry !== clickedEntry) entry.selected = false if (entry !== clickedEntry) entry.selected = false;
}); });
this.selectedEntries = []; this.selectedEntries = [];
} }
@ -199,11 +198,11 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
selectedIndex --; selectedIndex --;
break; break;
case "right": case "right":
selectedIndex++ selectedIndex++;
break; break;
} }
while (selectedIndex < 0) { while (selectedIndex < 0) {
selectedIndex = this.gridEntries.length + selectedIndex selectedIndex = this.gridEntries.length + selectedIndex;
} }
if (selectedIndex > this.gridEntries.length) { if (selectedIndex > this.gridEntries.length) {
selectedIndex %= this.gridEntries.length; selectedIndex %= this.gridEntries.length;
@ -222,7 +221,7 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
offsetTop = this.virtualScroll.measureScrollOffset("top"); offsetTop = this.virtualScroll.measureScrollOffset("top");
if (contentOffset < offsetTop + (viewportSize / 2)) { if (contentOffset < offsetTop + (viewportSize / 2)) {
this.virtualScroll.scrollToOffset((offsetTop + 130) - viewportSize/ 2) this.virtualScroll.scrollToOffset((offsetTop + 130) - viewportSize/ 2);
} }
} }
} }
@ -266,7 +265,7 @@ export class FileGridComponent implements OnChanges, OnInit, AfterViewInit {
break; break;
case "Enter": case "Enter":
if (this.selectedEntries.length === 1) { if (this.selectedEntries.length === 1) {
this.fileOpenEvent.emit(this.selectedEntries[0].data) this.fileOpenEvent.emit(this.selectedEntries[0].data);
} }
break; break;
} }

@ -1,7 +1,7 @@
<app-file-grid #fileGrid (fileOpenEvent)="this.onFileOpen($event)" (fileSelectEvent)="this.onFileSelect($event)" <app-file-grid (fileOpenEvent)="this.onFileOpen($event)" (fileSelectEvent)="this.onFileSelect($event)"
*ngIf="this.mode === 'grid'" *ngIf="this.mode === 'grid'"
[files]="this.files" [preselectedFile]="this.preselectedFile"></app-file-grid> [files]="this.files" [preselectedFile]="this.preselectedFile"></app-file-grid>
<app-file-gallery #fileGallery (closeEvent)="this.setMode('grid')" (fileSelectEvent)="this.onSingleFileSelect($event)" <app-file-gallery (closeEvent)="this.setMode('grid')" (fileSelectEvent)="this.onSingleFileSelect($event)"
*ngIf="this.mode === 'gallery'" *ngIf="this.mode === 'gallery'"
[files]="this.files" [files]="this.files"
[preselectedFile]="this.preselectedFile"></app-file-gallery> [preselectedFile]="this.preselectedFile"></app-file-gallery>

@ -1,13 +1,12 @@
import { import {
AfterViewChecked, AfterViewInit, AfterViewInit,
Component, Component,
ElementRef,
EventEmitter, EventEmitter,
Input, Input,
Output, Output,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
import {FileGalleryComponent} from "./file-gallery/file-gallery.component"; import {FileGalleryComponent} from "./file-gallery/file-gallery.component";
import {FileGridComponent} from "./file-grid/file-grid.component"; import {FileGridComponent} from "./file-grid/file-grid.component";
@ -36,7 +35,7 @@ export class FileMultiviewComponent implements AfterViewInit {
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
if (this.preselectedFile) { if (this.preselectedFile) {
this.fileSelectEvent.emit([this.preselectedFile]) this.fileSelectEvent.emit([this.preselectedFile]);
this.selectedFiles = [this.preselectedFile]; this.selectedFiles = [this.preselectedFile];
} }
} }
@ -59,7 +58,7 @@ export class FileMultiviewComponent implements AfterViewInit {
public onFileOpen(file: File): void { public onFileOpen(file: File): void {
this.preselectedFile = file; this.preselectedFile = file;
this.setMode("gallery") this.setMode("gallery");
this.fileOpenEvent.emit(file); this.fileOpenEvent.emit(file);
} }

@ -2,7 +2,7 @@
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.getThumbnailSupported() && this.thumbUrl" class="file-icon-overlay">
<ng-icon *ngIf="getFileType() === 'video'" name="mat-movie"></ng-icon> <ng-icon *ngIf="getFileType() === 'video'" name="mat-movie"></ng-icon>
<ng-icon *ngIf="this.file.mime_type === '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.getThumbnailSupported() || !this.thumbUrl" class="file-type-icon">
<ng-icon *ngIf="getFileType() === 'image'" name="mat-image"></ng-icon> <ng-icon *ngIf="getFileType() === 'image'" name="mat-image"></ng-icon>

@ -5,13 +5,10 @@ import {
OnChanges, OnChanges,
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../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 {
SchedulingService
} from "../../../../services/scheduling/scheduling.service";
@Component({ @Component({
selector: "app-file-thumbnail", selector: "app-file-thumbnail",
@ -41,14 +38,14 @@ export class FileThumbnailComponent implements OnChanges, AfterViewInit {
} }
public getThumbnailSupported(): boolean { public getThumbnailSupported(): boolean {
const mimeParts = FileHelper.parseMime(this.file.mime_type); const mimeParts = FileHelper.parseMime(this.file.mimeType);
return !!mimeParts && this.supportedThumbnailTypes.includes( return !!mimeParts && this.supportedThumbnailTypes.includes(
mimeParts[0]); mimeParts[0]);
} }
public getFileType(): string { public getFileType(): string {
const mimeParts = FileHelper.parseMime(this.file.mime_type); const mimeParts = FileHelper.parseMime(this.file.mimeType);
return (mimeParts && mimeParts[0]) ?? "other"; return (mimeParts && mimeParts[0]) ?? "other";
} }
} }

@ -8,7 +8,7 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../../api/models/Tag";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
@ -60,7 +60,7 @@ export class TagInputComponent implements OnChanges {
} }
private addTag(value: string) { private addTag(value: string) {
const tag = this.normalizeTag(value); const tag = TagInputComponent.normalizeTag(value);
if (tag.length > 0 && (this.allowInvalid || this.checkTagValid(tag))) { if (tag.length > 0 && (this.allowInvalid || this.checkTagValid(tag))) {
this.tagAdded.emit(tag); this.tagAdded.emit(tag);
this.formControl.setValue(""); this.formControl.setValue("");
@ -69,7 +69,7 @@ export class TagInputComponent implements OnChanges {
} }
private filterSuggestionTag(tag: string) { private filterSuggestionTag(tag: string) {
let normalizedTag = this.normalizeTag(tag); let normalizedTag = TagInputComponent.normalizeTag(tag);
const negated = normalizedTag.startsWith("-") && this.allowNegation; const negated = normalizedTag.startsWith("-") && this.allowNegation;
normalizedTag = this.allowNegation ? normalizedTag.replace(/^-/, normalizedTag = this.allowNegation ? normalizedTag.replace(/^-/,
"") : normalizedTag; "") : normalizedTag;
@ -80,11 +80,11 @@ export class TagInputComponent implements OnChanges {
const autocompleteTags = this.tagsForAutocomplete.filter( const autocompleteTags = this.tagsForAutocomplete.filter(
t => t.includes(normalizedTag)) t => t.includes(normalizedTag))
.map(t => negated ? "-" + t : t) .map(t => negated ? "-" + t : t)
.sort((l, r) => this.compareSuggestionTags(normalizedTag, l, r)) .sort((l, r) => TagInputComponent.compareSuggestionTags(normalizedTag, l, r))
.slice(0, 50); .slice(0, 50);
if (containsWildcard) { if (containsWildcard) {
autocompleteTags.unshift(this.normalizeTag(tag)); autocompleteTags.unshift(TagInputComponent.normalizeTag(tag));
} }
return autocompleteTags; return autocompleteTags;
@ -111,7 +111,7 @@ export class TagInputComponent implements OnChanges {
* @returns {string} * @returns {string}
* @private * @private
*/ */
private normalizeTag(tag: string): string { private static normalizeTag(tag: string): string {
let normalizedTag = tag.trim(); let normalizedTag = tag.trim();
let parts = normalizedTag.split(":"); let parts = normalizedTag.split(":");
@ -124,7 +124,7 @@ export class TagInputComponent implements OnChanges {
} }
} }
private compareSuggestionTags(query: string, l: string, r: string): number { private static compareSuggestionTags(query: string, l: string, r: string): number {
if (l.startsWith(query) && !r.startsWith(query)) { if (l.startsWith(query) && !r.startsWith(query)) {
return -1; return -1;
} else if (!l.startsWith(query) && r.startsWith(query)) { } else if (!l.startsWith(query) && r.startsWith(query)) {
@ -134,7 +134,7 @@ export class TagInputComponent implements OnChanges {
} else if (l.length > r.length) { } else if (l.length > r.length) {
return 1; return 1;
} else { } else {
return l.localeCompare(r) return l.localeCompare(r);
} }
} }
} }

@ -9,7 +9,7 @@ import {
ErrorBrokerService ErrorBrokerService
} from "../../../../../services/error-broker/error-broker.service"; } from "../../../../../services/error-broker/error-broker.service";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {Repository} from "../../../../../models/Repository"; import {Repository} from "../../../../../../api/models/Repository";
@Component({ @Component({
selector: "app-edit-repository-dialog", selector: "app-edit-repository-dialog",
@ -62,10 +62,7 @@ export class EditRepositoryDialogComponent {
} }
await this.repoService.addRepository(name, path, address, await this.repoService.addRepository(name, path, address,
repositoryType === "local"); repositoryType === "local");
this.selectedRepository.name = name; this.selectedRepository.update({name, local: repositoryType === "local", path, address});
this.selectedRepository.local = repositoryType === "local";
this.selectedRepository.path = path;
this.selectedRepository.address = address;
this.dialogRef.close(); this.dialogRef.close();
} catch (err) { } catch (err) {

@ -6,7 +6,7 @@ import {
ValidationErrors, ValidationErrors,
Validators Validators
} from "@angular/forms"; } from "@angular/forms";
import {Repository} from "../../../../../models/Repository"; import {Repository} from "../../../../../../api/models/Repository";
import { import {
RepositoryService RepositoryService
} from "../../../../../services/repository/repository.service"; } from "../../../../../services/repository/repository.service";
@ -20,7 +20,7 @@ import {MatDialog} from "@angular/material/dialog";
}) })
export class RepositoryFormComponent implements OnInit { export class RepositoryFormComponent implements OnInit {
@Input() name: string = "My Repository"; @Input() name: string = "My RepositoryData";
@Input() repositoryType: "local" | "remote" = "local"; @Input() repositoryType: "local" | "remote" = "local";
@Input() path: string = ""; @Input() path: string = "";
@Input() address: string = ""; @Input() address: string = "";
@ -120,7 +120,7 @@ export class RepositoryFormComponent implements OnInit {
"repositoryType")?.value ?? "remote"; "repositoryType")?.value ?? "remote";
if (repositoryType === "remote") { if (repositoryType === "remote") {
const match = /(\d+\.){3}\d+:\d+|\S+:\d+/.test(control.value) const match = /(\d+\.){3}\d+:\d+|\S+:\d+/.test(control.value);
return match ? null : {invalidAddress: control.value}; return match ? null : {invalidAddress: control.value};
} }

@ -16,7 +16,7 @@ import {MatSelectModule} from "@angular/material/select";
import {MatInputModule} from "@angular/material/input"; import {MatInputModule} from "@angular/material/input";
import {ReactiveFormsModule} from "@angular/forms"; import {ReactiveFormsModule} from "@angular/forms";
import {NgIconsModule} from "@ng-icons/core"; import {NgIconsModule} from "@ng-icons/core";
import {MatFolder} from "@ng-icons/material-icons" import {MatFolder} from "@ng-icons/material-icons";
@NgModule({ @NgModule({

@ -1,5 +1,5 @@
import {Component, EventEmitter, Output} from "@angular/core"; import {Component, EventEmitter, Output} from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
@Component({ @Component({
selector: "app-file-import", selector: "app-file-import",

@ -1,12 +1,12 @@
import {Component, EventEmitter, Output} from "@angular/core"; import {Component, EventEmitter, Output} from "@angular/core";
import {FileOsMetadata} from "../../../../../models/FileOsMetadata";
import {ImportService} from "../../../../../services/import/import.service"; import {ImportService} from "../../../../../services/import/import.service";
import { import {
ErrorBrokerService ErrorBrokerService
} from "../../../../../services/error-broker/error-broker.service"; } from "../../../../../services/error-broker/error-broker.service";
import {AddFileOptions} from "../../../../../models/AddFileOptions"; import {AddFileOptions} from "../../../../../models/AddFileOptions";
import {File} from "../../../../../models/File"; import {File} from "../../../../../../api/models/File";
import {DialogFilter} from "@tauri-apps/api/dialog"; import {DialogFilter} from "@tauri-apps/api/dialog";
import {FileOsMetadata} from "../../../../../../api/api-types/files";
@Component({ @Component({
selector: "app-filesystem-import", selector: "app-filesystem-import",

@ -2,7 +2,7 @@
<app-metadata-entry *ngIf="mode === 'read'" [attributeName]="attributeName">{{value}}</app-metadata-entry> <app-metadata-entry *ngIf="mode === 'read'" [attributeName]="attributeName">{{value}}</app-metadata-entry>
<mat-form-field *ngIf="mode === 'write'"> <mat-form-field *ngIf="mode === 'write'">
<mat-label>{{attributeName}}</mat-label> <mat-label>{{attributeName}}</mat-label>
<input [formControl]="formControl" type="text" matInput [value]="value"> <input [formControl]="formControl" type="text" matInput [value]="value.toString()">
</mat-form-field> </mat-form-field>
<button *ngIf="mode === 'write'" mat-button (click)="this.onSave()"> <button *ngIf="mode === 'write'" mat-button (click)="this.onSave()">
<ng-icon name="mat-save"></ng-icon> <ng-icon name="mat-save"></ng-icon>

@ -1,4 +1,12 @@
import {Component, EventEmitter, Input, OnInit, Output} from "@angular/core"; import {
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from "@angular/core";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
@Component({ @Component({
@ -6,7 +14,7 @@ import {FormControl} from "@angular/forms";
templateUrl: "./editable-metadata-entry.component.html", templateUrl: "./editable-metadata-entry.component.html",
styleUrls: ["./editable-metadata-entry.component.scss"] styleUrls: ["./editable-metadata-entry.component.scss"]
}) })
export class EditableMetadataEntryComponent implements OnInit{ export class EditableMetadataEntryComponent implements OnInit, OnChanges {
@Input() attributeName!: string; @Input() attributeName!: string;
@Input() value!: string | number; @Input() value!: string | number;
@ -22,6 +30,12 @@ export class EditableMetadataEntryComponent implements OnInit{
this.formControl.setValue(this.value); this.formControl.setValue(this.value);
} }
public ngOnChanges(changes: SimpleChanges): void {
if (changes["value"] || changes["mode"]) {
this.formControl.setValue(this.value);
}
}
public onSave(): void { public onSave(): void {
this.valueChangeEvent.emit(this.formControl.value); this.valueChangeEvent.emit(this.formControl.value);
this.mode = "read"; this.mode = "read";

@ -6,12 +6,12 @@
<div class="file-metadata-entries-scroll-container"> <div class="file-metadata-entries-scroll-container">
<div class="file-metadata-entries"> <div class="file-metadata-entries">
<app-editable-metadata-entry attributeName="Name" [value]="file.name ?? ''" (valueChangeEvent)="this.saveFileName($event)"></app-editable-metadata-entry> <app-editable-metadata-entry *ngIf="fileMetadata" attributeName="Name" [value]="fileMetadata.name ?? ''" (valueChangeEvent)="this.saveFileName($event)"></app-editable-metadata-entry>
<app-metadata-entry attributeName="Hash">{{file.hash}}</app-metadata-entry> <app-metadata-entry attributeName="Content Descriptor (CD)">{{file.cd}}</app-metadata-entry>
<app-metadata-entry attributeName="Mime Type">{{file.mime_type ?? 'unknown'}}</app-metadata-entry> <app-metadata-entry attributeName="Mime Type">{{file.mimeType}}</app-metadata-entry>
<app-metadata-entry attributeName="Imported at">{{file.import_time.toLocaleString()}}</app-metadata-entry> <app-metadata-entry *ngIf="fileMetadata" attributeName="Imported at">{{fileMetadata.import_time.toLocaleString()}}</app-metadata-entry>
<app-metadata-entry attributeName="Created at">{{file.creation_time.toLocaleString()}}</app-metadata-entry> <app-metadata-entry *ngIf="fileMetadata" attributeName="Created at">{{fileMetadata.creation_time.toLocaleString()}}</app-metadata-entry>
<app-metadata-entry attributeName="Changed at">{{file.change_time.toLocaleString()}}</app-metadata-entry> <app-metadata-entry *ngIf="fileMetadata" attributeName="Changed at">{{fileMetadata.change_time.toLocaleString()}}</app-metadata-entry>
</div> </div>
</div> </div>

@ -1,21 +1,41 @@
import {Component, Input} from "@angular/core"; import {
import {File} from "../../../../models/File"; Component,
Input,
OnChanges,
OnInit,
SimpleChanges
} from "@angular/core";
import {File} from "../../../../../api/models/File";
import {FileService} from "../../../../services/file/file.service"; import {FileService} from "../../../../services/file/file.service";
import {FileMetadata} from "../../../../../api/api-types/files";
@Component({ @Component({
selector: "app-file-metadata", selector: "app-file-metadata",
templateUrl: "./file-metadata.component.html", templateUrl: "./file-metadata.component.html",
styleUrls: ["./file-metadata.component.scss"] styleUrls: ["./file-metadata.component.scss"]
}) })
export class FileMetadataComponent { export class FileMetadataComponent implements OnInit, OnChanges {
@Input() file!: File; @Input() file!: File;
public fileMetadata: FileMetadata | undefined;
constructor(private fileService: FileService) { constructor(private fileService: FileService) {
} }
public async ngOnInit() {
this.fileMetadata = await this.fileService.getFileMetadata(this.file.id);
}
public async ngOnChanges(changes:SimpleChanges) {
if (changes["file"] && (!this.fileMetadata || this.fileMetadata.file_id != this.file.id)) {
this.fileMetadata = await this.fileService.getFileMetadata(this.file.id);
}
}
public async saveFileName(name: string) { public async saveFileName(name: string) {
const newFile = await this.fileService.updateFileName(this.file, name); const newFile = await this.fileService.updateFileName(this.file.id, name);
this.file.name = newFile.name; if (this.fileMetadata) {
this.fileMetadata.name = newFile.name;
}
} }
} }

@ -16,11 +16,11 @@ import {
ErrorBrokerService ErrorBrokerService
} from "../../../../services/error-broker/error-broker.service"; } from "../../../../services/error-broker/error-broker.service";
import { import {
FilterExpression, GenericFilter,
SingleFilterExpression SingleFilterExpression
} from "../../../../models/FilterExpression"; } from "../../../../models/GenericFilter";
import {FilterDialogComponent} from "./filter-dialog/filter-dialog.component"; import {FilterDialogComponent} from "./filter-dialog/filter-dialog.component";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../../api/models/Tag";
import {clipboard} from "@tauri-apps/api"; import {clipboard} from "@tauri-apps/api";
import {TabState} from "../../../../models/TabState"; import {TabState} from "../../../../models/TabState";
@ -32,7 +32,7 @@ import {TabState} from "../../../../models/TabState";
}) })
export class FileSearchComponent implements AfterViewChecked, OnInit { export class FileSearchComponent implements AfterViewChecked, OnInit {
public sortExpression: SortKey[] = []; public sortExpression: SortKey[] = [];
public filters: FilterExpression[] = []; public filters: GenericFilter[] = [];
@Input() availableTags: Tag[] = []; @Input() availableTags: Tag[] = [];
@Input() contextTags: Tag[] = []; @Input() contextTags: Tag[] = [];
@ -93,7 +93,7 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
this.state.setFilters([]); this.state.setFilters([]);
} }
public async removeFilterExpression(expr: FilterExpression) { public async removeFilterExpression(expr: GenericFilter) {
const index = this.filters.indexOf(expr); const index = this.filters.indexOf(expr);
if (index >= 0) { if (index >= 0) {
this.filters.splice(index, 1); this.filters.splice(index, 1);
@ -105,7 +105,7 @@ export class FileSearchComponent implements AfterViewChecked, OnInit {
const sortEntries = this.sortExpression.map( const sortEntries = this.sortExpression.map(
key => JSON.parse(JSON.stringify(key))).map( key => JSON.parse(JSON.stringify(key))).map(
key => new SortKey(key.sortType, key.sortDirection, key => new SortKey(key.sortType, key.sortDirection,
key.namespaceName)) key.namespaceName));
const openedDialog = this.dialog.open(SortDialogComponent, { const openedDialog = this.dialog.open(SortDialogComponent, {
minWidth: "40vw", minWidth: "40vw",
data: { data: {

@ -2,12 +2,12 @@ import {Component, HostListener, Inject, ViewChildren} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {SortDialogComponent} from "../sort-dialog/sort-dialog.component"; import {SortDialogComponent} from "../sort-dialog/sort-dialog.component";
import { import {
FilterExpression, GenericFilter,
OrFilterExpression, OrFilterExpression,
SingleFilterExpression SingleFilterExpression
} from "../../../../../models/FilterExpression"; } from "../../../../../models/GenericFilter";
import {TagQuery} from "../../../../../models/TagQuery"; import {TagQuery} from "../../../../../models/TagQuery";
import {Tag} from "../../../../../models/Tag"; import {Tag} from "../../../../../../api/models/Tag";
import { import {
TagFilterListItemComponent TagFilterListItemComponent
} from "./tag-filter-list-item/tag-filter-list-item.component"; } from "./tag-filter-list-item/tag-filter-list-item.component";
@ -20,7 +20,7 @@ import {Selectable} from "../../../../../models/Selectable";
}) })
export class FilterDialogComponent { export class FilterDialogComponent {
public filters: Selectable<FilterExpression>[]; public filters: Selectable<GenericFilter>[];
public availableTags: Tag[] = []; public availableTags: Tag[] = [];
public mode: "AND" | "OR" = "AND"; public mode: "AND" | "OR" = "AND";
@ -32,12 +32,12 @@ export class FilterDialogComponent {
constructor(public dialogRef: MatDialogRef<SortDialogComponent>, @Inject( constructor(public dialogRef: MatDialogRef<SortDialogComponent>, @Inject(
MAT_DIALOG_DATA) data: any) { MAT_DIALOG_DATA) data: any) {
this.filters = data.filterEntries.map( this.filters = data.filterEntries.map(
(f: FilterExpression) => new Selectable<FilterExpression>(f, (f: GenericFilter) => new Selectable<GenericFilter>(f,
false)) ?? []; false)) ?? [];
this.availableTags = data.availableTags ?? []; this.availableTags = data.availableTags ?? [];
} }
private static checkFiltersEqual(l: FilterExpression, r: FilterExpression): boolean { private static checkFiltersEqual(l: GenericFilter, r: GenericFilter): boolean {
const lTags = l.queryList().map(q => q.getNormalizedTag()).sort(); const lTags = l.queryList().map(q => q.getNormalizedTag()).sort();
const rTags = r.queryList().map(q => q.getNormalizedTag()).sort(); const rTags = r.queryList().map(q => q.getNormalizedTag()).sort();
let match = false; let match = false;
@ -77,7 +77,7 @@ export class FilterDialogComponent {
if (this.mode === "AND" || this.filters.length === 0) { if (this.mode === "AND" || this.filters.length === 0) {
this.filters.push( this.filters.push(
new Selectable<FilterExpression>( new Selectable<GenericFilter>(
new SingleFilterExpression(query), new SingleFilterExpression(query),
false)); false));
tag = tag.replace(/^-/g, ""); tag = tag.replace(/^-/g, "");
@ -94,7 +94,7 @@ export class FilterDialogComponent {
const filterExpression = new OrFilterExpression(queryList); const filterExpression = new OrFilterExpression(queryList);
filterExpression.removeDuplicates(); filterExpression.removeDuplicates();
this.filters.push( this.filters.push(
new Selectable<FilterExpression>(filterExpression, new Selectable<GenericFilter>(filterExpression,
false)); false));
} }
this.unselectAll(); this.unselectAll();
@ -120,7 +120,7 @@ export class FilterDialogComponent {
public convertSelectionToAndExpression(): void { public convertSelectionToAndExpression(): void {
for (const query of this.selectedQueries) { for (const query of this.selectedQueries) {
this.filters.push( this.filters.push(
new Selectable<FilterExpression>( new Selectable<GenericFilter>(
new SingleFilterExpression(query), new SingleFilterExpression(query),
false)); false));
} }
@ -131,7 +131,7 @@ export class FilterDialogComponent {
public convertSelectionToOrExpression(): void { public convertSelectionToOrExpression(): void {
const queries = this.selectedQueries; const queries = this.selectedQueries;
const expression = new OrFilterExpression(queries); const expression = new OrFilterExpression(queries);
this.filters.push(new Selectable<FilterExpression>(expression, false)); this.filters.push(new Selectable<GenericFilter>(expression, false));
this.removeFilterDuplicates(); this.removeFilterDuplicates();
this.unselectAll(); this.unselectAll();
} }
@ -142,7 +142,7 @@ export class FilterDialogComponent {
private removeFilterDuplicates() { private removeFilterDuplicates() {
const filters = this.filters; const filters = this.filters;
let newFilters: Selectable<FilterExpression>[] = []; let newFilters: Selectable<GenericFilter>[] = [];
for (const filterItem of filters) { for (const filterItem of filters) {
if (filterItem.data.filter_type == "OrExpression") { if (filterItem.data.filter_type == "OrExpression") {

@ -7,10 +7,10 @@ import {
SimpleChanges SimpleChanges
} from "@angular/core"; } from "@angular/core";
import { import {
FilterExpression, GenericFilter,
OrFilterExpression, OrFilterExpression,
SingleFilterExpression SingleFilterExpression
} from "../../../../../../models/FilterExpression"; } from "../../../../../../models/GenericFilter";
import {TagQuery} from "../../../../../../models/TagQuery"; import {TagQuery} from "../../../../../../models/TagQuery";
import {Selectable} from "../../../../../../models/Selectable"; import {Selectable} from "../../../../../../models/Selectable";
@ -21,7 +21,7 @@ import {Selectable} from "../../../../../../models/Selectable";
}) })
export class TagFilterListItemComponent implements OnChanges { export class TagFilterListItemComponent implements OnChanges {
@Input() expression!: Selectable<FilterExpression>; @Input() expression!: Selectable<GenericFilter>;
@Output() removeClicked = new EventEmitter<TagFilterListItemComponent>(); @Output() removeClicked = new EventEmitter<TagFilterListItemComponent>();
@Output() querySelect = new EventEmitter<TagQuery>(); @Output() querySelect = new EventEmitter<TagQuery>();
@Output() queryUnselect = new EventEmitter<TagQuery>(); @Output() queryUnselect = new EventEmitter<TagQuery>();

@ -2,9 +2,8 @@ import {Component, Inject} from "@angular/core";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {SortKey} from "../../../../../models/SortKey"; import {SortKey} from "../../../../../models/SortKey";
import {CdkDragDrop, moveItemInArray} from "@angular/cdk/drag-drop"; import {CdkDragDrop, moveItemInArray} from "@angular/cdk/drag-drop";
import {Namespace} from "../../../../../models/Namespace"; import {Namespace} from "../../../../../../api/models/Namespace";
import {TagService} from "../../../../../services/tag/tag.service"; import {TagService} from "../../../../../services/tag/tag.service";
import {FormControl} from "@angular/forms";
@Component({ @Component({
selector: "app-sort-dialog", selector: "app-sort-dialog",
@ -27,7 +26,7 @@ export class SortDialogComponent {
addNewSortKey() { addNewSortKey() {
const sortKey = new SortKey("FileName", "Ascending", undefined); const sortKey = new SortKey("FileName", "Ascending", undefined);
this.sortEntries.push(sortKey) this.sortEntries.push(sortKey);
} }
public removeSortKey(sortKey: SortKey): void { public removeSortKey(sortKey: SortKey): void {
@ -40,7 +39,7 @@ export class SortDialogComponent {
} }
public cancelSort(): void { public cancelSort(): void {
this.dialogRef.close() this.dialogRef.close();
} }
public onSortEntryDrop(event: CdkDragDrop<SortKey[]>): void { public onSortEntryDrop(event: CdkDragDrop<SortKey[]>): void {
@ -50,11 +49,11 @@ export class SortDialogComponent {
public updateAutocompleteSuggestions(value: string): void { public updateAutocompleteSuggestions(value: string): void {
this.suggestedNamespaces = this.namespaces.sort( this.suggestedNamespaces = this.namespaces.sort(
(a, b) => this.compareSuggestionNamespaces(value, a.name, b.name)) (a, b) => SortDialogComponent.compareSuggestionNamespaces(value, a.name, b.name))
.slice(0, 50) .slice(0, 50);
} }
private compareSuggestionNamespaces(query: string, l: string, r: string): number { private static compareSuggestionNamespaces(query: string, l: string, r: string): number {
if (l.startsWith(query) && !r.startsWith(query)) { if (l.startsWith(query) && !r.startsWith(query)) {
return -1; return -1;
} else if (!l.startsWith(query) && r.startsWith(query)) { } else if (!l.startsWith(query) && r.startsWith(query)) {
@ -64,7 +63,7 @@ export class SortDialogComponent {
} else if (l.length > r.length) { } else if (l.length > r.length) {
return 1; return 1;
} else { } else {
return l.localeCompare(r) return l.localeCompare(r);
} }
} }
} }

@ -42,9 +42,6 @@ import {MatCheckboxModule} from "@angular/material/checkbox";
import {MatProgressBarModule} from "@angular/material/progress-bar"; import {MatProgressBarModule} from "@angular/material/progress-bar";
import {MatMenuModule} from "@angular/material/menu"; import {MatMenuModule} from "@angular/material/menu";
import {FileMetadataComponent} from "./file-metadata/file-metadata.component"; import {FileMetadataComponent} from "./file-metadata/file-metadata.component";
import {
MetadataEntryComponent
} from "../app-common/metadata-entry/metadata-entry.component";
import { import {
EditableMetadataEntryComponent EditableMetadataEntryComponent
} from "./file-metadata/editable-metadata-entry/editable-metadata-entry.component"; } from "./file-metadata/editable-metadata-entry/editable-metadata-entry.component";

@ -1,16 +1,17 @@
import { import {
Component, EventEmitter, Component,
EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnInit, Output, OnInit,
Output,
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from "@angular/core"; } from "@angular/core";
import {File} from "../../../../models/File"; import {File} from "../../../../../api/models/File";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../../api/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",
@ -44,7 +45,7 @@ export class TagEditComponent implements OnInit, OnChanges {
async ngOnChanges(changes: SimpleChanges) { async ngOnChanges(changes: SimpleChanges) {
if (changes["files"]) { if (changes["files"]) {
await this.loadFileTags() await this.loadFileTags();
} }
} }
@ -93,7 +94,9 @@ export class TagEditComponent implements OnInit, OnChanges {
} }
this.mapFileTagsToTagList(); this.mapFileTagsToTagList();
const index = this.tags.indexOf(tag); const index = this.tags.indexOf(tag);
index >= 0 && this.tagScroll.scrollToIndex(index); if (index >= 0) {
this.tagScroll.scrollToIndex(index);
}
this.tagEditEvent.emit(this); this.tagEditEvent.emit(this);
} }
@ -107,7 +110,9 @@ export class TagEditComponent implements OnInit, OnChanges {
} }
this.mapFileTagsToTagList(); this.mapFileTagsToTagList();
const index = this.tags.indexOf(tag); const index = this.tags.indexOf(tag);
index >= 0 && this.tagScroll.scrollToIndex(index); if (index >= 0) {
this.tagScroll.scrollToIndex(index);
}
this.tagEditEvent.emit(this); this.tagEditEvent.emit(this);
await this.tagService.loadTags(); await this.tagService.loadTags();
await this.tagService.loadNamespaces(); await this.tagService.loadNamespaces();
@ -132,8 +137,8 @@ export class TagEditComponent implements OnInit, OnChanges {
const promises = []; const promises = [];
const loadFn = async (file: File) => { const loadFn = async (file: File) => {
this.fileTags[file.id] = await this.tagService.getTagsForFiles( this.fileTags[file.id] = await this.tagService.getTagsForFiles(
[file.hash]); [file.cd]);
} };
for (const file of this.files) { for (const file of this.files) {
promises.push(loadFn(file)); promises.push(loadFn(file));
} }

@ -1,5 +1,5 @@
import {Component, Input} from "@angular/core"; import {Component, Input} from "@angular/core";
import {Tag} from "../../../../models/Tag"; import {Tag} from "../../../../../api/models/Tag";
@Component({ @Component({
selector: "app-tag-item", selector: "app-tag-item",

@ -24,7 +24,7 @@ export class AppState {
public async closeTab(uuid: number) { public async closeTab(uuid: number) {
const index = this.tabs.value.findIndex(t => t.uuid === uuid); const index = this.tabs.value.findIndex(t => t.uuid === uuid);
const tabs = this.tabs.value; const tabs = this.tabs.value;
tabs.splice(index, 1) tabs.splice(index, 1);
this.tabs.next(tabs); this.tabs.next(tabs);
} }
@ -37,7 +37,7 @@ export class AppState {
appState.tabIdCounter = state.tabIdCounter; appState.tabIdCounter = state.tabIdCounter;
appState.selectedTab.next(state.selectedTab); appState.selectedTab.next(state.selectedTab);
return appState return appState;
} }
public serializeJson(): string { public serializeJson(): string {

@ -1,14 +0,0 @@
export class File {
constructor(
public id: number,
public name: string | undefined,
public comment: string | undefined,
public hash: string,
public file_type: number,
public mime_type: string | undefined,
public creation_time: Date,
public change_time: Date,
public import_time: Date,
) {
}
}

@ -1,7 +0,0 @@
export type FileOsMetadata = {
name: string,
path: string,
mime_type: string,
created_at: Date,
modified_at: Date,
}

@ -1,7 +1,8 @@
import {TagQuery} from "./TagQuery"; import {TagQuery} from "./TagQuery";
import {createRustEnum, RustEnum} from "./rust-types"; import {createRustEnum} from "./rust-types";
import {FilterExpression} from "../../api/api-types/files";
export interface FilterExpression { export interface GenericFilter {
filter_type: "OrExpression" | "Query"; filter_type: "OrExpression" | "Query";
filter: TagQuery[] | TagQuery; filter: TagQuery[] | TagQuery;
@ -11,14 +12,14 @@ export interface FilterExpression {
getDisplayName(): string; getDisplayName(): string;
clone(): FilterExpression; clone(): GenericFilter;
queryList(): TagQuery[]; queryList(): TagQuery[];
toBackendType(): RustEnum<TagQuery | TagQuery[]>; toBackendType(): FilterExpression;
} }
export class OrFilterExpression implements FilterExpression { export class OrFilterExpression implements GenericFilter {
public filter_type: "OrExpression" = "OrExpression"; public filter_type: "OrExpression" = "OrExpression";
public filter: TagQuery[] = []; public filter: TagQuery[] = [];
@ -27,7 +28,7 @@ export class OrFilterExpression implements FilterExpression {
} }
public eq(value: any): boolean { public eq(value: any): boolean {
return this == value return this == value;
} }
public partiallyEq(value: any): boolean { public partiallyEq(value: any): boolean {
@ -41,7 +42,7 @@ export class OrFilterExpression implements FilterExpression {
public clone(): OrFilterExpression { public clone(): OrFilterExpression {
let tags = this.filter.map( let tags = this.filter.map(
(t: TagQuery) => new TagQuery(t.tag, t.negate)); (t: TagQuery) => new TagQuery(t.tag, t.negate));
return new OrFilterExpression(tags) return new OrFilterExpression(tags);
} }
public queryList(): TagQuery[] { public queryList(): TagQuery[] {
@ -64,12 +65,12 @@ export class OrFilterExpression implements FilterExpression {
this.filter = newEntries.reverse(); this.filter = newEntries.reverse();
} }
public toBackendType(): RustEnum<TagQuery | TagQuery[]> { public toBackendType(): FilterExpression {
return createRustEnum(this.filter_type, this.filter); return createRustEnum(this.filter_type, this.filter) as unknown as FilterExpression;
} }
} }
export class SingleFilterExpression implements FilterExpression { export class SingleFilterExpression implements GenericFilter {
public filter_type: "Query" = "Query"; public filter_type: "Query" = "Query";
public filter: TagQuery; public filter: TagQuery;
@ -89,16 +90,16 @@ export class SingleFilterExpression implements FilterExpression {
return this.filter.getNormalizedTag(); return this.filter.getNormalizedTag();
} }
public clone(): FilterExpression { public clone(): GenericFilter {
return new SingleFilterExpression( return new SingleFilterExpression(
new TagQuery(this.filter.tag, this.filter.negate)) new TagQuery(this.filter.tag, this.filter.negate));
} }
public queryList(): TagQuery[] { public queryList(): TagQuery[] {
return [this.filter] return [this.filter];
} }
public toBackendType(): RustEnum<TagQuery | TagQuery[]> { public toBackendType(): FilterExpression {
return createRustEnum(this.filter_type, this.filter); return createRustEnum(this.filter_type, this.filter) as unknown as FilterExpression;
} }
} }

@ -1,4 +0,0 @@
export class Namespace {
constructor(public id: number, public name: string) {
}
}

@ -1,9 +0,0 @@
export class Repository {
constructor(
public name: string,
public address: string | undefined,
public path: string | undefined,
public local: boolean,
) {
}
}

@ -1,11 +0,0 @@
export enum SizeType {
Total = "Total",
FileFolder = "FileFolder",
ThumbFolder = "ThumbFolder",
DatabaseFile = "DatabaseFile",
}
export type SizeMetadata = {
size_type: SizeType,
size: number,
}

@ -10,9 +10,9 @@ export class SortKey {
public toString(): string { public toString(): string {
if (this.sortType == "Namespace") { if (this.sortType == "Namespace") {
return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}` return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}`;
} else { } else {
return `${this.sortType} ${this.sortDirection}` return `${this.sortType} ${this.sortDirection}`;
} }
} }
@ -24,7 +24,7 @@ export class SortKey {
direction: this.sortDirection, direction: this.sortDirection,
name: this.namespaceName name: this.namespaceName
} }
} };
} else { } else {
let returnObj: any = {}; let returnObj: any = {};
returnObj[this.sortType] = this.sortDirection; returnObj[this.sortType] = this.sortDirection;

@ -1,25 +1,26 @@
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory"; import {TabCategory} from "./TabCategory";
import {FileService} from "../services/file/file.service"; import {FileService} from "../services/file/file.service";
import {File} from "./File"; import {File} from "../../api/models/File";
import { import {
FilterExpression, GenericFilter,
OrFilterExpression, OrFilterExpression,
SingleFilterExpression SingleFilterExpression
} from "./FilterExpression"; } from "./GenericFilter";
import {SortKey} from "./SortKey"; import {SortKey} from "./SortKey";
import {TagQuery} from "./TagQuery"; import {TagQuery} from "./TagQuery";
import {debounceTime} from "rxjs/operators"; import {debounceTime} from "rxjs/operators";
import {mapNew} from "../../api/models/adaptors";
export class TabState { export class TabState {
public uuid: number; public uuid: number;
public category: TabCategory; public category: TabCategory;
public mode = new BehaviorSubject<"grid" | "gallery">("grid"); public mode = new BehaviorSubject<"grid" | "gallery">("grid");
public selectedFileHash = new BehaviorSubject<string | undefined>(undefined); public selectedCD = new BehaviorSubject<string | undefined>(undefined);
public loading = new BehaviorSubject<boolean>(false); public loading = new BehaviorSubject<boolean>(false);
public files = new BehaviorSubject<File[]>([]); public files = new BehaviorSubject<File[]>([]);
public filters = new BehaviorSubject<FilterExpression[]>([]); public filters = new BehaviorSubject<GenericFilter[]>([]);
public sortKeys = new BehaviorSubject<SortKey[]>( public sortKeys = new BehaviorSubject<SortKey[]>(
[new SortKey("FileImportedTime", [new SortKey("FileImportedTime",
"Ascending", undefined)]); "Ascending", undefined)]);
@ -46,7 +47,7 @@ export class TabState {
this.loading.next(false); this.loading.next(false);
} }
public setFilters(filters: FilterExpression[]) { public setFilters(filters: GenericFilter[]) {
this.filters.next(filters); this.filters.next(filters);
} }
@ -58,21 +59,21 @@ export class TabState {
const state = new TabState(dto.uuid, dto.category, fileService); const state = new TabState(dto.uuid, dto.category, fileService);
const filters = dto.filters.map((f: {filter: any, filter_type: any}) => { const filters = dto.filters.map((f: {filter: any, filter_type: any}) => {
if (f.filter_type === "OrExpression") { if (f.filter_type === "OrExpression") {
return new OrFilterExpression(f.filter.map((f: any) => new TagQuery(f.tag, f.negate))) return new OrFilterExpression(f.filter.map((f: any) => new TagQuery(f.tag, f.negate)));
} else { } else {
return new SingleFilterExpression(new TagQuery(f.filter.tag, f.filter.negate)) return new SingleFilterExpression(new TagQuery(f.filter.tag, f.filter.negate));
} }
}) });
const sortKeys = dto.sortKeys.map((s: {sortType: any, sortDirection: any, namespaceName: any}) => const sortKeys = dto.sortKeys.map((s: {sortType: any, sortDirection: any, namespaceName: any}) =>
new SortKey(s.sortType, s.sortDirection, s.namespaceName) new SortKey(s.sortType, s.sortDirection, s.namespaceName)
); );
state.filters.next(filters); state.filters.next(filters);
state.sortKeys.next(sortKeys); state.sortKeys.next(sortKeys);
state.mode.next(dto.mode ?? "grid"); state.mode.next(dto.mode ?? "grid");
state.selectedFileHash.next(dto.selectedFileHash); state.selectedCD.next(dto.selectedFileHash);
state.files.next(dto.files); state.files.next(dto.files.map(mapNew(File)));
return state return state;
} }
public getDTO(): any { public getDTO(): any {
@ -82,8 +83,8 @@ export class TabState {
filters: this.filters.value, filters: this.filters.value,
sortKeys: this.sortKeys.value, sortKeys: this.sortKeys.value,
mode: this.mode.value, mode: this.mode.value,
selectedFileHash: this.selectedFileHash.value, selectedFileHash: this.selectedCD.value,
files: this.files.value, files: this.category === TabCategory.Import? this.files.value.map(f => f.rawData) : [],
}; };
} }
} }

@ -1,18 +0,0 @@
export class Tag {
private normalizedTag?: string = undefined;
constructor(
public id: number,
public name: string,
public namespace: string | undefined
) {
}
public getNormalizedOutput(): string {
if (!this.normalizedTag) {
this.normalizedTag = this.namespace ? this.namespace + ":" + this.name : this.name
}
return this.normalizedTag;
}
}

@ -1,6 +0,0 @@
export type Thumbnail = {
file_hash: string,
height: number,
width: number,
mime_type: string | undefined
}

@ -10,18 +10,18 @@ export class ErrorBrokerService {
infoCb: Function | undefined; infoCb: Function | undefined;
constructor() { constructor() {
this.registerListener(); this.registerListener().catch(err => console.error(err));
} }
async registerListener() { async registerListener() {
const _unlisten = await listen("error", event => { const _unlisten = await listen("error", event => {
const payload: any = event.payload; const payload: any = event.payload;
if (payload.message) { if (payload.message) {
this.showError(payload) this.showError(payload);
} else { } else {
this.showError(payload.toString()) this.showError(payload.toString());
} }
}) });
} }
showInfo(info: string) { showInfo(info: string) {

@ -1,6 +1,6 @@
import {downloadDir} from "@tauri-apps/api/path"; import {downloadDir} from "@tauri-apps/api/path";
import {dialog} from "@tauri-apps/api"; import {dialog} from "@tauri-apps/api";
import {File} from "../../models/File"; import {File} from "../../../api/models/File";
export class FileHelper { export class FileHelper {
@ -9,21 +9,18 @@ export class FileHelper {
* @param {File} file * @param {File} file
*/ */
public static async getFileDownloadLocation(file: File): Promise<string | undefined> { public static async getFileDownloadLocation(file: File): Promise<string | undefined> {
let extension; let extension = FileHelper.getExtensionForMime(file.mimeType);
if (file.mime_type) {
extension = FileHelper.getExtensionForMime(file.mime_type);
}
const downloadDirectory = await downloadDir(); const downloadDirectory = await downloadDir();
const suggestionPath = downloadDirectory + file.hash + "." + extension; const suggestionPath = downloadDirectory + file.cd + "." + extension;
return await dialog.save({ return await dialog.save({
defaultPath: suggestionPath, defaultPath: suggestionPath,
filters: [{ filters: [{
name: file.mime_type ?? "All", name: file.mimeType,
extensions: [extension ?? "*"] extensions: [extension ?? "*"]
}, {name: "All", extensions: ["*"]}] }, {name: "All", extensions: ["*"]}]
}) });
} }
/** /**

@ -1,9 +1,11 @@
import {Inject, Injectable} from "@angular/core"; import {Inject, Injectable} from "@angular/core";
import {File} from "../../models/File"; import {File} from "../../../api/models/File";
import {invoke} from "@tauri-apps/api/tauri";
import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser";
import {SortKey} from "../../models/SortKey"; import {SortKey} from "../../models/SortKey";
import {FilterExpression} from "../../models/FilterExpression"; import {GenericFilter} from "../../models/GenericFilter";
import {MediarepApi} from "../../../api/Api";
import {mapMany, mapNew} from "../../../api/models/adaptors";
import {FileMetadata} from "../../../api/api-types/files";
@Injectable({ @Injectable({
@ -15,22 +17,22 @@ export class FileService {
@Inject(DomSanitizer) private sanitizer: DomSanitizer, @Inject(DomSanitizer) private sanitizer: DomSanitizer,
) { ) {
} }
public async getAllFiles(): Promise<File[]> { public async getAllFiles(): Promise<File[]> {
return await invoke<File[]>("plugin:mediarepo|get_all_files"); return MediarepApi.getAllFiles().then(mapMany(mapNew(File)));
} }
public async findFiles(filters: FilterExpression[], sortBy: SortKey[]): Promise<File[]> { public async findFiles(filters: GenericFilter[], sortBy: SortKey[]): Promise<File[]> {
let backendFilters = filters.map(f => f.toBackendType()); let backendFilters = filters.map(f => f.toBackendType());
return await invoke<File[]>("plugin:mediarepo|find_files", return MediarepApi.findFiles({filters: backendFilters, sortBy: sortBy.map(k => k.toBackendType())}).then(mapMany(mapNew(File)));
{ }
filters: backendFilters,
sortBy: sortBy.map(k => k.toBackendType()) public async getFileMetadata(id: number): Promise<FileMetadata> {
}); return MediarepApi.getFileMetadata({id});
} }
public async updateFileName(file: File, name: string): Promise<File> { public async updateFileName(id: number, name: string): Promise<FileMetadata> {
return await invoke<File>("plugin:mediarepo|update_file_name", return MediarepApi.updateFileName({id, name});
{id: file.id, name})
} }
/** /**
@ -42,7 +44,7 @@ export class FileService {
*/ */
public buildThumbnailUrl(file: File, height: number, width: number): SafeResourceUrl { public buildThumbnailUrl(file: File, height: number, width: number): SafeResourceUrl {
return this.sanitizer.bypassSecurityTrustResourceUrl( return this.sanitizer.bypassSecurityTrustResourceUrl(
`thumb://${file.hash}?width=${250}&height=${250}`) `thumb://${file.cd}?width=${250}&height=${250}`);
} }
/** /**
@ -52,7 +54,7 @@ export class FileService {
*/ */
public buildContentUrl(file: File): SafeResourceUrl { public buildContentUrl(file: File): SafeResourceUrl {
return this.sanitizer.bypassSecurityTrustResourceUrl( return this.sanitizer.bypassSecurityTrustResourceUrl(
`content://${file.hash}`) `content://${file.cd}`);
} }
/** /**
@ -62,8 +64,7 @@ export class FileService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async saveFile(file: File, targetPath: string) { public async saveFile(file: File, targetPath: string) {
await invoke("plugin:mediarepo|save_file_locally", await MediarepApi.saveFileLocally({id: file.id, path: targetPath});
{id: file.id, path: targetPath})
} }
/** /**
@ -72,7 +73,7 @@ export class FileService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async deleteThumbnails(file: File) { public async deleteThumbnails(file: File) {
await invoke("plugin:mediarepo|delete_thumbnails", {id: file.id}); await MediarepApi.deleteThumbnails({id: file.id});
} }
/** /**
@ -81,9 +82,8 @@ export class FileService {
* @returns {Promise<SafeResourceUrl>} * @returns {Promise<SafeResourceUrl>}
*/ */
public async readFile(file: File): Promise<SafeResourceUrl> { public async readFile(file: File): Promise<SafeResourceUrl> {
const data = await invoke<number[]>("plugin:mediarepo|read_file", const data = await MediarepApi.readFile({mimeType: file.mimeType, hash: file.cd});
{hash: file.hash, mimeType: file.mime_type}); const blob = new Blob([new Uint8Array(data)], {type: file.mimeType});
const blob = new Blob([new Uint8Array(data)], {type: file.mime_type});
const url = URL?.createObjectURL(blob); const url = URL?.createObjectURL(blob);
return this.sanitizer.bypassSecurityTrustResourceUrl(url); return this.sanitizer.bypassSecurityTrustResourceUrl(url);
} }

@ -1,8 +1,9 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {FileOsMetadata} from "../../models/FileOsMetadata";
import {invoke} from "@tauri-apps/api/tauri";
import {AddFileOptions} from "../../models/AddFileOptions"; import {AddFileOptions} from "../../models/AddFileOptions";
import {File} from "../../models/File"; import {File} from "../../../api/models/File";
import {MediarepApi} from "../../../api/Api";
import {mapNew,} from "../../../api/models/adaptors";
import {FileOsMetadata} from "../../../api/api-types/files";
@Injectable({ @Injectable({
providedIn: "root" providedIn: "root"
@ -18,8 +19,7 @@ export class ImportService {
* @returns {Promise<FileOsMetadata[]>} * @returns {Promise<FileOsMetadata[]>}
*/ */
public async resolvePathsToFiles(paths: string[]): Promise<FileOsMetadata[]> { public async resolvePathsToFiles(paths: string[]): Promise<FileOsMetadata[]> {
return await invoke<FileOsMetadata[]>( return MediarepApi.resolvePathsToFiles({paths});
"plugin:mediarepo|resolve_paths_to_files", {paths});
} }
/** /**
@ -29,7 +29,6 @@ export class ImportService {
* @returns {Promise<File>} * @returns {Promise<File>}
*/ */
public async addLocalFile(metadata: FileOsMetadata, options: AddFileOptions): Promise<File> { public async addLocalFile(metadata: FileOsMetadata, options: AddFileOptions): Promise<File> {
return await invoke<File>("plugin:mediarepo|add_local_file", return MediarepApi.addLocalFile({metadata, options}).then(mapNew(File));
{metadata, options});
} }
} }

@ -1,13 +1,13 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {Repository} from "../../models/Repository"; import {Repository} from "../../../api/models/Repository";
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {invoke} from "@tauri-apps/api/tauri";
import {listen} from "@tauri-apps/api/event"; import {listen} from "@tauri-apps/api/event";
import {Info} from "../../models/Info"; import {Info} from "../../models/Info";
import {ErrorBrokerService} from "../error-broker/error-broker.service"; import {ErrorBrokerService} from "../error-broker/error-broker.service";
import {FileService} from "../file/file.service";
import {RepositoryMetadata} from "../../models/RepositoryMetadata"; import {RepositoryMetadata} from "../../models/RepositoryMetadata";
import {SizeMetadata, SizeType} from "../../models/SizeMetadata"; import {MediarepApi} from "../../../api/Api";
import {mapMany, mapNew, mapOptional,} from "../../../api/models/adaptors";
import {SizeMetadata, SizeType} from "../../../api/api-types/repo";
@Injectable({ @Injectable({
providedIn: "root" providedIn: "root"
@ -17,8 +17,8 @@ export class RepositoryService {
public selectedRepository = new BehaviorSubject<Repository | undefined>( public selectedRepository = new BehaviorSubject<Repository | undefined>(
undefined); undefined);
constructor(private errorBroker: ErrorBrokerService, private fileService: FileService) { constructor(private errorBroker: ErrorBrokerService) {
this.registerListener() this.registerListener().catch(err => console.error(err));
} }
/// Registers the info listener /// Registers the info listener
@ -34,7 +34,7 @@ export class RepositoryService {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
public async checkDameonConfigured(): Promise<boolean> { public async checkDameonConfigured(): Promise<boolean> {
return await invoke<boolean>("plugin:mediarepo|has_executable"); return MediarepApi.hasExecutable();
} }
/** /**
@ -43,8 +43,7 @@ export class RepositoryService {
*/ */
public async loadRepositories() { public async loadRepositories() {
await this.loadSelectedRepository(); await this.loadSelectedRepository();
let repos = await invoke<Repository[]>( let repos = await MediarepApi.getRepositories().then(mapMany(mapNew(Repository)));
"plugin:mediarepo|get_repositories");
this.repositories.next(repos); this.repositories.next(repos);
} }
@ -54,7 +53,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async setRepository(repo: Repository) { public async setRepository(repo: Repository) {
const selectedRepo = this.selectedRepository.getValue() const selectedRepo = this.selectedRepository.getValue();
if (selectedRepo) { if (selectedRepo) {
if (selectedRepo.local) { if (selectedRepo.local) {
await this.closeSelectedRepository(); await this.closeSelectedRepository();
@ -70,7 +69,7 @@ export class RepositoryService {
} }
} }
await invoke("plugin:mediarepo|select_repository", {name: repo.name}); await MediarepApi.selectRepository({name: repo.name});
await this.loadRepositories(); await this.loadRepositories();
} }
@ -79,7 +78,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async disconnectSelectedRepository() { public async disconnectSelectedRepository() {
await invoke("plugin:mediarepo|disconnect_repository"); await MediarepApi.disconnectRepository();
await this.loadRepositories(); await this.loadRepositories();
} }
@ -88,7 +87,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async closeSelectedRepository() { public async closeSelectedRepository() {
await invoke("plugin:mediarepo|close_local_repository"); await MediarepApi.closeLocalRepository();
await this.loadRepositories(); await this.loadRepositories();
} }
@ -101,9 +100,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async addRepository(name: string, path: string | undefined, address: string | undefined, local: boolean) { public async addRepository(name: string, path: string | undefined, address: string | undefined, local: boolean) {
let repos = await invoke<Repository[]>( let repos = await MediarepApi.addRepository({name, path, address, local}).then(mapMany(mapNew(Repository)));
"plugin:mediarepo|add_repository",
{name, path, address, local});
this.repositories.next(repos); this.repositories.next(repos);
} }
@ -113,8 +110,7 @@ export class RepositoryService {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
public async checkDaemonRunning(address: string): Promise<boolean> { public async checkDaemonRunning(address: string): Promise<boolean> {
return await invoke<boolean>("plugin:mediarepo|check_daemon_running", return MediarepApi.checkDaemonRunning({address});
{address});
} }
/** /**
@ -123,8 +119,7 @@ export class RepositoryService {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
public async checkLocalRepositoryExists(path: string): Promise<boolean> { public async checkLocalRepositoryExists(path: string): Promise<boolean> {
return await invoke<boolean>( return await MediarepApi.checkLocalRepositoryExists({path});
"plugin:mediarepo|check_local_repository_exists", {path})
} }
/** /**
@ -133,7 +128,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async removeRepository(name: string): Promise<void> { public async removeRepository(name: string): Promise<void> {
await invoke("plugin:mediarepo|remove_repository", {name}); await MediarepApi.removeRepository({name});
await this.loadRepositories(); await this.loadRepositories();
} }
@ -143,7 +138,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async deleteRepository(name: string): Promise<void> { public async deleteRepository(name: string): Promise<void> {
await invoke("plugin:mediarepo|delete_repository", {name}); await MediarepApi.deleteRepository({name});
await this.removeRepository(name); await this.removeRepository(name);
} }
@ -153,7 +148,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async startDaemon(repoPath: string): Promise<void> { public async startDaemon(repoPath: string): Promise<void> {
await invoke("plugin:mediarepo|start_daemon", {repoPath}) return MediarepApi.startDaemon({repoPath});
} }
/** /**
@ -162,7 +157,7 @@ export class RepositoryService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async initRepository(repoPath: string): Promise<void> { public async initRepository(repoPath: string): Promise<void> {
await invoke("plugin:mediarepo|init_repository", {repoPath}); return MediarepApi.initRepository({repoPath});
} }
/** /**
@ -170,21 +165,20 @@ export class RepositoryService {
* @returns {Promise<RepositoryMetadata>} * @returns {Promise<RepositoryMetadata>}
*/ */
public async getRepositoryMetadata(): Promise<RepositoryMetadata> { public async getRepositoryMetadata(): Promise<RepositoryMetadata> {
return await invoke<RepositoryMetadata>("plugin:mediarepo|get_repo_metadata"); return MediarepApi.getRepositoryMetadata();
} }
/** /**
* Returns a specific size * Returns a specific size
* @param {SizeType} type
* @returns {Promise<SizeMetadata>} * @returns {Promise<SizeMetadata>}
* @param sizeType
*/ */
public async getSize(sizeType: SizeType): Promise<SizeMetadata> { public async getSize(sizeType: SizeType): Promise<SizeMetadata> {
return await invoke<SizeMetadata>("plugin:mediarepo|get_size", {sizeType}); return MediarepApi.getSize({sizeType});
} }
async loadSelectedRepository() { async loadSelectedRepository() {
let active_repo = await invoke<Repository | undefined>( let active_repo = await MediarepApi.getActiveRepository().then(mapOptional(mapNew(Repository)));
"plugin:mediarepo|get_active_repository");
this.selectedRepository.next(active_repo); this.selectedRepository.next(active_repo);
} }
} }

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

@ -1,9 +1,7 @@
import {Injectable} from '@angular/core'; import {Injectable} from "@angular/core";
import {BehaviorSubject} from "rxjs";
import {filter} from "rxjs/operators";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: "root"
}) })
export class SchedulingService { export class SchedulingService {
@ -51,8 +49,8 @@ export class SchedulingService {
} }
public async delay(time: number) { public async delay(time: number) {
return new Promise((res, rej) => { return new Promise((res) => {
setTimeout(res, time); setTimeout(res, time);
}) });
} }
} }

@ -1,11 +1,11 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {BehaviorSubject, Subscription} from "rxjs"; import {BehaviorSubject, Subscription} from "rxjs";
import {AppState} from "../../models/AppState"; import {AppState} from "../../models/AppState";
import {invoke} from "@tauri-apps/api/tauri";
import {FileService} from "../file/file.service"; import {FileService} from "../file/file.service";
import {RepositoryService} from "../repository/repository.service"; import {RepositoryService} from "../repository/repository.service";
import {TabState} from "../../models/TabState"; import {TabState} from "../../models/TabState";
import {debounceTime} from "rxjs/operators"; import {debounceTime} from "rxjs/operators";
import {MediarepApi} from "../../../api/Api";
@Injectable({ @Injectable({
providedIn: "root" providedIn: "root"
@ -38,12 +38,11 @@ export class StateService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async loadState() { public async loadState() {
let stateString = await invoke<string | undefined>( let stateString = await MediarepApi.getFrontendState();
"plugin:mediarepo|get_frontend_state");
let state; let state;
if (stateString) { if (stateString) {
state = AppState.deserializeJson(stateString, this.fileService) state = AppState.deserializeJson(stateString, this.fileService);
} else { } else {
state = new AppState(this.fileService); state = new AppState(this.fileService);
} }
@ -56,7 +55,7 @@ export class StateService {
this.tabSubscriptions.forEach(s => s.unsubscribe()); this.tabSubscriptions.forEach(s => s.unsubscribe());
tabs.forEach((tab) => this.subscribeToTab(tab)); tabs.forEach((tab) => this.subscribeToTab(tab));
this.stateChange.next(); this.stateChange.next();
}) });
} }
private subscribeToTab(tab: TabState) { private subscribeToTab(tab: TabState) {
@ -65,10 +64,10 @@ export class StateService {
this.tabSubscriptions.push(tab.sortKeys this.tabSubscriptions.push(tab.sortKeys
.subscribe(() => this.stateChange.next())); .subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push( this.tabSubscriptions.push(
tab.selectedFileHash.subscribe(() => this.stateChange.next())); tab.selectedCD.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push( this.tabSubscriptions.push(
tab.mode.subscribe(() => this.stateChange.next())) tab.mode.subscribe(() => this.stateChange.next()));
this.tabSubscriptions.push(tab.files.subscribe(() => this.stateChange.next())) this.tabSubscriptions.push(tab.files.subscribe(() => this.stateChange.next()));
} }
/** /**
@ -76,7 +75,6 @@ export class StateService {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async saveState(): Promise<void> { public async saveState(): Promise<void> {
await invoke("plugin:mediarepo|set_frontend_state", await MediarepApi.setFrontendState({state: this.state.value.serializeJson()});
{state: this.state.value.serializeJson()})
} }
} }

@ -1,8 +1,9 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {invoke} from "@tauri-apps/api/tauri"; import {Tag} from "../../../api/models/Tag";
import {Tag} from "../../models/Tag";
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {Namespace} from "../../models/Namespace"; import {Namespace} from "../../../api/models/Namespace";
import {mapMany, mapNew} from "../../../api/models/adaptors";
import {MediarepApi} from "../../../api/Api";
@Injectable({ @Injectable({
providedIn: "root" providedIn: "root"
@ -16,33 +17,28 @@ export class TagService {
} }
public async loadTags() { public async loadTags() {
const tags = await invoke<Tag[]>("plugin:mediarepo|get_all_tags"); const tags = await MediarepApi.getAllTags().then(mapMany(mapNew(Tag)));
this.tags.next(tags.map(t => new Tag(t.id, t.name, t.namespace))); this.tags.next(tags);
} }
public async loadNamespaces() { public async loadNamespaces() {
const namespaces = await invoke<Namespace[]>("plugin:mediarepo|get_all_namespaces"); const namespaces = await MediarepApi.getAllNamespaces().then(mapMany(mapNew(Namespace)));
this.namespaces.next(namespaces.map(n => new Namespace(n.id, n.name))); this.namespaces.next(namespaces);
} }
public async getTagsForFiles(hashes: string[]): Promise<Tag[]> { public async getTagsForFiles(cds: string[]): Promise<Tag[]> {
let tags: Tag[] = [] let tags: Tag[] = [];
if (hashes.length > 0) { if (cds.length > 0) {
tags = await invoke<Tag[]>("plugin:mediarepo|get_tags_for_files", tags = await MediarepApi.getTagsForFiles({cds}).then(mapMany(mapNew(Tag)));
{hashes});
} }
return tags.map(t => new Tag(t.id, t.name, t.namespace)); return tags;
} }
public async createTags(tags: string[]): Promise<Tag[]> { public async createTags(tags: string[]): Promise<Tag[]> {
const resultTags = await invoke<Tag[]>("plugin:mediarepo|create_tags", return MediarepApi.createTags({tags}).then(mapMany(mapNew(Tag)));
{tags});
return resultTags.map(t => new Tag(t.id, t.name, t.namespace));
} }
public async changeFileTags(fileId: number, addedTags: number[], removedTags: number[]): Promise<Tag[]> { public async changeFileTags(fileId: number, addedTags: number[], removedTags: number[]): Promise<Tag[]> {
const tags = await invoke<Tag[]>("plugin:mediarepo|change_file_tags", return MediarepApi.changeFileTags({id: fileId, addedTags, removedTags}).then(mapMany(mapNew(Tag)));
{id: fileId, addedTags, removedTags});
return tags.map(t => new Tag(t.id, t.name, t.namespace));
} }
} }

Loading…
Cancel
Save