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
This commit is contained in:
Dionisio
2026-02-16 21:51:53 +01:00
parent a4709426d9
commit 1ceae0ce94
28 changed files with 486 additions and 1707 deletions
@@ -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<GlobalAppState>,
auth_user: AuthUser,
Path(id): Path<String>,
) -> 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<AppState>,
+34 -3
View File
@@ -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<usize>,
pub per_page: Option<usize>,
pub item_id: Option<String>,
pub item_type: Option<String>,
}
#[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<Arc<dyn ShareUseCase>>,
auth_user: OptionalAuthUser,
Query(query): Query<GetSharesQuery>,
) -> 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(),
+9 -1
View File
@@ -144,6 +144,13 @@ pub fn create_api_routes(app_state: &AppState) -> Router<AppState> {
.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<AppState> {
// 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()