Refactor and update to new API types

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

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

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

@ -25,7 +25,7 @@ features = [ "env-filter" ]
[dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "0c897acfd959c776fc10bd8fabdd2eb22b437be3"
rev = "91d8182548bfdb19f2de9afd8c29d5c8ebd48993"
features = [ "tauri-plugin" ]
[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">
<mat-tab-group #tabGroup (selectedTabChange)="this.onTabSelectionChange($event)" class="main-tab-group"
animationDuration="0">
<mat-tab [label]="this.selectedRepository? 'Repository' : 'Repositories'">
<mat-tab [label]="this.selectedRepository? 'RepositoryData' : 'Repositories'">
<app-repositories-tab></app-repositories-tab>
</mat-tab>
<mat-tab *ngFor="let tab of tabs">

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

@ -1,5 +1,5 @@
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 {MatTabChangeEvent, MatTabGroup} from "@angular/material/tabs";
import {TagService} from "../../services/tag/tag.service";
@ -60,7 +60,7 @@ export class CoreComponent {
this.addTab();
}
});
})
});
}
async loadRepoData() {

@ -46,7 +46,9 @@ import {
RepositoryModule
} from "../shared/repository/repository/repository.module";
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({

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

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

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

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

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

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

@ -1,5 +1,5 @@
import {Component, Input, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {Repository} from "../../../../models/Repository";
import {Repository} from "../../../../../api/models/Repository";
import {
RepositoryService
} from "../../../../services/repository/repository.service";
@ -52,7 +52,7 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
}
public isSelectedRepository(): boolean {
return this.repoService.selectedRepository.getValue()?.name === this.repository.name
return this.repoService.selectedRepository.getValue()?.name === this.repository.name;
}
public async removeRepository() {
@ -124,7 +124,7 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
await this.repoService.startDaemon(this.repository.path!);
this.daemonRunning = true;
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();
@ -156,6 +156,6 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
data: {
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', () => {
let component: RepositoryDetailsViewComponent;
let fixture: ComponentFixture<RepositoryDetailsViewComponent>;
describe("RepositoryDetailsViewComponent", () => {
let component: RepositoryDetailsViewComponent;
let fixture: ComponentFixture<RepositoryDetailsViewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RepositoryDetailsViewComponent ]
})
.compileComponents();
});
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RepositoryDetailsViewComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(RepositoryDetailsViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
beforeEach(() => {
fixture = TestBed.createComponent(RepositoryDetailsViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});

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

@ -15,7 +15,9 @@ import {MatMenuModule} from "@angular/material/menu";
import {
ContentAwareImageComponent
} 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 {
MetadataEntryComponent
} from "./metadata-entry/metadata-entry.component";

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

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

@ -3,7 +3,7 @@
<app-audio-viewer *ngIf="getContentType() === 'audio' && this.blobUrl" [blobUrl]="this.blobUrl!"></app-audio-viewer>
<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>
</div>
<app-busy-indicator></app-busy-indicator>

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

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

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

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

@ -1,8 +1,8 @@
import {
AfterContentInit, AfterViewInit,
Component, ElementRef,
AfterViewInit,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
OnInit,
@ -10,7 +10,7 @@ import {
SimpleChanges,
ViewChild
} from "@angular/core";
import {File} from "../../../../../models/File";
import {File} from "../../../../../../api/models/File";
import {FileService} from "../../../../../services/file/file.service";
import {SafeResourceUrl} from "@angular/platform-browser";
import {Selectable} from "../../../../../models/Selectable";
@ -48,7 +48,7 @@ export class FileGalleryComponent implements OnChanges, OnInit, AfterViewInit {
if (!this.selectedFile || this.files.indexOf(
this.selectedFile.data) < 0) {
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> {
if (changes["files"]) {
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(
f => f.hash === this.selectedFile?.data.hash);
f => f.id === this.selectedFile?.data.id);
if (!this.selectedFile || selectedIndex < 0) {
await this.onEntrySelect(
this.getPreselectedEntry() ?? this.entries[0])
this.getPreselectedEntry() ?? this.entries[0]);
} 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() {
if (this.selectedFile) {
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]);
} 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]);
} else {
await this.onEntrySelect(this.entries[0])
await this.onEntrySelect(this.entries[0]);
}
}

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

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

@ -2,7 +2,7 @@
borderRadius="0.25em"></app-content-aware-image>
<div *ngIf="this.getThumbnailSupported() && this.thumbUrl" class="file-icon-overlay">
<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 *ngIf="!this.getThumbnailSupported() || !this.thumbUrl" class="file-type-icon">
<ng-icon *ngIf="getFileType() === 'image'" name="mat-image"></ng-icon>

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

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

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

@ -6,7 +6,7 @@ import {
ValidationErrors,
Validators
} from "@angular/forms";
import {Repository} from "../../../../../models/Repository";
import {Repository} from "../../../../../../api/models/Repository";
import {
RepositoryService
} from "../../../../../services/repository/repository.service";
@ -20,7 +20,7 @@ import {MatDialog} from "@angular/material/dialog";
})
export class RepositoryFormComponent implements OnInit {
@Input() name: string = "My Repository";
@Input() name: string = "My RepositoryData";
@Input() repositoryType: "local" | "remote" = "local";
@Input() path: string = "";
@Input() address: string = "";
@ -120,7 +120,7 @@ export class RepositoryFormComponent implements OnInit {
"repositoryType")?.value ?? "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};
}

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

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

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

