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
+28 -88
View File
@@ -68,25 +68,10 @@ function initApp() {
// Setup event listeners
setupEventListeners();
// Initialize file renderer if available
if (window.fileRenderer) {
console.log('Using optimized file renderer');
} else {
console.log('Using standard file rendering');
}
// Check if inline viewer is initialized
if (window.inlineViewer) {
console.log('Inline viewer is available');
} else {
console.warn('Inline viewer not initialized yet, will initialize it now');
// Ensure inline viewer is initialized
if (!window.inlineViewer && typeof InlineViewer !== 'undefined') {
try {
// Create inline viewer if not already created and if the class exists
if (typeof InlineViewer !== 'undefined') {
window.inlineViewer = new InlineViewer();
} else {
console.warn('InlineViewer class is not defined, skipping initialization');
}
window.inlineViewer = new InlineViewer();
} catch (e) {
console.error('Error initializing inline viewer:', e);
}
@@ -719,7 +704,7 @@ async function loadFiles(options = {}) {
await resolveHomeFolder();
}
// Add timestamp to avoid cache
// Build the listing URL — single endpoint returns folders + files together
const timestamp = new Date().getTime();
let url;
@@ -727,7 +712,7 @@ async function loadFiles(options = {}) {
if (!app.currentPath || app.currentPath === '') {
// If at root, force user to their home folder
if (app.userHomeFolderId) {
url = `/api/folders/${app.userHomeFolderId}/contents?t=${timestamp}`;
url = `/api/folders/${app.userHomeFolderId}/listing?t=${timestamp}`;
app.currentPath = app.userHomeFolderId;
ui.updateBreadcrumb(app.userHomeFolderName || 'Home');
console.log(`Loading user folder: ${app.userHomeFolderName} (${app.userHomeFolderId})`);
@@ -738,7 +723,7 @@ async function loadFiles(options = {}) {
}
} else {
// Normal case - viewing subfolder contents
url = `/api/folders/${app.currentPath}/contents?t=${timestamp}`;
url = `/api/folders/${app.currentPath}/listing?t=${timestamp}`;
console.log(`Loading subfolder content: ${app.currentPath}`);
}
@@ -762,13 +747,12 @@ async function loadFiles(options = {}) {
console.log('Forcing complete refresh ignoring cache');
}
console.log(`Loading files from ${url}`);
console.log(`Loading listing from ${url}`);
const response = await fetch(url, requestOptions);
// Critical error handling
if (response.status === 401 || response.status === 403) {
console.warn("Auth error when loading files, showing empty list");
// Just show empty state instead of causing redirect loops
elements.filesGrid.innerHTML = '<div class="empty-state"><p>Could not load files</p></div>';
elements.filesListView.innerHTML = `
<div class="list-header">
@@ -785,19 +769,22 @@ async function loadFiles(options = {}) {
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
const folders = await response.json();
// Clear existing files in both views
// Single response contains both folders and files
const listing = await response.json();
// Clear existing content in both views
if (window.multiSelect) window.multiSelect.clear();
ui._items.clear();
elements.filesGrid.innerHTML = '';
const _t = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop();
elements.filesListView.innerHTML = `
<div class="list-header">
<div class="list-header-checkbox"><input type="checkbox" id="select-all-checkbox" title="Select all"></div>
<div data-i18n="files.name">Name</div>
<div data-i18n="files.type">Type</div>
<div data-i18n="files.size">Size</div>
<div data-i18n="files.modified">Modified</div>
<div data-i18n="files.name">${_t('files.name')}</div>
<div data-i18n="files.type">${_t('files.type')}</div>
<div data-i18n="files.size">${_t('files.size')}</div>
<div data-i18n="files.modified">${_t('files.modified')}</div>
</div>
`;
@@ -807,57 +794,14 @@ async function loadFiles(options = {}) {
selectAllCb.addEventListener('change', () => window.multiSelect.toggleAll());
}
// Translate the header if i18n is available
if (window.i18n && window.i18n.translatePage) {
window.i18n.translatePage();
}
// Render folders and files from the combined listing
const folderList = Array.isArray(listing.folders) ? listing.folders : [];
const fileList = Array.isArray(listing.files) ? listing.files : [];
// Add folders (check if it's an array)
const folderList = Array.isArray(folders) ? folders : [];
// Backend already scopes folders to the authenticated user,
// so no client-side filtering is needed.
ui.renderFolders(folderList);
ui.renderFiles(fileList);
// Also load files in this folder
const cacheTimestamp = new Date().getTime();
let filesUrl = `/api/files?t=${cacheTimestamp}`; // Add timestamp to avoid cache issues
if (app.currentPath) {
filesUrl += `&folder_id=${app.currentPath}`;
}
console.log(`Loading files from: ${filesUrl}`);
try {
console.log(`Fetching files from: ${filesUrl}`);
const filesResponse = await fetch(filesUrl, requestOptions); // Use same auth token
console.log(`Files response status: ${filesResponse.status}`);
// Handle auth errors for files too
if (filesResponse.status === 401 || filesResponse.status === 403) {
console.warn("Auth error when loading files");
return; // Already showing folders, just stop here
}
if (filesResponse.ok) {
const files = await filesResponse.json();
console.log(`Files received:`, files);
// Add files (check if it's an array)
const fileList = Array.isArray(files) ? files : [];
console.log(`Processing ${fileList.length} files`);
ui.renderFiles(fileList);
} else {
const errorText = await filesResponse.text();
console.error(`Error loading files: ${filesResponse.status} - ${errorText}`);
}
} catch (error) {
console.error('Error loading files:', error);
// File API may not be implemented yet, so we silently ignore this error
}
// Update file icons based on file type
ui.updateFileIcons();
console.log(`Loaded ${folderList.length} folders and ${fileList.length} files`);
} catch (error) {
console.error('Error loading folders:', error);
ui.showNotification('Error', 'Could not load files and folders');
@@ -890,21 +834,17 @@ async function loadTrashItems() {
// Clear existing content
if (window.multiSelect) window.multiSelect.clear();
elements.filesGrid.innerHTML = '';
const _tt = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop();
elements.filesListView.innerHTML = `
<div class="list-header trash-header">
<div data-i18n="files.name">Name</div>
<div data-i18n="files.type">Type</div>
<div data-i18n="trash.original_location">Original location</div>
<div data-i18n="trash.deleted_date">Deletion date</div>
<div data-i18n="trash.actions">Actions</div>
<div data-i18n="files.name">${_tt('files.name')}</div>
<div data-i18n="files.type">${_tt('files.type')}</div>
<div data-i18n="trash.original_location">${_tt('trash.original_location')}</div>
<div data-i18n="trash.deleted_date">${_tt('trash.deleted_date')}</div>
<div data-i18n="trash.actions">${_tt('trash.actions')}</div>
</div>
`;
// Translate the header if i18n is available
if (window.i18n && window.i18n.translatePage) {
window.i18n.translatePage();
}
// Update breadcrumb - just show Home
ui.updateBreadcrumb('');
@@ -1150,7 +1090,7 @@ window.formatDateShort = function formatDateShort(value) {
/**
* Centralised "is this MIME type text-viewable?" check.
* Used by fileViewer, inlineViewer, and ui.isViewableFile.
* Used by inlineViewer and ui.isViewableFile.
*/
window.isTextViewable = function isTextViewable(mimeType) {
if (!mimeType) return false;