diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock
index ef10d76..ae23ff6 100644
--- a/mediarepo-ui/src-tauri/Cargo.lock
+++ b/mediarepo-ui/src-tauri/Cargo.lock
@@ -1473,8 +1473,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mediarepo-api"
-version = "0.11.1"
-source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=d56e2c7406e9b8b15b22bc2714eebff00f63d778#d56e2c7406e9b8b15b22bc2714eebff00f63d778"
+version = "0.12.0"
+source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=16b892eab0b3446198601a8f5829d0bd54d2efdf#16b892eab0b3446198601a8f5829d0bd54d2efdf"
dependencies = [
"async-trait",
"chrono",
@@ -2309,9 +2309,9 @@ dependencies = [
[[package]]
name = "rmp-ipc"
-version = "0.10.0"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc14d76d6718bdbdf12596ed68f5db46f4fa46fca0bd3acf85f7e347788df3c2"
+checksum = "573eb3e2e1008f550b7b5a53053d5ed8378fbda57731fa3739c3cfb18ad667f6"
dependencies = [
"async-trait",
"byteorder",
diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml
index 6e040b8..6f78e24 100644
--- a/mediarepo-ui/src-tauri/Cargo.toml
+++ b/mediarepo-ui/src-tauri/Cargo.toml
@@ -30,7 +30,7 @@ features = ["env-filter"]
[dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git"
-rev = "d56e2c7406e9b8b15b22bc2714eebff00f63d778"
+rev = "16b892eab0b3446198601a8f5829d0bd54d2efdf"
features = ["tauri-plugin"]
[features]
diff --git a/mediarepo-ui/src-tauri/tauri.conf.json b/mediarepo-ui/src-tauri/tauri.conf.json
index 66e8a82..f98a965 100644
--- a/mediarepo-ui/src-tauri/tauri.conf.json
+++ b/mediarepo-ui/src-tauri/tauri.conf.json
@@ -13,7 +13,7 @@
"bundle": {
"active": true,
"targets": "all",
- "identifier": "com.tauri.dev",
+ "identifier": "net.trivernis.mediarepo",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -24,8 +24,8 @@
"resources": [],
"externalBin": [],
"copyright": "",
- "category": "DeveloperTool",
- "shortDescription": "",
+ "category": "Productivity",
+ "shortDescription": "A media mangagement tool",
"longDescription": "",
"deb": {
"depends": [],
diff --git a/mediarepo-ui/src/app/app.module.ts b/mediarepo-ui/src/app/app.module.ts
index a4cadde..e5a0467 100644
--- a/mediarepo-ui/src/app/app.module.ts
+++ b/mediarepo-ui/src/app/app.module.ts
@@ -48,19 +48,23 @@ import {ConfirmDialogComponent} from './components/confirm-dialog/confirm-dialog
import {FilesTabSidebarComponent} from './pages/home/files-tab/files-tab-sidebar/files-tab-sidebar.component';
import {MatExpansionModule} from "@angular/material/expansion";
import {TagItemComponent} from './components/tag-item/tag-item.component';
-import { FileEditComponent } from './components/file-edit/file-edit.component';
-import { ImportTabComponent } from './pages/home/import-tab/import-tab.component';
-import { ImportTabSidebarComponent } from './pages/home/import-tab/import-tab-sidebar/import-tab-sidebar.component';
-import { NativeFileSelectComponent } from './components/inputs/native-file-select/native-file-select.component';
-import { FilesystemImportComponent } from './pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component';
+import {FileEditComponent} from './components/file-edit/file-edit.component';
+import {ImportTabComponent} from './pages/home/import-tab/import-tab.component';
+import {ImportTabSidebarComponent} from './pages/home/import-tab/import-tab-sidebar/import-tab-sidebar.component';
+import {NativeFileSelectComponent} from './components/inputs/native-file-select/native-file-select.component';
+import {FilesystemImportComponent} from './pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component';
import {MatCheckboxModule} from "@angular/material/checkbox";
-import { FilterDialogComponent } from './components/file-search/filter-dialog/filter-dialog.component';
-import { TagFilterListItemComponent } from './components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component';
-import { TagInputComponent } from './components/inputs/tag-input/tag-input.component';
-import { ContextMenuComponent } from './components/context-menu/context-menu.component';
-import { FileContextMenuComponent } from './components/context-menu/file-context-menu/file-context-menu.component';
-import { ContentViewerComponent } from './components/file-gallery/content-viewer/content-viewer.component';
-import { ImageViewerComponent } from './components/file-gallery/content-viewer/image-viewer/image-viewer.component';
+import {FilterDialogComponent} from './components/file-search/filter-dialog/filter-dialog.component';
+import {TagFilterListItemComponent} from './components/file-search/filter-dialog/tag-filter-list-item/tag-filter-list-item.component';
+import {TagInputComponent} from './components/inputs/tag-input/tag-input.component';
+import {ContextMenuComponent} from './components/context-menu/context-menu.component';
+import {FileContextMenuComponent} from './components/context-menu/file-context-menu/file-context-menu.component';
+import {ContentViewerComponent} from './components/file-gallery/content-viewer/content-viewer.component';
+import {ImageViewerComponent} from './components/file-gallery/content-viewer/image-viewer/image-viewer.component';
+import {VideoViewerComponent} from './components/file-gallery/content-viewer/video-viewer/video-viewer.component';
+import {HttpClientModule} from "@angular/common/http";
+import { AudioViewerComponent } from './components/file-gallery/content-viewer/audio-viewer/audio-viewer.component';
+import { BusyIndicatorComponent } from './components/busy-indicator/busy-indicator.component';
@NgModule({
declarations: [
@@ -92,43 +96,47 @@ import { ImageViewerComponent } from './components/file-gallery/content-viewer/i
FileContextMenuComponent,
ContentViewerComponent,
ImageViewerComponent,
+ VideoViewerComponent,
+ AudioViewerComponent,
+ BusyIndicatorComponent,
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ BrowserAnimationsModule,
+ MatCardModule,
+ MatListModule,
+ MatButtonModule,
+ MatToolbarModule,
+ MatSnackBarModule,
+ MatFormFieldModule,
+ MatInputModule,
+ ReactiveFormsModule,
+ MatSidenavModule,
+ MatGridListModule,
+ MatProgressBarModule,
+ MatPaginatorModule,
+ ScrollingModule,
+ MatChipsModule,
+ MatIconModule,
+ MatAutocompleteModule,
+ MatTabsModule,
+ FlexModule,
+ GridModule,
+ MatRippleModule,
+ MatDialogModule,
+ MatSelectModule,
+ MatProgressSpinnerModule,
+ BlockUIModule,
+ PanelModule,
+ DragDropModule,
+ MatSliderModule,
+ MatTooltipModule,
+ MatMenuModule,
+ MatExpansionModule,
+ MatCheckboxModule,
+ HttpClientModule,
],
- imports: [
- BrowserModule,
- AppRoutingModule,
- BrowserAnimationsModule,
- MatCardModule,
- MatListModule,
- MatButtonModule,
- MatToolbarModule,
- MatSnackBarModule,
- MatFormFieldModule,
- MatInputModule,
- ReactiveFormsModule,
- MatSidenavModule,
- MatGridListModule,
- MatProgressBarModule,
- MatPaginatorModule,
- ScrollingModule,
- MatChipsModule,
- MatIconModule,
- MatAutocompleteModule,
- MatTabsModule,
- FlexModule,
- GridModule,
- MatRippleModule,
- MatDialogModule,
- MatSelectModule,
- MatProgressSpinnerModule,
- BlockUIModule,
- PanelModule,
- DragDropModule,
- MatSliderModule,
- MatTooltipModule,
- MatMenuModule,
- MatExpansionModule,
- MatCheckboxModule,
- ],
providers: [],
bootstrap: [AppComponent]
})
diff --git a/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.html b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.html
new file mode 100644
index 0000000..36c45b3
--- /dev/null
+++ b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.scss b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.scss
new file mode 100644
index 0000000..93d7771
--- /dev/null
+++ b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.scss
@@ -0,0 +1,31 @@
+.busy-indicator-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ display: flex;
+ z-index: 998;
+
+ mat-progress-spinner {
+ z-index: 999;
+ margin: auto;
+ }
+}
+
+.busy-indicator-overlay.blur {
+ background-color: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(5px);
+}
+
+.busy-indicator-overlay.darken {
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+::ng-deep app-busy-indicator {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: block;
+}
diff --git a/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.spec.ts b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.spec.ts
new file mode 100644
index 0000000..198dbe9
--- /dev/null
+++ b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BusyIndicatorComponent } from './busy-indicator.component';
+
+describe('BusyIndicatorComponent', () => {
+ let component: BusyIndicatorComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ BusyIndicatorComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BusyIndicatorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.ts b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.ts
new file mode 100644
index 0000000..90f3be2
--- /dev/null
+++ b/mediarepo-ui/src/app/components/busy-indicator/busy-indicator.component.ts
@@ -0,0 +1,48 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {ProgressSpinnerMode} from "@angular/material/progress-spinner";
+
+@Component({
+ selector: 'app-busy-indicator',
+ templateUrl: './busy-indicator.component.html',
+ styleUrls: ['./busy-indicator.component.scss']
+})
+export class BusyIndicatorComponent {
+
+ @Input() busy: boolean = false;
+ @Input() blurBackground: boolean = false;
+ @Input() darkenBackground: boolean = false;
+ @Input() mode: ProgressSpinnerMode = "indeterminate";
+ @Input() value: number | undefined;
+
+ constructor() { }
+
+ public setBusy(busy: boolean) {
+ this.busy = busy;
+ }
+
+ public wrapOperation(operation: Function): T | undefined {
+ this.setBusy(true)
+ try {
+ const result = operation();
+ this.setBusy(false);
+ return result;
+ } catch {
+ return undefined;
+ } finally {
+ this.setBusy(false);
+ }
+ }
+
+ public async wrapAsyncOperation(operation: Function): Promise {
+ this.setBusy(true)
+ try {
+ const result = await operation();
+ this.setBusy(false);
+ return result;
+ } catch {
+ return undefined;
+ } finally {
+ this.setBusy(false);
+ }
+ }
+}
diff --git a/mediarepo-ui/src/app/components/context-menu/file-context-menu/file-context-menu.component.ts b/mediarepo-ui/src/app/components/context-menu/file-context-menu/file-context-menu.component.ts
index 0e349b0..0aa41d3 100644
--- a/mediarepo-ui/src/app/components/context-menu/file-context-menu/file-context-menu.component.ts
+++ b/mediarepo-ui/src/app/components/context-menu/file-context-menu/file-context-menu.component.ts
@@ -5,6 +5,7 @@ import {clipboard, dialog} from "@tauri-apps/api";
import {FileService} from "../../../services/file/file.service";
import {ErrorBrokerService} from "../../../services/error-broker/error-broker.service";
import {downloadDir} from "@tauri-apps/api/path";
+import {FileHelper} from "../../../services/file/file.helper";
@Component({
selector: 'app-file-context-menu',
@@ -29,17 +30,8 @@ export class FileContextMenuComponent {
}
public async exportFile(): Promise {
- let extension;
- if (this.file.mime_type) {
- extension = FileContextMenuComponent.getExtensionForMime(this.file.mime_type);
- }
- const downloadDirectory = await downloadDir();
- const suggestionPath = downloadDirectory + this.file.hash + "." + extension;
+ const path = await FileHelper.getFileDownloadLocation(this.file)
- const path = await dialog.save({
- defaultPath: suggestionPath,
- filters: [{name: this.file.mime_type ?? "All", extensions: [extension ?? "*"]}, {name: "All", extensions: ["*"]}]
- });
if (path) {
try {
await this.fileService.saveFile(this.file, path);
@@ -48,25 +40,4 @@ export class FileContextMenuComponent {
}
}
}
-
- /**
- * Returns the extension for a mime type
- * @param {string} mime
- * @returns {string | undefined}
- * @private
- */
- private static getExtensionForMime(mime: string): string | undefined {
- let parts = mime.split("/");
-
- if (parts.length === 2) {
- const type = parts[0];
- const subtype = parts[1];
- return FileContextMenuComponent.convertMimeSubtypeToExtension(subtype);
- }
- return undefined;
- }
-
- private static convertMimeSubtypeToExtension(subtype: string): string {
- return subtype;
- }
}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.html b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.html
new file mode 100644
index 0000000..1172d35
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.html
@@ -0,0 +1,4 @@
+
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.scss b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.scss
new file mode 100644
index 0000000..52ae794
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.scss
@@ -0,0 +1,8 @@
+.audio-container {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ audio {
+ margin: auto;
+ }
+}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.spec.ts b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.spec.ts
new file mode 100644
index 0000000..1ef6ca2
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AudioViewerComponent } from './audio-viewer.component';
+
+describe('AudioViewerComponent', () => {
+ let component: AudioViewerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ AudioViewerComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AudioViewerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.ts b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.ts
new file mode 100644
index 0000000..e3ce219
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/audio-viewer/audio-viewer.component.ts
@@ -0,0 +1,14 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {SafeResourceUrl} from "@angular/platform-browser";
+
+@Component({
+ selector: 'app-audio-viewer',
+ templateUrl: './audio-viewer.component.html',
+ styleUrls: ['./audio-viewer.component.scss']
+})
+export class AudioViewerComponent {
+
+ @Input() blobUrl!: SafeResourceUrl;
+
+ constructor() { }
+}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.html b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.html
index 14cb1f4..c64a703 100644
--- a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.html
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.html
@@ -1 +1,9 @@
-
+
+
+
+
+
+ Unsupported content type {{this.file.mime_type}}
+
+
+
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.scss b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.scss
index aa664cb..d9b4e79 100644
--- a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.scss
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.scss
@@ -1,4 +1,19 @@
-app-image-viewer {
+app-image-viewer, app-video-viewer, app-audio-viewer {
width: 100%;
height: 100%;
}
+
+.download-prompt {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ button {
+ margin: 1em 0 auto;
+ align-self: center;
+ }
+ span {
+ margin: auto 0 0;
+ align-self: center;
+ }
+}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.ts b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.ts
index 0b966f2..af22e9c 100644
--- a/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.ts
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/content-viewer.component.ts
@@ -1,25 +1,68 @@
-import {Component, Input, OnInit} from '@angular/core';
+import {
+ AfterContentInit, AfterViewInit,
+ Component,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ SimpleChanges, ViewChild
+} from '@angular/core';
import {SafeResourceUrl} from "@angular/platform-browser";
+import {File} from "../../../models/File";
+import {FileService} from "../../../services/file/file.service";
+import {FileHelper} from "../../../services/file/file.helper";
+import {ErrorBrokerService} from "../../../services/error-broker/error-broker.service";
+import {BusyIndicatorComponent} from "../../busy-indicator/busy-indicator.component";
-type ContentType = "image" | "video" | "text" | "other";
+type ContentType = "image" | "video" | "audio" | "other";
@Component({
selector: 'app-content-viewer',
templateUrl: './content-viewer.component.html',
styleUrls: ['./content-viewer.component.scss']
})
-export class ContentViewerComponent {
+export class ContentViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
+ @Input() file!: File;
- @Input() contentUrl!: SafeResourceUrl | string;
- @Input() mimeType: string | undefined;
+ public contentUrl: SafeResourceUrl | undefined;
+ public blobUrl: SafeResourceUrl | undefined;
- constructor() { }
+ @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent;
+
+ constructor(
+ private errorBroker: ErrorBrokerService,
+ private fileService: FileService
+ ) {
+ }
+
+ public async ngAfterViewInit() {
+ if (["audio", "video"].includes(this.getContentType())) {
+ await this.loadBlobUrl();
+ } else {
+ this.contentUrl = this.fileService.buildContentUrl(this.file);
+ }
+ }
+
+ public async ngOnChanges(changes:SimpleChanges) {
+ if (changes["file"]) {
+ if (["audio", "video"].includes(this.getContentType()) && this.busyIndicator) {
+ await this.loadBlobUrl();
+ } else {
+ this.contentUrl = this.fileService.buildContentUrl(this.file);
+ this.unloadBlobUrl();
+ }
+ }
+ }
+
+ public ngOnDestroy(): void {
+ this.unloadBlobUrl();
+ }
public getContentType(): ContentType {
- if (!this.mimeType) {
+ if (!this.file.mime_type) {
return "other";
}
- let mimeParts = this.mimeType.split("/");
+ let mimeParts = this.file.mime_type.split("/");
const type = mimeParts.shift() ?? "other";
const subtype = mimeParts.shift() ?? "*";
@@ -28,10 +71,40 @@ export class ContentViewerComponent {
return "image";
case "video":
return "video";
- case "text":
- return "text";
+ case "audio":
+ return "audio";
default:
return "other";
}
}
+
+ public async downloadContent() {
+ const path = await FileHelper.getFileDownloadLocation(this.file)
+
+ if (path) {
+ try {
+ await this.fileService.saveFile(this.file, path);
+ } catch (err) {
+ this.errorBroker.showError(err);
+ }
+ }
+ }
+
+ public async loadBlobUrl(): Promise {
+ await this.busyIndicator.wrapAsyncOperation(async () => {
+ const startId = this.file.id;
+ this.unloadBlobUrl();
+ const url = await this.fileService.readFile(this.file);
+ if (startId === this.file.id) {
+ this.blobUrl = url;
+ }
+ });
+ }
+
+ private unloadBlobUrl() {
+ if (this.blobUrl) {
+ URL?.revokeObjectURL(this.blobUrl as string);
+ this.blobUrl = undefined;
+ }
+ }
}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.html b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.html
new file mode 100644
index 0000000..ca7c555
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.html
@@ -0,0 +1,3 @@
+
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.scss b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.scss
new file mode 100644
index 0000000..b1f16a5
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.scss
@@ -0,0 +1,4 @@
+video {
+ height: 100%;
+ width: 100%;
+}
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.spec.ts b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.spec.ts
new file mode 100644
index 0000000..cccd6c0
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { VideoViewerComponent } from './video-viewer.component';
+
+describe('VideoViewerComponent', () => {
+ let component: VideoViewerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ VideoViewerComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(VideoViewerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.ts b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.ts
new file mode 100644
index 0000000..a667dd8
--- /dev/null
+++ b/mediarepo-ui/src/app/components/file-gallery/content-viewer/video-viewer/video-viewer.component.ts
@@ -0,0 +1,14 @@
+import {
+ Component,
+ Input,
+} from '@angular/core';
+import {SafeResourceUrl} from "@angular/platform-browser";
+
+@Component({
+ selector: 'app-video-viewer',
+ templateUrl: './video-viewer.component.html',
+ styleUrls: ['./video-viewer.component.scss']
+})
+export class VideoViewerComponent {
+ @Input() blobUrl!: SafeResourceUrl;
+}
diff --git a/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.html b/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.html
index b149aa6..d7aaba4 100644
--- a/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.html
+++ b/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.html
@@ -4,10 +4,8 @@
diff --git a/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.scss b/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.scss
index 83fccf1..30dd6d9 100644
--- a/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.scss
+++ b/mediarepo-ui/src/app/components/file-gallery/file-gallery.component.scss
@@ -32,9 +32,10 @@ app-file-gallery-entry {
overflow: hidden;
}
-app-content-aware-image {
+app-content-viewer{
height: 100%;
width: 100%;
+ display: block;
}
.close-button {
diff --git a/mediarepo-ui/src/app/pages/home/files-tab/files-tab.component.html b/mediarepo-ui/src/app/pages/home/files-tab/files-tab.component.html
index 2846292..19d1273 100644
--- a/mediarepo-ui/src/app/pages/home/files-tab/files-tab.component.html
+++ b/mediarepo-ui/src/app/pages/home/files-tab/files-tab.component.html
@@ -5,16 +5,17 @@
[selectedFiles]="this.selectedFiles">
-
-
-
-
-
+
+
+
+
diff --git a/mediarepo-ui/src/app/pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component.ts b/mediarepo-ui/src/app/pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component.ts
index a64365c..77dcfbe 100644
--- a/mediarepo-ui/src/app/pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component.ts
+++ b/mediarepo-ui/src/app/pages/home/import-tab/import-tab-sidebar/filesystem-import/filesystem-import.component.ts
@@ -22,6 +22,7 @@ export class FilesystemImportComponent {
public filters: DialogFilter[] = [
{name: "Images", extensions: ["png", "jpg", "jpeg", "webp", "bmp"]},
{name: "Videos", extensions: ["mp4", "mkv", "wmv", "avi", "webm"]},
+ {name: "Audio", extensions: ["mp3", "ogg", "wav", "flac", "aac"]},
{name: "Documents", extensions: ["pdf", "doc", "docx", "odf"]},
{name: "Text", extensions: ["txt", "md"]},
{name: "All", extensions: ["*"]}
diff --git a/mediarepo-ui/src/app/pages/home/repositories-tab/repositories-tab.component.scss b/mediarepo-ui/src/app/pages/home/repositories-tab/repositories-tab.component.scss
index 0091879..61c3481 100644
--- a/mediarepo-ui/src/app/pages/home/repositories-tab/repositories-tab.component.scss
+++ b/mediarepo-ui/src/app/pages/home/repositories-tab/repositories-tab.component.scss
@@ -26,3 +26,7 @@
overflow-y: auto;
height: calc(100% - 5em);
}
+
+app-repository-card{
+ position: relative;
+}
diff --git a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html
index 30e43a3..0f1c07b 100644
--- a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html
+++ b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.html
@@ -1,31 +1,37 @@
-
- {{repository.name}}
-
-
{{this.getDaemonStatusText()}}
-
-
- {{repository.path!}}
- {{repository.address}}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {{repository.name}}
+
+
{{this.getDaemonStatusText()}}
+
+
+ {{repository.path!}}
+ {{repository.address}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.ts b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.ts
index 7623bfb..4544ffe 100644
--- a/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.ts
+++ b/mediarepo-ui/src/app/pages/home/repositories-tab/repository-card/repository-card.component.ts
@@ -1,10 +1,11 @@
-import {Component, Input, OnDestroy, OnInit} from '@angular/core';
+import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {Repository} from "../../../../models/Repository";
import {RepositoryService} from "../../../../services/repository/repository.service";
import {Router} from "@angular/router";
import {ErrorBrokerService} from "../../../../services/error-broker/error-broker.service";
import {MatDialog} from "@angular/material/dialog";
import {ConfirmDialogComponent} from "../../../../components/confirm-dialog/confirm-dialog.component";
+import {BusyIndicatorComponent} from "../../../../components/busy-indicator/busy-indicator.component";
@Component({
selector: 'app-repository-card',
@@ -14,6 +15,7 @@ import {ConfirmDialogComponent} from "../../../../components/confirm-dialog/conf
export class RepositoryCardComponent implements OnInit, OnDestroy {
@Input() repository!: Repository;
+ @ViewChild(BusyIndicatorComponent) busyIndicator!: BusyIndicatorComponent;
public daemonRunning: boolean = false;
@@ -95,11 +97,13 @@ export class RepositoryCardComponent implements OnInit, OnDestroy {
}
public async selectRepository() {
+ this.busyIndicator.setBusy(true);
try {
await this.repoService.setRepository(this.repository);
} catch (err) {
this.errorBroker.showError(err);
}
+ this.busyIndicator.setBusy(false);
}
async checkRemoteRepositoryStatus() {
diff --git a/mediarepo-ui/src/app/services/file/file.helper.ts b/mediarepo-ui/src/app/services/file/file.helper.ts
new file mode 100644
index 0000000..f99e72a
--- /dev/null
+++ b/mediarepo-ui/src/app/services/file/file.helper.ts
@@ -0,0 +1,49 @@
+import {downloadDir} from "@tauri-apps/api/path";
+import {dialog} from "@tauri-apps/api";
+import {File} from "../../models/File";
+
+export class FileHelper {
+
+ /**
+ * Opens a dialog to get a download location for the given file
+ * @param {File} file
+ */
+ public static async getFileDownloadLocation(file: File): Promise
{
+ let extension;
+
+ if (file.mime_type) {
+ extension = FileHelper.getExtensionForMime(file.mime_type);
+ }
+ const downloadDirectory = await downloadDir();
+ const suggestionPath = downloadDirectory + file.hash + "." + extension;
+
+ return await dialog.save({
+ defaultPath: suggestionPath,
+ filters: [{
+ name: file.mime_type ?? "All",
+ extensions: [extension ?? "*"]
+ }, {name: "All", extensions: ["*"]}]
+ })
+ }
+
+ /**
+ * Returns the extension for a mime type
+ * @param {string} mime
+ * @returns {string | undefined}
+ * @private
+ */
+ public static getExtensionForMime(mime: string): string | undefined {
+ let parts = mime.split("/");
+
+ if (parts.length === 2) {
+ const type = parts[0];
+ const subtype = parts[1];
+ return FileHelper.convertMimeSubtypeToExtension(subtype);
+ }
+ return undefined;
+ }
+
+ private static convertMimeSubtypeToExtension(subtype: string): string {
+ return subtype;
+ }
+}
diff --git a/mediarepo-ui/src/app/services/file/file.service.ts b/mediarepo-ui/src/app/services/file/file.service.ts
index 274b6d4..8537a97 100644
--- a/mediarepo-ui/src/app/services/file/file.service.ts
+++ b/mediarepo-ui/src/app/services/file/file.service.ts
@@ -8,6 +8,9 @@ import {TagQuery} from "../../models/TagQuery";
import {SortKey} from "../../models/SortKey";
import {RepositoryService} from "../repository/repository.service";
import {FilterExpression} from "../../models/FilterExpression";
+import {HttpClient} from "@angular/common/http";
+import {map} from "rxjs/operators";
+import {http} from "@tauri-apps/api";
@Injectable({
providedIn: 'root'
@@ -17,7 +20,11 @@ export class FileService {
displayedFiles = new BehaviorSubject([]);
thumbnailCache: {[key: number]: Thumbnail[]} = {};
- constructor(@Inject(DomSanitizer) private sanitizer: DomSanitizer, private repoService: RepositoryService) {
+ constructor(
+ @Inject(DomSanitizer) private sanitizer: DomSanitizer,
+ private repoService: RepositoryService,
+ private http: HttpClient,
+ ) {
repoService.selectedRepository.subscribe(_ => this.clearCache());
}
@@ -79,4 +86,16 @@ export class FileService {
public async deleteThumbnails(file: File) {
await invoke("plugin:mediarepo|delete_thumbnails", {id: file.id});
}
+
+ /**
+ * Reads the contents of a file and returns the object url for it
+ * @param {File} file
+ * @returns {Promise}
+ */
+ public async readFile(file: File): Promise {
+ const data = await invoke("plugin:mediarepo|read_file", {hash: file.hash, mimeType: file.mime_type});
+ const blob = new Blob([new Uint8Array(data)], {type: file.mime_type});
+ const url = URL?.createObjectURL(blob);
+ return this.sanitizer.bypassSecurityTrustResourceUrl(url);
+ }
}