@ -2,7 +2,7 @@
<app-metadata-entry *ngIf="mode === 'read'" [attributeName]="attributeName">{{value}}</app-metadata-entry>
<mat-form-field *ngIf="mode === 'write'">
<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>
<button *ngIf="mode === 'write'" mat-button (click)="this.onSave()">
<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";
@Component({
@ -6,7 +14,7 @@ import {FormControl} from "@angular/forms";
templateUrl: "./editable-metadata-entry.component.html",
styleUrls: ["./editable-metadata-entry.component.scss"]
})
export class EditableMetadataEntryComponent implements OnInit{
export class EditableMetadataEntryComponent implements OnInit, OnChanges {
@Input() attributeName!: string;
@Input() value!: string | number;
@ -22,6 +30,12 @@ export class EditableMetadataEntryComponent implements OnInit{
this.formControl.setValue(this.value);
}
public ngOnChanges(changes: SimpleChanges): void {
if (changes["value"] || changes["mode"]) {
this.formControl.setValue(this.value);
}
}
public onSave(): void {
this.valueChangeEvent.emit(this.formControl.value);
this.mode = "read";

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

@ -1,21 +1,41 @@
import {Component, Input} from "@angular/core";
import {File} from "../../../../models/File";
import {
Component,
Input,
OnChanges,
OnInit,
SimpleChanges
} from "@angular/core";
import {File} from "../../../../../api/models/File";
import {FileService} from "../../../../services/file/file.service";
import {FileMetadata} from "../../../../../api/api-types/files";
@Component({
selector: "app-file-metadata",
templateUrl: "./file-metadata.component.html",
styleUrls: ["./file-metadata.component.scss"]
})
export class FileMetadataComponent {
export class FileMetadataComponent implements OnInit, OnChanges {
@Input() file!: File;
public fileMetadata: FileMetadata | undefined;
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) {
const newFile = await this.fileService.updateFileName(this.file, name);
this.file.name = newFile.name;
const newFile = await this.fileService.updateFileName(this.file.id, name);
if (this.fileMetadata) {
this.fileMetadata.name = newFile.name;
}
}
}

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

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

@ -7,10 +7,10 @@ import {
SimpleChanges
} from "@angular/core";
import {
FilterExpression,
GenericFilter,
OrFilterExpression,
SingleFilterExpression
} from "../../../../../../models/FilterExpression";
} from "../../../../../../models/GenericFilter";
import {TagQuery} from "../../../../../../models/TagQuery";
import {Selectable} from "../../../../../../models/Selectable";
@ -21,7 +21,7 @@ import {Selectable} from "../../../../../../models/Selectable";
})
export class TagFilterListItemComponent implements OnChanges {
@Input() expression!: Selectable<FilterExpression>;
@Input() expression!: Selectable<GenericFilter>;
@Output() removeClicked = new EventEmitter<TagFilterListItemComponent>();
@Output() querySelect = 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 {SortKey} from "../../../../../models/SortKey";
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 {FormControl} from "@angular/forms";
@Component({
selector: "app-sort-dialog",
@ -27,7 +26,7 @@ export class SortDialogComponent {
addNewSortKey() {
const sortKey = new SortKey("FileName", "Ascending", undefined);
this.sortEntries.push(sortKey)
this.sortEntries.push(sortKey);
}
public removeSortKey(sortKey: SortKey): void {
@ -40,7 +39,7 @@ export class SortDialogComponent {
}
public cancelSort(): void {
this.dialogRef.close()
this.dialogRef.close();
}
public onSortEntryDrop(event: CdkDragDrop<SortKey[]>): void {
@ -50,11 +49,11 @@ export class SortDialogComponent {
public updateAutocompleteSuggestions(value: string): void {
this.suggestedNamespaces = this.namespaces.sort(
(a, b) => this.compareSuggestionNamespaces(value, a.name, b.name))
.slice(0, 50)
(a, b) => SortDialogComponent.compareSuggestionNamespaces(value, a.name, b.name))
.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)) {
return -1;
} else if (!l.startsWith(query) && r.startsWith(query)) {
@ -64,7 +63,7 @@ export class SortDialogComponent {
} else if (l.length > r.length) {
return 1;
} 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 {MatMenuModule} from "@angular/material/menu";
import {FileMetadataComponent} from "./file-metadata/file-metadata.component";
import {
MetadataEntryComponent
} from "../app-common/metadata-entry/metadata-entry.component";
import {
EditableMetadataEntryComponent
} from "./file-metadata/editable-metadata-entry/editable-metadata-entry.component";

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

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

@ -24,7 +24,7 @@ export class AppState {
public async closeTab(uuid: number) {
const index = this.tabs.value.findIndex(t => t.uuid === uuid);
const tabs = this.tabs.value;
tabs.splice(index, 1)
tabs.splice(index, 1);
this.tabs.next(tabs);
}
@ -37,7 +37,7 @@ export class AppState {
appState.tabIdCounter = state.tabIdCounter;
appState.selectedTab.next(state.selectedTab);
return appState
return appState;
}
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 {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: TagQuery[] | TagQuery;
@ -11,14 +12,14 @@ export interface FilterExpression {
getDisplayName(): string;
clone(): FilterExpression;
clone(): GenericFilter;
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: TagQuery[] = [];
@ -27,7 +28,7 @@ export class OrFilterExpression implements FilterExpression {
}
public eq(value: any): boolean {
return this == value
return this == value;
}
public partiallyEq(value: any): boolean {
@ -41,7 +42,7 @@ export class OrFilterExpression implements FilterExpression {
public clone(): OrFilterExpression {
let tags = this.filter.map(
(t: TagQuery) => new TagQuery(t.tag, t.negate));
return new OrFilterExpression(tags)
return new OrFilterExpression(tags);
}
public queryList(): TagQuery[] {
@ -64,12 +65,12 @@ export class OrFilterExpression implements FilterExpression {
this.filter = newEntries.reverse();
}
public toBackendType(): RustEnum<TagQuery | TagQuery[]> {
return createRustEnum(this.filter_type, this.filter);
public toBackendType(): FilterExpression {
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: TagQuery;
@ -89,16 +90,16 @@ export class SingleFilterExpression implements FilterExpression {
return this.filter.getNormalizedTag();
}
public clone(): FilterExpression {
public clone(): GenericFilter {
return new SingleFilterExpression(
new TagQuery(this.filter.tag, this.filter.negate))
new TagQuery(this.filter.tag, this.filter.negate));
}
public queryList(): TagQuery[] {
return [this.filter]
return [this.filter];
}
public toBackendType(): RustEnum<TagQuery | TagQuery[]> {
return createRustEnum(this.filter_type, this.filter);
public toBackendType(): FilterExpression {
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 {
if (this.sortType == "Namespace") {
return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}`
return `${this.sortType} '${this.namespaceName}' ${this.sortDirection}`;
} else {
return `${this.sortType} ${this.sortDirection}`
return `${this.sortType} ${this.sortDirection}`;
}
}
@ -24,7 +24,7 @@ export class SortKey {
direction: this.sortDirection,
name: this.namespaceName
}
}
};
} else {
let returnObj: any = {};
returnObj[this.sortType] = this.sortDirection;

@ -1,25 +1,26 @@
import {BehaviorSubject} from "rxjs";
import {TabCategory} from "./TabCategory";
import {FileService} from "../services/file/file.service";
import {File} from "./File";
import {File} from "../../api/models/File";
import {
FilterExpression,
GenericFilter,
OrFilterExpression,
SingleFilterExpression
} from "./FilterExpression";
} from "./GenericFilter";
import {SortKey} from "./SortKey";
import {TagQuery} from "./TagQuery";
import {debounceTime} from "rxjs/operators";
import {mapNew} from "../../api/models/adaptors";
export class TabState {
public uuid: number;
public category: TabCategory;
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 files = new BehaviorSubject<File[]>([]);
public filters = new BehaviorSubject<FilterExpression[]>([]);
public filters = new BehaviorSubject<GenericFilter[]>([]);
public sortKeys = new BehaviorSubject<SortKey[]>(
[new SortKey("FileImportedTime",
"Ascending", undefined)]);
@ -46,7 +47,7 @@ export class TabState {
this.loading.next(false);
}
public setFilters(filters: FilterExpression[]) {
public setFilters(filters: GenericFilter[]) {
this.filters.next(filters);
}
@ -58,21 +59,21 @@ export class TabState {
const state = new TabState(dto.uuid, dto.category, fileService);
const filters = dto.filters.map((f: {filter: any, filter_type: any}) => {
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 {
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}) =>
new SortKey(s.sortType, s.sortDirection, s.namespaceName)
);
state.filters.next(filters);
state.sortKeys.next(sortKeys);
state.mode.next(dto.mode ?? "grid");
state.selectedFileHash.next(dto.selectedFileHash);
state.files.next(dto.files);
state.selectedCD.next(dto.selectedFileHash);
state.files.next(dto.files.map(mapNew(File)));
return state
return state;
}
public getDTO(): any {
@ -82,8 +83,8 @@ export class TabState {
filters: this.filters.value,
sortKeys: this.sortKeys.value,
mode: this.mode.value,
selectedFileHash: this.selectedFileHash.value,
files: this.files.value,
selectedFileHash: this.selectedCD.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;
constructor() {
this.registerListener();
this.registerListener().catch(err => console.error(err));
}
async registerListener() {
const _unlisten = await listen("error", event => {
const payload: any = event.payload;
if (payload.message) {
this.showError(payload)
this.showError(payload);
} else {
this.showError(payload.toString())
this.showError(payload.toString());
}
})
});
}
showInfo(info: string) {

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

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

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

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

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

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

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

Loading…
Cancel
Save