From 1ceae0ce9421c0142004bf2fd375a80f8ce8bde0 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 16 Feb 2026 21:51:53 +0100 Subject: [PATCH] Frontend optimizations: SVG icons, remove updateFileIcons, unify rendering, scope translatePage - Replace Font Awesome CDN with inline SVG system (icons.js + MutationObserver) - Remove updateFileIcons() (~140 lines) - redundant with backend icon_class + MutationObserver - Migrate favorites.js and recent.js to use shared ui.renderFolders/renderFiles (eliminate ~260 lines of duplicate rendering + per-item event listeners) - Add view-mode aware click delegation for favorites/recent views - Fix _createFileCard to apply icon_special_class - Add translateElement(root) for scoped i18n translation - Replace full-page translatePage() calls with scoped translateElement() or inline t() - Remove redundant translatePage() calls in shared.js and auth.js - Remove Alpine.js from Service Worker cache --- src/application/dtos/folder_listing_dto.rs | 14 + src/application/dtos/mod.rs | 1 + src/interfaces/api/handlers/folder_handler.rs | 38 ++ src/interfaces/api/handlers/share_handler.rs | 37 +- src/interfaces/api/routes.rs | 10 +- static/admin.html | 2 +- static/css/fileViewer.css | 189 ------- static/index.html | 37 +- static/js/app.js | 116 +--- static/js/auth.js | 5 +- static/js/components/sharedView.js | 4 +- static/js/contextMenus.js | 8 +- static/js/favorites.js | 186 +------ static/js/fileOperations.js | 34 +- static/js/fileRenderer.js | 503 ------------------ static/js/fileSharing.js | 13 +- static/js/fileViewer.js | 413 -------------- static/js/i18n.js | 17 +- static/js/icons.js | 232 ++++++++ static/js/multiSelect.js | 4 +- static/js/recent.js | 145 +---- static/js/search.js | 2 - static/js/shared.js | 8 +- static/js/ui.js | 161 +----- static/login.html | 3 +- static/profile.html | 2 +- static/shared.html | 2 +- static/sw.js | 7 +- 28 files changed, 486 insertions(+), 1707 deletions(-) create mode 100644 src/application/dtos/folder_listing_dto.rs delete mode 100644 static/css/fileViewer.css delete mode 100644 static/js/fileRenderer.js delete mode 100644 static/js/fileViewer.js create mode 100644 static/js/icons.js diff --git a/src/application/dtos/folder_listing_dto.rs b/src/application/dtos/folder_listing_dto.rs new file mode 100644 index 00000000..0bdebacb --- /dev/null +++ b/src/application/dtos/folder_listing_dto.rs @@ -0,0 +1,14 @@ +use serde::Serialize; + +use super::file_dto::FileDto; +use super::folder_dto::FolderDto; + +/// Combined DTO that returns both sub-folders and files for a given folder +/// in a single response, eliminating the double-fetch on every navigation. +#[derive(Debug, Serialize)] +pub struct FolderListingDto { + /// Sub-folders inside the requested folder + pub folders: Vec, + /// Files inside the requested folder + pub files: Vec, +} diff --git a/src/application/dtos/mod.rs b/src/application/dtos/mod.rs index 93ad76a5..bf2718d9 100644 --- a/src/application/dtos/mod.rs +++ b/src/application/dtos/mod.rs @@ -5,6 +5,7 @@ pub mod display_helpers; pub mod favorites_dto; pub mod file_dto; pub mod folder_dto; +pub mod folder_listing_dto; pub mod i18n_dto; pub mod pagination; pub mod recent_dto; diff --git a/src/interfaces/api/handlers/folder_handler.rs b/src/interfaces/api/handlers/folder_handler.rs index 3a5dcb71..f20de467 100644 --- a/src/interfaces/api/handlers/folder_handler.rs +++ b/src/interfaces/api/handlers/folder_handler.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::sync::Arc; use crate::application::dtos::folder_dto::{CreateFolderDto, MoveFolderDto, RenameFolderDto}; +use crate::application::dtos::folder_listing_dto::FolderListingDto; use crate::application::dtos::pagination::PaginationRequestDto; use crate::application::ports::inbound::FolderUseCase; use crate::application::services::folder_service::FolderService; @@ -183,6 +184,43 @@ impl FolderHandler { } } + /// Returns both sub-folders and files for a given folder in a single + /// response, eliminating the double-fetch the frontend used to make. + /// + /// Both queries run concurrently via `tokio::join!`. + pub async fn list_folder_listing( + State(state): State, + auth_user: AuthUser, + Path(id): Path, + ) -> axum::response::Response { + let folder_service = &state.applications.folder_service; + let file_service = &state.applications.file_retrieval_service; + + // Run both queries concurrently — no sequential wait. + let (folders_result, files_result) = tokio::join!( + folder_service.list_folders_for_owner(Some(&id), &auth_user.id), + file_service.list_files(Some(&id)) + ); + + match (folders_result, files_result) { + (Ok(folders), Ok(files)) => { + let listing = FolderListingDto { folders, files }; + (StatusCode::OK, Json(listing)).into_response() + } + (Err(err), _) | (_, Err(err)) => { + let status = match err.kind { + ErrorKind::NotFound => StatusCode::NOT_FOUND, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }; + ( + status, + Json(serde_json::json!({ "error": err.to_string() })), + ) + .into_response() + } + } + } + /// Renames a folder (ownership enforced by service layer) pub async fn rename_folder( State(service): State, diff --git a/src/interfaces/api/handlers/share_handler.rs b/src/interfaces/api/handlers/share_handler.rs index 0bdcb8e3..0dfb93c9 100644 --- a/src/interfaces/api/handlers/share_handler.rs +++ b/src/interfaces/api/handlers/share_handler.rs @@ -15,6 +15,7 @@ use crate::{ ports::share_ports::ShareUseCase, }, common::errors::ErrorKind, + domain::entities::share::ShareItemType, interfaces::middleware::auth::OptionalAuthUser, }; @@ -22,6 +23,8 @@ use crate::{ pub struct GetSharesQuery { pub page: Option, pub per_page: Option, + pub item_id: Option, + pub item_type: Option, } #[derive(Debug, Deserialize)] @@ -69,21 +72,49 @@ pub async fn get_shared_link( } } -/// Get all shared links created by the current user +/// Get all shared links created by the current user. +/// Supports optional filtering by item_id + item_type query params. pub async fn get_user_shares( State(share_use_case): State>, auth_user: OptionalAuthUser, Query(query): Query, ) -> impl IntoResponse { - let user_id = auth_user + let _user_id = auth_user .0 .map(|u| u.id) .unwrap_or_else(|| "anonymous".to_string()); + + // If both item_id and item_type are provided, return shares for that specific item + if let (Some(item_id), Some(item_type_str)) = (&query.item_id, &query.item_type) { + let item_type = match ShareItemType::try_from(item_type_str.as_str()) { + Ok(t) => t, + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({ "error": format!("Invalid item_type: {}", item_type_str) })), + ) + .into_response(); + } + }; + return match share_use_case + .get_shared_links_for_item(item_id, &item_type) + .await + { + Ok(shares) => (StatusCode::OK, Json(shares)).into_response(), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": err.to_string() })), + ) + .into_response(), + }; + } + + // Default: paginated list of all user shares let page = query.page.unwrap_or(1); let per_page = query.per_page.unwrap_or(20); match share_use_case - .get_user_shared_links(&user_id, page, per_page) + .get_user_shared_links(&_user_id, page, per_page) .await { Ok(shares) => (StatusCode::OK, Json(shares)).into_response(), diff --git a/src/interfaces/api/routes.rs b/src/interfaces/api/routes.rs index 92477e3f..0d13c143 100644 --- a/src/interfaces/api/routes.rs +++ b/src/interfaces/api/routes.rs @@ -144,6 +144,13 @@ pub fn create_api_routes(app_state: &AppState) -> Router { .route("/{id}/download", get(FolderHandler::download_folder_zip)) .with_state(app_state.clone()); + // Combined listing endpoint: returns both sub-folders AND files in one + // response. Needs full AppState because it calls both FolderService + // and FileRetrievalService concurrently. + let folder_listing_router = Router::new() + .route("/{id}/listing", get(FolderHandler::list_folder_listing)) + .with_state(app_state.clone()); + // Create folder operations that use trash (requires full AppState) let folders_ops_router = Router::new().route("/{id}", delete(FolderHandler::delete_folder_with_trash)); @@ -151,7 +158,8 @@ pub fn create_api_routes(app_state: &AppState) -> Router { // Merge the routers let folders_router = folders_basic_router .merge(folders_ops_router) - .merge(folder_zip_router); + .merge(folder_zip_router) + .merge(folder_listing_router); // Create file routes for basic operations and trash-enabled delete let basic_file_router = Router::new() diff --git a/static/admin.html b/static/admin.html index 278109a1..dc637a83 100644 --- a/static/admin.html +++ b/static/admin.html @@ -6,7 +6,7 @@ OxiCloud — Admin Panel - +