From aeac1e2e364ebc047f1c13b13b327e90aa5dd3d7 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 22 Oct 2021 22:36:03 +0200 Subject: [PATCH] Add tag display and file search Signed-off-by: trivernis --- mediarepo-ui/src-tauri/Cargo.lock | 33 ++----- mediarepo-ui/src-tauri/Cargo.toml | 4 +- mediarepo-ui/src-tauri/src/commands/files.rs | 91 +++++++++++++------ mediarepo-ui/src-tauri/src/commands/mod.rs | 12 ++- mediarepo-ui/src-tauri/src/commands/repo.rs | 33 +++++-- mediarepo-ui/src-tauri/src/commands/tags.rs | 21 +++++ mediarepo-ui/src-tauri/src/context.rs | 10 +- mediarepo-ui/src-tauri/src/ipc/mod.rs | 26 ++++-- mediarepo-ui/src-tauri/src/main.rs | 23 ++++- mediarepo-ui/src/app/app.module.ts | 42 +++++---- .../file-grid/file-grid.component.ts | 14 ++- mediarepo-ui/src/app/models/Tag.ts | 5 + .../src/app/pages/home/home.component.html | 25 ++++- .../src/app/pages/home/home.component.scss | 4 + .../src/app/pages/home/home.component.ts | 47 +++++++++- .../src/app/services/file/file.service.ts | 5 + .../src/app/services/tag/tag.service.spec.ts | 16 ++++ .../src/app/services/tag/tag.service.ts | 15 +++ 18 files changed, 317 insertions(+), 109 deletions(-) create mode 100644 mediarepo-ui/src-tauri/src/commands/tags.rs create mode 100644 mediarepo-ui/src/app/models/Tag.ts create mode 100644 mediarepo-ui/src/app/services/tag/tag.service.spec.ts create mode 100644 mediarepo-ui/src/app/services/tag/tag.service.ts diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 5dfc3a1..700e015 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -55,7 +55,7 @@ version = "0.1.0" dependencies = [ "directories", "mediarepo", - "rmp-ipc 0.4.3", + "rmp-ipc", "serde", "serde_json", "tauri", @@ -1840,7 +1840,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo" version = "0.1.0" -source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=57308089d58706ee1438f6b55d53691e9e08ce6b#57308089d58706ee1438f6b55d53691e9e08ce6b" +source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=ce93a5793348a05052b324562730d97f5c6e2128#ce93a5793348a05052b324562730d97f5c6e2128" dependencies = [ "log", "mediarepo-core", @@ -1851,14 +1851,14 @@ dependencies = [ [[package]] name = "mediarepo-core" version = "0.1.0" -source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=57308089d58706ee1438f6b55d53691e9e08ce6b#57308089d58706ee1438f6b55d53691e9e08ce6b" +source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=ce93a5793348a05052b324562730d97f5c6e2128#ce93a5793348a05052b324562730d97f5c6e2128" dependencies = [ "base64", "futures", "image", "multibase", "multihash", - "rmp-ipc 0.6.0", + "rmp-ipc", "sea-orm", "serde", "sqlx", @@ -1871,7 +1871,7 @@ dependencies = [ [[package]] name = "mediarepo-database" version = "0.2.0" -source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=57308089d58706ee1438f6b55d53691e9e08ce6b#57308089d58706ee1438f6b55d53691e9e08ce6b" +source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=ce93a5793348a05052b324562730d97f5c6e2128#ce93a5793348a05052b324562730d97f5c6e2128" dependencies = [ "chrono", "mediarepo-core", @@ -1882,7 +1882,7 @@ dependencies = [ [[package]] name = "mediarepo-model" version = "0.1.0" -source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=57308089d58706ee1438f6b55d53691e9e08ce6b#57308089d58706ee1438f6b55d53691e9e08ce6b" +source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=ce93a5793348a05052b324562730d97f5c6e2128#ce93a5793348a05052b324562730d97f5c6e2128" dependencies = [ "chrono", "mediarepo-core", @@ -1898,7 +1898,7 @@ dependencies = [ [[package]] name = "mediarepo-socket" version = "0.1.0" -source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=57308089d58706ee1438f6b55d53691e9e08ce6b#57308089d58706ee1438f6b55d53691e9e08ce6b" +source = "git+https://github.com/Trivernis/mediarepo-daemon?rev=ce93a5793348a05052b324562730d97f5c6e2128#ce93a5793348a05052b324562730d97f5c6e2128" dependencies = [ "chrono", "mediarepo-core", @@ -2927,24 +2927,9 @@ dependencies = [ [[package]] name = "rmp-ipc" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ada2c7329ea55e1e80f2a1ae9ddb123f93ae4b349d493273138118feadfecb" -dependencies = [ - "lazy_static", - "log", - "rmp-serde", - "serde", - "thiserror", - "tokio", - "typemap_rev", -] - -[[package]] -name = "rmp-ipc" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd5082cefa9a4407327087d75dfadb5bf2d6f07168d0b609a2509f54fa3b243" +checksum = "17765aa8bd4d19dd81c53c0707192115f36ec200aea9f4557526932ac1f418e0" dependencies = [ "lazy_static", "log", diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml index 077db0e..b3c07c8 100644 --- a/mediarepo-ui/src-tauri/Cargo.toml +++ b/mediarepo-ui/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ tauri-build = { version = "1.0.0-beta.4" } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.0-beta.8", features = ["api-all"] } -rmp-ipc = "0.4.3" +rmp-ipc = "0.7.0" thiserror = "1.0.30" directories = "4.0.1" toml = "0.5.8" @@ -30,7 +30,7 @@ features = ["fs", "io-std", "io-util"] [dependencies.mediarepo] git = "https://github.com/Trivernis/mediarepo-daemon" -rev = "57308089d58706ee1438f6b55d53691e9e08ce6b" +rev = "ce93a5793348a05052b324562730d97f5c6e2128" features = ["library"] default-features=false diff --git a/mediarepo-ui/src-tauri/src/commands/files.rs b/mediarepo-ui/src-tauri/src/commands/files.rs index 682b0ad..8d908bd 100644 --- a/mediarepo-ui/src-tauri/src/commands/files.rs +++ b/mediarepo-ui/src-tauri/src/commands/files.rs @@ -1,4 +1,5 @@ -use mediarepo::requests::{GetFileThumbnailsRequest, ReadFileRequest}; +use crate::commands::get_ipc; +use mediarepo::requests::{FindFilesByTagsRequest, GetFileThumbnailsRequest, ReadFileRequest}; use mediarepo::responses::{FileResponse, ThumbnailResponse}; use crate::context::Context; @@ -6,47 +7,83 @@ use crate::error::{AppError, AppResult}; #[tauri::command] pub async fn get_all_files(context: tauri::State<'_, Context>) -> AppResult> { - let ipc = context.ipc.read().await; - if let Some(ipc) = &*ipc { - let response = ipc.emitter.emit_to("files", "all_files", ()).await?.await_reply(&ipc).await?; + let ipc = get_ipc(context).await?; + let response = ipc + .emitter + .emit_to("files", "all_files", ()) + .await? + .await_reply(&ipc) + .await?; - Ok(response.data::>()?) - } else { - Err(AppError::new("No ipc connection.")) - } + Ok(response.data::>()?) } #[tauri::command] -pub async fn read_file_by_hash(hash: String, context: tauri::State<'_, Context>) -> AppResult> { - let ipc = context.ipc.read().await; - if let Some(ipc) = &*ipc { - let response = ipc.emitter.emit_to("files", "read_file", ReadFileRequest::Hash(hash)).await?.await_reply(&ipc).await?; +pub async fn find_files( + tags: Vec, + context: tauri::State<'_, Context>, +) -> AppResult> { + let ipc = get_ipc(context).await?; + let response = ipc + .emitter + .emit_to("files", "find_files", FindFilesByTagsRequest { tags }) + .await? + .await_reply(&ipc) + .await?; + Ok(response.data::>()?) +} - Ok(response.data::>()?) - } else { - Err(AppError::new("No ipc connection.")) - } +#[tauri::command] +pub async fn read_file_by_hash( + hash: String, + context: tauri::State<'_, Context>, +) -> AppResult> { + let ipc = get_ipc(context).await?; + let response = ipc + .emitter + .emit_to("files", "read_file", ReadFileRequest::Hash(hash)) + .await? + .await_reply(&ipc) + .await?; + + Ok(response.data_raw().to_vec()) } #[tauri::command] -pub async fn get_thumbnails(hash: String, context: tauri::State<'_, Context>) -> AppResult> { - let ipc = context.ipc.read().await; - if let Some(ipc) = &*ipc { - let response = ipc.emitter.emit_to("files", "get_thumbnails", GetFileThumbnailsRequest::Hash(hash)).await?.await_reply(&ipc).await?; +pub async fn get_thumbnails( + hash: String, + context: tauri::State<'_, Context>, +) -> AppResult> { + let ipc = get_ipc(context).await?; + let response = ipc + .emitter + .emit_to( + "files", + "get_thumbnails", + GetFileThumbnailsRequest::Hash(hash), + ) + .await? + .await_reply(&ipc) + .await?; - Ok(response.data::>()?) - } else { - Err(AppError::new("No ipc connection.")) - } + Ok(response.data::>()?) } #[tauri::command] -pub async fn read_thumbnail(hash: String, context: tauri::State<'_, Context>) -> AppResult> { +pub async fn read_thumbnail( + hash: String, + context: tauri::State<'_, Context>, +) -> AppResult> { let ipc = context.ipc.read().await; if let Some(ipc) = &*ipc { - let response = ipc.emitter.emit_to("files", "read_thumbnail", hash).await?.await_reply(&ipc).await?; + let response = ipc + .emitter + .emit_to("files", "read_thumbnail", hash) + .await? + .await_reply(&ipc) + .await?; - Ok(response.data::>()?) + Ok(response.data_raw().to_vec()) } else { Err(AppError::new("No ipc connection.")) } diff --git a/mediarepo-ui/src-tauri/src/commands/mod.rs b/mediarepo-ui/src-tauri/src/commands/mod.rs index deb16ad..594b24c 100644 --- a/mediarepo-ui/src-tauri/src/commands/mod.rs +++ b/mediarepo-ui/src-tauri/src/commands/mod.rs @@ -1,8 +1,11 @@ +use rmp_ipc::ipc::context::Context as IPCContext; + use crate::context::Context; -use crate::error::AppResult; +use crate::error::{AppError, AppResult}; -pub mod repo; pub mod files; +pub mod repo; +pub mod tags; #[tauri::command] pub async fn emit_info(context: tauri::State<'_, Context>) -> AppResult<()> { @@ -16,3 +19,8 @@ pub async fn emit_info(context: tauri::State<'_, Context>) -> AppResult<()> { Ok(()) } + +pub async fn get_ipc(context: tauri::State<'_, Context>) -> AppResult { + let ipc = context.ipc.read().await; + (ipc.clone()).ok_or(AppError::new("No ipc connection.")) +} diff --git a/mediarepo-ui/src-tauri/src/commands/repo.rs b/mediarepo-ui/src-tauri/src/commands/repo.rs index 2f17a9a..37ac99f 100644 --- a/mediarepo-ui/src-tauri/src/commands/repo.rs +++ b/mediarepo-ui/src-tauri/src/commands/repo.rs @@ -1,13 +1,13 @@ -use std::path::PathBuf; -use serde::{Serialize, Deserialize}; use crate::context::Context; use crate::error::{AppError, AppResult}; -use tokio::fs; -use crate::settings::save_settings; -use rmp_ipc::context::Context as IPCContext; -use tauri::Window; use crate::ipc::build_ipc_context; +use crate::settings::save_settings; +use rmp_ipc::ipc::context::Context as IPCContext; +use serde::{Deserialize, Serialize}; use std::mem; +use std::path::PathBuf; +use tauri::Window; +use tokio::fs; static REPO_CONFIG_FILE: &str = "repo.toml"; @@ -33,13 +33,19 @@ pub async fn get_repositories(context: tauri::State<'_, Context>) -> AppResult) -> AppResult> { +pub async fn get_active_repository( + context: tauri::State<'_, Context>, +) -> AppResult> { let repo = context.active_repository.read().await; Ok(repo.clone()) } #[tauri::command] -pub async fn add_repository(name: String, path: String, context: tauri::State<'_, Context>) -> AppResult> { +pub async fn add_repository( + name: String, + path: String, + context: tauri::State<'_, Context>, +) -> AppResult> { let repo_path = path.clone(); let path = PathBuf::from(path); let RepoConfig { listen_address, .. } = read_repo_config(path.join(REPO_CONFIG_FILE)).await?; @@ -62,9 +68,16 @@ pub async fn add_repository(name: String, path: String, context: tauri::State<'_ } #[tauri::command] -pub async fn select_repository(window: Window, name: String, context: tauri::State<'_, Context>) -> AppResult<()> { +pub async fn select_repository( + window: Window, + name: String, + context: tauri::State<'_, Context>, +) -> AppResult<()> { let settings = context.settings.read().await; - let repo = settings.repositories.get(&name).ok_or(AppError::new(format!("Repository '{}' not found", name)))?; + let repo = settings + .repositories + .get(&name) + .ok_or(AppError::new(format!("Repository '{}' not found", name)))?; let ipc = connect(window, &repo.address).await?; let mut ipc_ctx = context.ipc.write().await; let old_ipc = mem::replace(&mut *ipc_ctx, Some(ipc)); diff --git a/mediarepo-ui/src-tauri/src/commands/tags.rs b/mediarepo-ui/src-tauri/src/commands/tags.rs new file mode 100644 index 0000000..1770e30 --- /dev/null +++ b/mediarepo-ui/src-tauri/src/commands/tags.rs @@ -0,0 +1,21 @@ +use crate::commands::get_ipc; +use crate::context::Context; +use crate::error::AppResult; +use mediarepo::requests::FileIdentifier; +use mediarepo::responses::TagResponse; + +#[tauri::command] +pub async fn get_tags_for_file( + hash: String, + context: tauri::State<'_, Context>, +) -> AppResult> { + let ipc = get_ipc(context).await?; + let response = ipc + .emitter + .emit_to("tags", "tags_for_file", FileIdentifier::Hash(hash)) + .await? + .await_reply(&ipc) + .await?; + + Ok(response.data::>()?) +} diff --git a/mediarepo-ui/src-tauri/src/context.rs b/mediarepo-ui/src-tauri/src/context.rs index 0753627..8e04277 100644 --- a/mediarepo-ui/src-tauri/src/context.rs +++ b/mediarepo-ui/src-tauri/src/context.rs @@ -1,14 +1,14 @@ -use std::sync::Arc; -use tokio::sync::RwLock; -use rmp_ipc::context::Context as IPCContext; use crate::commands::repo::Repository; use crate::settings::Settings; +use rmp_ipc::ipc::context::Context as IPCContext; +use std::sync::Arc; +use tokio::sync::RwLock; #[derive(Clone)] pub struct Context { pub active_repository: Arc>>, pub ipc: Arc>>, - pub settings: Arc> + pub settings: Arc>, } impl Context { @@ -16,7 +16,7 @@ impl Context { Self { ipc: Arc::new(RwLock::new(None)), active_repository: Arc::new(RwLock::new(None)), - settings: Arc::new(RwLock::new(settings)) + settings: Arc::new(RwLock::new(settings)), } } } diff --git a/mediarepo-ui/src-tauri/src/ipc/mod.rs b/mediarepo-ui/src-tauri/src/ipc/mod.rs index 2affd4e..0ab5b7c 100644 --- a/mediarepo-ui/src-tauri/src/ipc/mod.rs +++ b/mediarepo-ui/src-tauri/src/ipc/mod.rs @@ -1,13 +1,14 @@ mod files; +use crate::error::AppResult; use mediarepo::responses::InfoResponse; -use rmp_ipc::context::{Context as IPCContext, Context}; -use rmp_ipc::{Event, IPCBuilder}; use rmp_ipc::error::Result; -use rmp_ipc::error_event::{ERROR_EVENT_NAME, ErrorEventData}; +use rmp_ipc::error_event::{ErrorEventData, ERROR_EVENT_NAME}; +use rmp_ipc::event::Event; +use rmp_ipc::ipc::context::Context as IPCContext; +use rmp_ipc::IPCBuilder; use tauri::Window; use typemap_rev::TypeMapKey; -use crate::error::AppResult; pub struct WindowKey; @@ -19,27 +20,32 @@ pub async fn build_ipc_context(window: Window, address: &str) -> AppResult(window) - .on(ERROR_EVENT_NAME, |c, e|Box::pin(handle_error(c, e))) + .on(ERROR_EVENT_NAME, |c, e| Box::pin(handle_error(c, e))) .on("info", |c, e| Box::pin(handle_info(c, e))) - .build_client().await?; + .build_client() + .await?; Ok(ctx) } -async fn handle_error(ctx: &Context, event: Event) -> Result<()> { +async fn handle_error(ctx: &IPCContext, event: Event) -> Result<()> { let error_data = event.data::()?; let data = ctx.data.read().await; let window = data.get::().unwrap(); - window.emit("error", error_data).expect("Failed to emit error event"); + window + .emit("error", error_data) + .expect("Failed to emit error event"); Ok(()) } -async fn handle_info(ctx: &Context, event: Event) -> Result<()> { +async fn handle_info(ctx: &IPCContext, event: Event) -> Result<()> { let info_data = event.data::()?; let data = ctx.data.read().await; let window = data.get::().unwrap(); - window.emit("info", info_data).expect("Failed to emit info event"); + window + .emit("info", info_data) + .expect("Failed to emit info event"); Ok(()) } diff --git a/mediarepo-ui/src-tauri/src/main.rs b/mediarepo-ui/src-tauri/src/main.rs index bd94925..189a6de 100644 --- a/mediarepo-ui/src-tauri/src/main.rs +++ b/mediarepo-ui/src-tauri/src/main.rs @@ -3,17 +3,20 @@ windows_subsystem = "windows" )] -use crate::commands::repo::{get_repositories, add_repository, select_repository, get_active_repository}; -use crate::commands::files::*; use crate::commands::emit_info; +use crate::commands::files::*; +use crate::commands::repo::{ + add_repository, get_active_repository, get_repositories, select_repository, +}; +use crate::commands::tags::*; use crate::context::Context; use crate::settings::load_settings; mod commands; pub mod context; pub mod error; -mod settings; mod ipc; +mod settings; fn main() { let settings = load_settings().expect("Failed to load settings"); @@ -21,7 +24,19 @@ fn main() { tauri::Builder::default() .manage(context) - .invoke_handler(tauri::generate_handler![get_repositories, add_repository, select_repository, get_active_repository, emit_info, get_all_files, read_file_by_hash, get_thumbnails, read_thumbnail]) + .invoke_handler(tauri::generate_handler![ + get_repositories, + add_repository, + select_repository, + get_active_repository, + emit_info, + get_all_files, + read_file_by_hash, + get_thumbnails, + read_thumbnail, + get_tags_for_file, + find_files, + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/mediarepo-ui/src/app/app.module.ts b/mediarepo-ui/src/app/app.module.ts index eb488da..3045b44 100644 --- a/mediarepo-ui/src/app/app.module.ts +++ b/mediarepo-ui/src/app/app.module.ts @@ -24,6 +24,8 @@ import {MatProgressBarModule} from "@angular/material/progress-bar"; import {MatPaginatorModule} from "@angular/material/paginator"; import {ScrollingModule} from "@angular/cdk/scrolling"; import {LightboxModule} from "ngx-lightbox"; +import {MatChipsModule} from "@angular/material/chips"; +import {MatIconModule} from "@angular/material/icon"; @NgModule({ declarations: [ @@ -35,25 +37,27 @@ import {LightboxModule} from "ngx-lightbox"; FileGridComponent, FileGridEntryComponent, ], - imports: [ - BrowserModule, - AppRoutingModule, - BrowserAnimationsModule, - MatCardModule, - MatListModule, - MatButtonModule, - MatToolbarModule, - MatSnackBarModule, - MatFormFieldModule, - MatInputModule, - ReactiveFormsModule, - MatSidenavModule, - MatGridListModule, - MatProgressBarModule, - MatPaginatorModule, - ScrollingModule, - LightboxModule - ], + imports: [ + BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + MatCardModule, + MatListModule, + MatButtonModule, + MatToolbarModule, + MatSnackBarModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + MatSidenavModule, + MatGridListModule, + MatProgressBarModule, + MatPaginatorModule, + ScrollingModule, + LightboxModule, + MatChipsModule, + MatIconModule + ], providers: [], bootstrap: [AppComponent] }) diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts b/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts index b4782ef..7c5c3b3 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts +++ b/mediarepo-ui/src/app/components/file-grid/file-grid.component.ts @@ -21,7 +21,8 @@ export class FileGridComponent implements OnChanges { @Input() files: File[] = []; @Input() columns: number = 6; @Output() fileDblClickEvent = new EventEmitter(); - @Output() filesSelectEvent = new EventEmitter(); + @Output() fileMultiselectEvent = new EventEmitter(); + @Output() fileSelectEvent = new EventEmitter(); selectedEntries: GridEntry[] = []; @@ -51,6 +52,8 @@ export class FileGridComponent implements OnChanges { * @param {FileGridEntryComponent} clickedEntry */ setSelectedFile(clickedEntry: GridEntry) { + const previousSelectionSize = this.selectedEntries.length; + if (!(this.shiftClicked || this.ctrlClicked) && this.selectedEntries.length > 0) { this.selectedEntries.forEach(entry => {if (entry !== clickedEntry) entry.selected = false}); this.selectedEntries = []; @@ -61,7 +64,13 @@ export class FileGridComponent implements OnChanges { clickedEntry.selected = !clickedEntry.selected; this.selectedEntries.push(clickedEntry); } - this.filesSelectEvent.emit(this.selectedEntries.map(entry => entry.file)); + if (this.selectedEntries.length == 1) { + this.fileSelectEvent.emit(this.selectedEntries.map(entry => entry.file)[0]); + } else if (this.selectedEntries.length == 0 && previousSelectionSize == 1){ + this.fileSelectEvent.emit(undefined); + } else { + this.fileMultiselectEvent.emit(this.selectedEntries.map(entry => entry.file)); + } } private handleShiftSelect(clickedEntry: GridEntry): void { @@ -85,7 +94,6 @@ export class FileGridComponent implements OnChanges { this.selectedEntries.push(gridEntry); } } - } } diff --git a/mediarepo-ui/src/app/models/Tag.ts b/mediarepo-ui/src/app/models/Tag.ts new file mode 100644 index 0000000..a5eb657 --- /dev/null +++ b/mediarepo-ui/src/app/models/Tag.ts @@ -0,0 +1,5 @@ +export type Tag = { + id: number, + name: string, + namespace: string | undefined, +}; diff --git a/mediarepo-ui/src/app/pages/home/home.component.html b/mediarepo-ui/src/app/pages/home/home.component.html index d770c78..a93026b 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.html +++ b/mediarepo-ui/src/app/pages/home/home.component.html @@ -4,10 +4,31 @@ -

Drawer

+ + + + {{tag}} + + + + + + +

Tags

+ + {{tag.namespace ? tag.namespace + ':' + tag.name : tag.name}} +
- +
diff --git a/mediarepo-ui/src/app/pages/home/home.component.scss b/mediarepo-ui/src/app/pages/home/home.component.scss index d11bdf8..8cd91d2 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.scss +++ b/mediarepo-ui/src/app/pages/home/home.component.scss @@ -8,6 +8,10 @@ overflow: hidden } +#tag-search { + width: 100%; +} + mat-drawer { height: 100%; width: 25%; diff --git a/mediarepo-ui/src/app/pages/home/home.component.ts b/mediarepo-ui/src/app/pages/home/home.component.ts index 95f057a..a8c22e3 100644 --- a/mediarepo-ui/src/app/pages/home/home.component.ts +++ b/mediarepo-ui/src/app/pages/home/home.component.ts @@ -5,6 +5,10 @@ import {PageEvent} from "@angular/material/paginator"; import {Lightbox, LIGHTBOX_EVENT, LightboxEvent} from "ngx-lightbox"; import {SafeResourceUrl} from "@angular/platform-browser"; import {ErrorBrokerService} from "../../services/error-broker/error-broker.service"; +import {TagService} from "../../services/tag/tag.service"; +import {Tag} from "../../models/Tag"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; @Component({ selector: 'app-home', @@ -14,15 +18,56 @@ import {ErrorBrokerService} from "../../services/error-broker/error-broker.servi export class HomeComponent implements OnInit { files: File[] = []; + tags: Tag[] = []; + searchTags: string[] = []; private openingLightbox = false; + searchInputSeparators = [ENTER, COMMA]; - constructor(private errorBroker: ErrorBrokerService, private fileService: FileService, private lightbox: Lightbox, private lightboxEvent: LightboxEvent) { } + constructor( + private errorBroker: ErrorBrokerService, + private fileService: FileService, + private tagService: TagService, + private lightbox: Lightbox, + private lightboxEvent: LightboxEvent) { } async ngOnInit() { this.fileService.displayedFiles.subscribe((files) => this.files = files); await this.fileService.getFiles(); } + async onFileSelect(file: File | undefined) { + if (file) { + await this.showFileDetails(file); + } else { + this.clearFileDetails(); + } + } + + clearFileDetails() { + this.tags = []; + } + + async showFileDetails(file: File) { + this.tags = await this.tagService.getTagsForFile(file.hash); + } + + async removeSearchTag(tag: string) { + const index = this.searchTags.indexOf(tag); + if (index >= 0) { + this.searchTags.splice(index, 1); + } + await this.fileService.findFiles(this.searchTags); + } + + async addSearchTag(event: MatChipInputEvent) { + const tag = event.value.trim(); + if (tag.length > 0) { + this.searchTags.push(tag); + event.chipInput?.clear(); + await this.fileService.findFiles(this.searchTags); + } + } + async openFile(file: File) { if (this.openingLightbox) { return; diff --git a/mediarepo-ui/src/app/services/file/file.service.ts b/mediarepo-ui/src/app/services/file/file.service.ts index 4c85cbf..b01cb3b 100644 --- a/mediarepo-ui/src/app/services/file/file.service.ts +++ b/mediarepo-ui/src/app/services/file/file.service.ts @@ -21,6 +21,11 @@ export class FileService { this.displayedFiles.next(all_files); } + public async findFiles(tags: string[]) { + let files = await invoke("find_files", {tags}); + this.displayedFiles.next(files); + } + public async readFile(hash: string, mime_type: string): Promise { const data = await invoke("read_file_by_hash", {hash}); const blob = new Blob([new Uint8Array(data)], {type: mime_type}); diff --git a/mediarepo-ui/src/app/services/tag/tag.service.spec.ts b/mediarepo-ui/src/app/services/tag/tag.service.spec.ts new file mode 100644 index 0000000..f091e93 --- /dev/null +++ b/mediarepo-ui/src/app/services/tag/tag.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { TagService } from './tag.service'; + +describe('TagService', () => { + let service: TagService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TagService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/mediarepo-ui/src/app/services/tag/tag.service.ts b/mediarepo-ui/src/app/services/tag/tag.service.ts new file mode 100644 index 0000000..0e7f8f8 --- /dev/null +++ b/mediarepo-ui/src/app/services/tag/tag.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import {invoke} from "@tauri-apps/api/tauri"; +import {Tag} from "../../models/Tag"; + +@Injectable({ + providedIn: 'root' +}) +export class TagService { + + constructor() { } + + public async getTagsForFile(hash: string): Promise { + return await invoke("get_tags_for_file", {hash}); + } +}