refactor(js): avoid use of window.XXX and move to import/export
- change worker: do not cache html pages (not necessary) - remove use of window.XXX and maximize import/export, this will provide more clarety, show circular dependencies + you will benefit IDE help
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
* Authentication/session bootstrap and home-folder resolution
|
||||
*/
|
||||
|
||||
import { getCsrfHeaders } from '../core/csrf.js';
|
||||
import { loadFiles } from './filesView.js';
|
||||
import { updateStorageUsageDisplay } from './main.js';
|
||||
import { app } from './state.js';
|
||||
import { ui } from './ui.js';
|
||||
|
||||
async function refreshUserData() {
|
||||
const USER_DATA_KEY = 'oxicloud_user';
|
||||
|
||||
@@ -24,7 +30,7 @@ async function refreshUserData() {
|
||||
console.log('Storage from server: used=', userData.storage_used_bytes, 'quota=', userData.storage_quota_bytes);
|
||||
|
||||
localStorage.setItem(USER_DATA_KEY, JSON.stringify(userData));
|
||||
window.updateStorageUsageDisplay(userData);
|
||||
updateStorageUsageDisplay(userData);
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error('Error refreshing user data:', error);
|
||||
@@ -89,7 +95,7 @@ async function checkAuthentication() {
|
||||
if (menuName) menuName.textContent = userData.username;
|
||||
if (menuEmail) menuEmail.textContent = userData.email || '';
|
||||
|
||||
window.updateStorageUsageDisplay(userData);
|
||||
updateStorageUsageDisplay(userData);
|
||||
|
||||
// Validate session BEFORE loading files to avoid 401 race condition
|
||||
const freshData = await refreshUserData();
|
||||
@@ -133,8 +139,8 @@ async function checkAuthentication() {
|
||||
document.querySelectorAll('.user-avatar, .user-menu-avatar').forEach((el) => {
|
||||
el.textContent = userInitials;
|
||||
});
|
||||
window.updateStorageUsageDisplay(freshData);
|
||||
resolveHomeFolder().then(() => window.loadFiles());
|
||||
updateStorageUsageDisplay(freshData);
|
||||
resolveHomeFolder().then(() => loadFiles());
|
||||
} else {
|
||||
console.warn('Could not retrieve user data, redirecting to login');
|
||||
localStorage.removeItem(USER_DATA_KEY);
|
||||
@@ -154,8 +160,6 @@ async function checkAuthentication() {
|
||||
}
|
||||
|
||||
async function resolveHomeFolder() {
|
||||
const app = window.app;
|
||||
|
||||
if (app.userHomeFolderId) return;
|
||||
try {
|
||||
const response = await fetch('/api/folders', {
|
||||
@@ -173,22 +177,20 @@ async function resolveHomeFolder() {
|
||||
app.userHomeFolderName = home.name;
|
||||
app.currentPath = home.id;
|
||||
app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
ui.updateBreadcrumb();
|
||||
console.log(`Home folder resolved: ${home.name} (${home.id})`);
|
||||
} else {
|
||||
console.warn('No root folders found for user');
|
||||
app.currentPath = '';
|
||||
app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
ui.updateBreadcrumb();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error resolving home folder:', error);
|
||||
app.currentPath = '';
|
||||
app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
ui.updateBreadcrumb();
|
||||
}
|
||||
}
|
||||
|
||||
window.refreshUserData = refreshUserData;
|
||||
window.checkAuthentication = checkAuthentication;
|
||||
window.resolveHomeFolder = resolveHomeFolder;
|
||||
export { checkAuthentication, refreshUserData, resolveHomeFolder };
|
||||
|
||||
Vendored
+4
-7
@@ -2,13 +2,10 @@
|
||||
* OxiCloud - App bootstrap
|
||||
* Isolated startup trigger for the main application initializer.
|
||||
*/
|
||||
import { initApp } from './main.js';
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof window.initApp === 'function') {
|
||||
window.initApp();
|
||||
}
|
||||
});
|
||||
} else if (typeof window.initApp === 'function') {
|
||||
window.initApp();
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
} else {
|
||||
initApp();
|
||||
}
|
||||
|
||||
+39
-35
@@ -1,5 +1,16 @@
|
||||
// @ts-check
|
||||
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { inlineViewer } from '../features/files/inlineViewer.js';
|
||||
import { multiSelect } from '../features/files/multiSelect.js';
|
||||
import { resolveHomeFolder } from './authSession.js';
|
||||
import { updateHistory } from './main.js';
|
||||
import { app } from './state.js';
|
||||
import { ui } from './ui.js';
|
||||
import { uiNotifications } from './uiNotifications.js';
|
||||
|
||||
let isLoadingFiles = false;
|
||||
|
||||
// TODO move to features/files/fileOperations.js ?
|
||||
/**
|
||||
* @typedef {Object} FolderInfo
|
||||
@@ -48,8 +59,6 @@ async function getFolder(id) {
|
||||
* rebuild breadcrumb from selected folder (iterate up to root)
|
||||
*/
|
||||
async function rebuildBreadCrumb() {
|
||||
const app = window.app;
|
||||
|
||||
/**
|
||||
* Store the leaf (this is the current displayed folder)
|
||||
* @type {FolderInfo | null}
|
||||
@@ -87,10 +96,7 @@ async function rebuildBreadCrumb() {
|
||||
} catch (_e) {
|
||||
console.log(`Error loading information from folder ${app.currentPath}, falling back to ${app.userHomeFolderId}`);
|
||||
// fallback of root
|
||||
window.uiNotifications.show(
|
||||
'error: folder not found or permission denied',
|
||||
'the given folder is not available or you do not have sufficient rights'
|
||||
);
|
||||
uiNotifications.show('error: folder not found or permission denied', 'the given folder is not available or you do not have sufficient rights');
|
||||
app.breadcrumbPath = [];
|
||||
id = app.userHomeFolderId;
|
||||
app.currentPath = id;
|
||||
@@ -109,33 +115,31 @@ async function rebuildBreadCrumb() {
|
||||
* @param {boolean} [options.forceRefresh] force refresh of content
|
||||
*/
|
||||
async function loadFiles(options = { insertHistory: true }) {
|
||||
const app = window.app;
|
||||
|
||||
try {
|
||||
console.log('Starting loadFiles() - loading files...', options);
|
||||
|
||||
const forceRefresh = options.forceRefresh || false;
|
||||
|
||||
if (window.isLoadingFiles) {
|
||||
if (isLoadingFiles) {
|
||||
console.log('A file load is already in progress, ignoring request');
|
||||
return;
|
||||
}
|
||||
|
||||
window.isLoadingFiles = true;
|
||||
isLoadingFiles = true;
|
||||
|
||||
// This to avoid blinking page, a better solution would be to put loading on an overlay and remove timeout
|
||||
const loadingFiles = setTimeout(() => {
|
||||
// display loader after few delay (will be canceled if result take less time)
|
||||
window.ui.showError(`
|
||||
ui.showError(`
|
||||
<div class="files-loading-spinner">
|
||||
<div class="spinner"></div>
|
||||
<span>${window.i18n ? window.i18n.t('files.loading') : 'Loading files…'}</span>
|
||||
<span>${i18n ? i18n.t('files.loading') : 'Loading files…'}</span>
|
||||
</div>
|
||||
`);
|
||||
}, 100);
|
||||
|
||||
if (!app.userHomeFolderId) {
|
||||
await window.resolveHomeFolder();
|
||||
await resolveHomeFolder();
|
||||
}
|
||||
|
||||
const timestamp = Math.floor(Date.now() / 1000);
|
||||
@@ -143,9 +147,9 @@ async function loadFiles(options = { insertHistory: true }) {
|
||||
await rebuildBreadCrumb();
|
||||
|
||||
// request a breadcrumb paint
|
||||
window.ui.updateBreadcrumb();
|
||||
ui.updateBreadcrumb();
|
||||
|
||||
window.updateHistory(options.insertHistory || false);
|
||||
updateHistory(options.insertHistory || false);
|
||||
|
||||
let url;
|
||||
|
||||
@@ -154,7 +158,7 @@ async function loadFiles(options = { insertHistory: true }) {
|
||||
url = `/api/folders/${app.userHomeFolderId}/listing?t=${timestamp}`;
|
||||
app.currentPath = app.userHomeFolderId;
|
||||
app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
ui.updateBreadcrumb();
|
||||
console.log(`Loading user folder: ${app.userHomeFolderName} (${app.userHomeFolderId})`);
|
||||
} else {
|
||||
url = `/api/folders?t=${timestamp}`;
|
||||
@@ -193,7 +197,7 @@ async function loadFiles(options = { insertHistory: true }) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
console.warn('Auth error when loading files, showing empty list');
|
||||
// FIXME: i18n
|
||||
window.ui.showError(`<p>Could not load files</p>`);
|
||||
ui.showError(`<p>Could not load files</p>`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -203,44 +207,44 @@ async function loadFiles(options = { insertHistory: true }) {
|
||||
|
||||
const listing = await response.json();
|
||||
|
||||
window.ui._items.clear();
|
||||
window.ui.resetFilesList();
|
||||
if (window.multiSelect) {
|
||||
window.multiSelect.clear();
|
||||
window.multiSelect.init(); // this will wire buttons & select-all-checkbox
|
||||
ui._items.clear();
|
||||
ui.resetFilesList();
|
||||
if (multiSelect) {
|
||||
multiSelect.clear();
|
||||
multiSelect.init(); // this will wire buttons & select-all-checkbox
|
||||
}
|
||||
|
||||
const folderList = Array.isArray(listing.folders) ? listing.folders : [];
|
||||
const fileList = Array.isArray(listing.files) ? listing.files : [];
|
||||
|
||||
if (folderList.length === 0 && fileList.length === 0) {
|
||||
window.ui.showEmptyList();
|
||||
ui.showEmptyList();
|
||||
} else {
|
||||
window.ui.renderFolders(folderList);
|
||||
window.ui.renderFiles(fileList);
|
||||
ui.renderFolders(folderList);
|
||||
ui.renderFiles(fileList);
|
||||
|
||||
// check if a file was provided
|
||||
if (window.app.viewFile) {
|
||||
if (app.viewFile) {
|
||||
let fileFound = null;
|
||||
|
||||
// lookup for the given fle
|
||||
for (const file of fileList) {
|
||||
if (file.id === window.app.viewFile) {
|
||||
if (file.id === app.viewFile) {
|
||||
fileFound = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileFound) {
|
||||
console.log(`file ${window.app.viewFile} found, calling viewer`);
|
||||
await window.inlineViewer.openFile(fileFound);
|
||||
console.log(`file ${app.viewFile} found, calling viewer`);
|
||||
await inlineViewer.openFile(fileFound);
|
||||
} else {
|
||||
// remove file
|
||||
console.log(`file ${window.app.viewFile} not found`);
|
||||
window.app.viewFile = null;
|
||||
console.log(`file ${app.viewFile} not found`);
|
||||
app.viewFile = null;
|
||||
|
||||
// correct url/history as file is not found
|
||||
window.updateHistory(false);
|
||||
updateHistory(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,10 +252,10 @@ async function loadFiles(options = { insertHistory: true }) {
|
||||
console.log(`Loaded ${folderList.length} folders and ${fileList.length} files`);
|
||||
} catch (error) {
|
||||
console.error('Error loading folders:', error);
|
||||
window.ui.showNotification('Error', 'Could not load files and folders');
|
||||
ui.showNotification('Error', 'Could not load files and folders');
|
||||
} finally {
|
||||
window.isLoadingFiles = false;
|
||||
isLoadingFiles = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.loadFiles = loadFiles;
|
||||
export { loadFiles };
|
||||
|
||||
+78
-70
@@ -3,8 +3,31 @@
|
||||
* This file contains the core functionality, initialization and state management
|
||||
*/
|
||||
|
||||
const app = window.app;
|
||||
const elements = window.appElements;
|
||||
import { formatFileSize, formatQuotaSize } from '../core/formatters.js';
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { fileOps } from '../features/files/fileOperations.js';
|
||||
import { multiSelect } from '../features/files/multiSelect.js';
|
||||
import { favorites } from '../features/library/favorites.js';
|
||||
import { recent } from '../features/library/recent.js';
|
||||
import { fileSharing } from '../features/sharing/fileSharing.js';
|
||||
import { sharedView } from '../views/shared/sharedView.js';
|
||||
import { checkAuthentication } from './authSession.js';
|
||||
import { loadFiles } from './filesView.js';
|
||||
import {
|
||||
switchToFavoritesSection,
|
||||
switchToFilesSection,
|
||||
switchToMusicSection,
|
||||
switchToPhotosSection,
|
||||
switchToRecentFilesSection,
|
||||
switchToSharedSection,
|
||||
switchToTrashSection
|
||||
} from './navigation.js';
|
||||
import { performSearch } from './searchView.js';
|
||||
import { app, appElements as elements } from './state.js';
|
||||
import { loadTrashItems } from './trashView.js';
|
||||
import { ui } from './ui.js';
|
||||
import { setupUserMenu } from './userMenu.js';
|
||||
|
||||
// Upload dropdown listener state (prevents accumulated listeners)
|
||||
/** @type { function | null } */
|
||||
@@ -125,8 +148,8 @@ function setActionsBarMode(mode, force = false) {
|
||||
elements.gridViewBtn = document.getElementById('grid-view-btn');
|
||||
elements.listViewBtn = document.getElementById('list-view-btn');
|
||||
|
||||
if (window.i18n?.translateElement) {
|
||||
window.i18n.translateElement(elements.actionsBar);
|
||||
if (i18n?.translateElement) {
|
||||
i18n.translateElement(elements.actionsBar);
|
||||
}
|
||||
|
||||
if (mode === 'files') {
|
||||
@@ -159,7 +182,7 @@ function setupActionsBarDelegation() {
|
||||
break;
|
||||
}
|
||||
case 'new-folder-btn': {
|
||||
const folderName = await window.Modal.promptNewFolder();
|
||||
const folderName = await Modal.promptNewFolder();
|
||||
if (folderName) {
|
||||
fileOps.createFolder(folderName);
|
||||
}
|
||||
@@ -173,14 +196,14 @@ function setupActionsBarDelegation() {
|
||||
break;
|
||||
case 'empty-trash-btn':
|
||||
if (await fileOps.emptyTrash()) {
|
||||
window.loadTrashItems();
|
||||
loadTrashItems();
|
||||
}
|
||||
break;
|
||||
case 'clear-recent-btn':
|
||||
if (window.recent) {
|
||||
window.recent.clearRecentFiles();
|
||||
window.recent.displayRecentFiles();
|
||||
window.ui.showNotification('Cleanup completed', 'Recent files history has been cleared');
|
||||
if (recent) {
|
||||
recent.clearRecentFiles();
|
||||
recent.displayRecentFiles();
|
||||
ui.showNotification('Cleanup completed', 'Recent files history has been cleared');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -245,8 +268,6 @@ function deserializeHash() {
|
||||
* @param {boolean} insertHistory true to change url and browser's history, false to change url only
|
||||
*/
|
||||
function updateHistory(insertHistory) {
|
||||
const app = window.app;
|
||||
|
||||
const historyData = {
|
||||
section: app.currentSection,
|
||||
id: app.currentFolder,
|
||||
@@ -259,8 +280,8 @@ function updateHistory(insertHistory) {
|
||||
historyData.id = app.currentFolder;
|
||||
historyUrl = historyUrl.concat('/folder/', app.currentFolderInfo.id);
|
||||
|
||||
if (window.app.viewFile) {
|
||||
historyUrl = historyUrl.concat('/file/', window.app.viewFile);
|
||||
if (app.viewFile) {
|
||||
historyUrl = historyUrl.concat('/file/', app.viewFile);
|
||||
}
|
||||
// update title
|
||||
document.title = `OxiCloud: ${app.currentFolderInfo.path}`;
|
||||
@@ -281,7 +302,7 @@ function updateHistory(insertHistory) {
|
||||
* @returns
|
||||
*/
|
||||
function switchSectionTo(section) {
|
||||
if (window.app.currentSection === section)
|
||||
if (app.currentSection === section)
|
||||
// no change ...
|
||||
return;
|
||||
|
||||
@@ -324,8 +345,8 @@ function initApp() {
|
||||
cacheElements();
|
||||
|
||||
// Initialize file sharing module first
|
||||
if (window.fileSharing?.init) {
|
||||
window.fileSharing.init();
|
||||
if (fileSharing?.init) {
|
||||
fileSharing.init();
|
||||
} else {
|
||||
console.warn('fileSharing module not fully initialized');
|
||||
}
|
||||
@@ -338,35 +359,26 @@ function initApp() {
|
||||
// Setup event listeners
|
||||
setupEventListeners();
|
||||
|
||||
// Ensure inline viewer is initialized
|
||||
if (!window.inlineViewer && typeof InlineViewer !== 'undefined') {
|
||||
try {
|
||||
window.inlineViewer = new InlineViewer();
|
||||
} catch (e) {
|
||||
console.error('Error initializing inline viewer:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize favorites module if available
|
||||
if (window.favorites?.init) {
|
||||
if (favorites?.init) {
|
||||
console.log('Initializing favorites module');
|
||||
window.favorites.init();
|
||||
favorites.init();
|
||||
} else {
|
||||
console.warn('Favorites module not available or not initializable');
|
||||
}
|
||||
|
||||
// Initialize recent files module if available
|
||||
if (window.recent?.init) {
|
||||
if (recent?.init) {
|
||||
console.log('Initializing recent files module');
|
||||
window.recent.init();
|
||||
recent.init();
|
||||
} else {
|
||||
console.warn('Recent files module not available or not initializable');
|
||||
}
|
||||
|
||||
// Initialize multi-select / batch actions
|
||||
if (window.multiSelect?.init) {
|
||||
if (multiSelect?.init) {
|
||||
console.log('Initializing multi-select module');
|
||||
window.multiSelect.init();
|
||||
multiSelect.init();
|
||||
}
|
||||
|
||||
window.addEventListener('authenticationDone', () => {
|
||||
@@ -376,33 +388,33 @@ function initApp() {
|
||||
if (hashContext.section === 'files') {
|
||||
if (hashContext.path) {
|
||||
console.log(`init: reusing folder from hash URL: ${hashContext.path}`);
|
||||
window.app.currentPath = hashContext.path;
|
||||
app.currentPath = hashContext.path;
|
||||
}
|
||||
|
||||
if (hashContext.file !== null) {
|
||||
window.app.viewFile = hashContext.file;
|
||||
app.viewFile = hashContext.file;
|
||||
}
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for translations to load before checking authentication
|
||||
if (window.i18n?.isLoaded?.()) {
|
||||
if (i18n?.isLoaded?.()) {
|
||||
// Translations already loaded, proceed with authentication
|
||||
window.checkAuthentication();
|
||||
checkAuthentication();
|
||||
} else {
|
||||
// Wait for translations to be loaded before proceeding
|
||||
console.log('Waiting for translations to load...');
|
||||
window.addEventListener('translationsLoaded', () => {
|
||||
console.log('Translations loaded, proceeding with authentication');
|
||||
window.checkAuthentication();
|
||||
checkAuthentication();
|
||||
});
|
||||
|
||||
// Set a timeout as a fallback in case translations take too long
|
||||
setTimeout(() => {
|
||||
if (!window.i18n?.isLoaded?.()) {
|
||||
if (!i18n?.isLoaded?.()) {
|
||||
console.warn('Translations loading timeout, proceeding with authentication anyway');
|
||||
window.checkAuthentication();
|
||||
checkAuthentication();
|
||||
}
|
||||
}, 3000); // 3 second timeout
|
||||
}
|
||||
@@ -493,14 +505,14 @@ function setupEventListeners() {
|
||||
const hashContext = deserializeHash();
|
||||
switchSectionTo(hashContext.section);
|
||||
if (hashContext.path) {
|
||||
window.app.currentPath = hashContext.path;
|
||||
window.loadFiles({ insertHistory: false });
|
||||
app.currentPath = hashContext.path;
|
||||
loadFiles({ insertHistory: false });
|
||||
}
|
||||
} else {
|
||||
// change is from history, data provided in event
|
||||
switchSectionTo(e.state.section);
|
||||
window.app.currentPath = e.state.id;
|
||||
window.loadFiles({ insertHistory: false });
|
||||
app.currentPath = e.state.id;
|
||||
loadFiles({ insertHistory: false });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -512,19 +524,19 @@ function setupEventListeners() {
|
||||
const query = elements.searchInput.value.trim();
|
||||
|
||||
// In shared view, filter locally
|
||||
if (app.isSharedView && window.sharedView) {
|
||||
window.sharedView.filterAndSortItems();
|
||||
if (app.isSharedView && sharedView) {
|
||||
sharedView.filterAndSortItems();
|
||||
return;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
window.performSearch(query);
|
||||
performSearch(query);
|
||||
} else if (app.isSearchMode) {
|
||||
// If search is empty and we're in search mode, return to normal view
|
||||
app.isSearchMode = false;
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -536,7 +548,7 @@ function setupEventListeners() {
|
||||
|
||||
if (query.length >= SEARCH_MIN_CHARS) {
|
||||
searchDebounceTimer = setTimeout(() => {
|
||||
window.performSearch(query);
|
||||
performSearch(query);
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
} else if (query.length === 0 && app.isSearchMode) {
|
||||
// User cleared the search input — return to normal view
|
||||
@@ -544,7 +556,7 @@ function setupEventListeners() {
|
||||
app.isSearchMode = false;
|
||||
app.currentPath = '';
|
||||
ui.updateBreadcrumb('');
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
}
|
||||
});
|
||||
@@ -554,7 +566,7 @@ function setupEventListeners() {
|
||||
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
||||
const query = elements.searchInput.value.trim();
|
||||
if (query) {
|
||||
window.performSearch(query);
|
||||
performSearch(query);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -627,12 +639,12 @@ function setupEventListeners() {
|
||||
|
||||
default:
|
||||
// Use the proper switchToFilesView function which handles all UI restoration
|
||||
window.switchToFilesSection();
|
||||
switchToFilesSection();
|
||||
// FIXME: because fileview handles it: need to converge code
|
||||
_updateHistory = false;
|
||||
}
|
||||
|
||||
document.title = `OxiCloud: ${window.i18n.t(itemI18nKey)}`;
|
||||
document.title = `OxiCloud: ${i18n.t(itemI18nKey)}`;
|
||||
|
||||
if (_updateHistory) {
|
||||
updateHistory(true);
|
||||
@@ -649,7 +661,7 @@ function setupEventListeners() {
|
||||
}
|
||||
|
||||
// User menu
|
||||
window.setupUserMenu();
|
||||
setupUserMenu();
|
||||
|
||||
// Global events to close context menus and deselect cards
|
||||
document.addEventListener('click', (e) => {
|
||||
@@ -666,18 +678,19 @@ function setupEventListeners() {
|
||||
});
|
||||
}
|
||||
|
||||
// Expose needed functions to global scope
|
||||
window.setActionsBarMode = setActionsBarMode;
|
||||
// View-switching actions moved to app/navigation.js
|
||||
|
||||
// Set up global selectFolder function for navigation
|
||||
window.selectFolder = (id, name) => {
|
||||
/**
|
||||
* Navigate into a folder and refresh the file list.
|
||||
* @param {string} id
|
||||
* @param {string} name
|
||||
*/
|
||||
export function selectFolder(id, name) {
|
||||
app.breadcrumbPath.push({ id, name });
|
||||
app.currentPath = id;
|
||||
ui.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
};
|
||||
|
||||
// View-switching actions moved to app/navigation.js
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the storage usage display with the user's actual storage usage
|
||||
@@ -719,8 +732,8 @@ function updateStorageUsageDisplay(userData) {
|
||||
storageInfo.removeAttribute('data-i18n');
|
||||
|
||||
// Use i18n if available
|
||||
if (window.i18n?.t) {
|
||||
storageInfo.textContent = window.i18n.t('storage.used', {
|
||||
if (i18n?.t) {
|
||||
storageInfo.textContent = i18n.t('storage.used', {
|
||||
percentage: usagePercentage,
|
||||
used: usedFormatted,
|
||||
total: quotaFormatted
|
||||
@@ -733,9 +746,4 @@ function updateStorageUsageDisplay(userData) {
|
||||
console.log(`Updated storage display: ${usagePercentage}% (${usedFormatted} / ${quotaFormatted})`);
|
||||
}
|
||||
|
||||
window.updateStorageUsageDisplay = updateStorageUsageDisplay;
|
||||
|
||||
// Initialize app when DOM is ready
|
||||
window.initApp = initApp;
|
||||
window.updateHistory = updateHistory;
|
||||
window.deserializeHash = deserializeHash;
|
||||
export { deserializeHash, initApp, setActionsBarMode, updateHistory, updateStorageUsageDisplay };
|
||||
|
||||
+74
-66
@@ -3,6 +3,19 @@
|
||||
* Extracted from main.js to keep navigation concerns isolated.
|
||||
*/
|
||||
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { multiSelect } from '../features/files/multiSelect.js';
|
||||
import { favorites } from '../features/library/favorites.js';
|
||||
import { musicView } from '../features/library/music.js';
|
||||
import { photosView } from '../features/library/photos.js';
|
||||
import { recent } from '../features/library/recent.js';
|
||||
import { sharedView } from '../views/shared/sharedView.js';
|
||||
import { loadFiles } from './filesView.js';
|
||||
import { setActionsBarMode } from './main.js';
|
||||
import { app, appElements } from './state.js';
|
||||
import { loadTrashItems } from './trashView.js';
|
||||
import { ui } from './ui.js';
|
||||
|
||||
/**
|
||||
* Sync the hidden class and inline display for the grid/list containers
|
||||
* based on the current view preference.
|
||||
@@ -12,7 +25,7 @@ function syncViewContainers() {
|
||||
const gridViewBtn = document.getElementById('grid-view-btn');
|
||||
const listViewBtn = document.getElementById('list-view-btn');
|
||||
|
||||
const isGrid = window.app.currentView === 'grid';
|
||||
const isGrid = app.currentView === 'grid';
|
||||
if (isGrid) {
|
||||
filesList.classList.remove('files-list-view');
|
||||
filesList.classList.add('files-grid-view');
|
||||
@@ -89,13 +102,6 @@ function initSidebarToggle() {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Expose functions globally
|
||||
window.sidebarToggle = {
|
||||
open: openSidebar,
|
||||
close: closeSidebar,
|
||||
toggle: toggleSidebar
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize sidebar toggle when DOM is ready
|
||||
@@ -128,17 +134,17 @@ function getSectionFromNavItem(navItem) {
|
||||
* @returns {boolean} true if the section changed
|
||||
*/
|
||||
function setCurrentSection(section) {
|
||||
if (window.app.currentSection === section) return false;
|
||||
if (app.currentSection === section) return false;
|
||||
|
||||
// Set all view flags - true for active section, false for others
|
||||
Object.entries(VIEW_FLAGS).forEach(([key, flag]) => {
|
||||
window.app[flag] = key === section;
|
||||
app[flag] = key === section;
|
||||
});
|
||||
|
||||
window.app.currentSection = section;
|
||||
app.currentSection = section;
|
||||
|
||||
// Update nav item active classes by finding matching item from DOM
|
||||
window.appElements.navItems.forEach((item) => {
|
||||
appElements.navItems.forEach((item) => {
|
||||
const itemSection = getSectionFromNavItem(item);
|
||||
item.classList.toggle('active', itemSection === section);
|
||||
});
|
||||
@@ -146,22 +152,22 @@ function setCurrentSection(section) {
|
||||
// Update page title
|
||||
const titleKey = `nav.${section}`;
|
||||
const defaultTitle = section.charAt(0).toUpperCase() + section.slice(1);
|
||||
window.appElements.pageTitle.textContent = window.i18n ? window.i18n.t(titleKey) : defaultTitle;
|
||||
window.appElements.pageTitle.setAttribute('data-i18n', titleKey);
|
||||
appElements.pageTitle.textContent = i18n ? i18n.t(titleKey) : defaultTitle;
|
||||
appElements.pageTitle.setAttribute('data-i18n', titleKey);
|
||||
|
||||
// Hide sharedView when switching to any other section
|
||||
if (section !== 'shared' && window.sharedView) {
|
||||
window.sharedView.hide();
|
||||
if (section !== 'shared' && sharedView) {
|
||||
sharedView.hide();
|
||||
}
|
||||
|
||||
// Hide photosView when switching to any other section
|
||||
if (section !== 'photos' && window.photosView) {
|
||||
window.photosView.hide();
|
||||
if (section !== 'photos' && photosView) {
|
||||
photosView.hide();
|
||||
}
|
||||
|
||||
// Hide musicView when switching to any other section
|
||||
if (section !== 'music' && window.musicView) {
|
||||
window.musicView.hide();
|
||||
if (section !== 'music' && musicView) {
|
||||
musicView.hide();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -175,27 +181,27 @@ function switchToSharedSection() {
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// Hide actions-bar for shared view
|
||||
window.setActionsBarMode('hidden');
|
||||
setActionsBarMode('hidden');
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
// Hide file containers
|
||||
toggleFileContainer(false);
|
||||
|
||||
// Show shared view
|
||||
if (window.sharedView) {
|
||||
window.sharedView.init();
|
||||
window.sharedView.show();
|
||||
if (sharedView) {
|
||||
sharedView.init();
|
||||
sharedView.show();
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToFilesSection() {
|
||||
if (!setCurrentSection('files')) return;
|
||||
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('files', true);
|
||||
setActionsBarMode('files', true);
|
||||
|
||||
// Show breadcrumb (only in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
@@ -208,22 +214,22 @@ function switchToFilesSection() {
|
||||
syncViewContainers();
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
// Reset to home folder and update breadcrumb
|
||||
window.app.currentPath = window.app.userHomeFolderId || '';
|
||||
window.app.breadcrumbPath = [];
|
||||
window.ui.updateBreadcrumb();
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
app.currentPath = app.userHomeFolderId || '';
|
||||
app.breadcrumbPath = [];
|
||||
ui.updateBreadcrumb();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
function switchToFavoritesSection() {
|
||||
if (!setCurrentSection('favorites')) return;
|
||||
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('favorites');
|
||||
setActionsBarMode('favorites');
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
@@ -236,26 +242,26 @@ function switchToFavoritesSection() {
|
||||
syncViewContainers();
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
if (window.favorites) {
|
||||
window.favorites.displayFavorites();
|
||||
if (favorites) {
|
||||
favorites.displayFavorites();
|
||||
} else {
|
||||
console.error('Favorites module not loaded or initialized');
|
||||
window.ui.showError(`
|
||||
ui.showError(`
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the favorites module</p>
|
||||
`);
|
||||
}
|
||||
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToRecentFilesSection() {
|
||||
if (!setCurrentSection('recent')) return;
|
||||
|
||||
// Set actions bar mode
|
||||
window.setActionsBarMode('recent');
|
||||
setActionsBarMode('recent');
|
||||
|
||||
// Hide breadcrumb (only shown in Files view)
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
@@ -268,18 +274,18 @@ function switchToRecentFilesSection() {
|
||||
syncViewContainers();
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
if (window.recent) {
|
||||
window.recent.displayRecentFiles();
|
||||
if (recent) {
|
||||
recent.displayRecentFiles();
|
||||
} else {
|
||||
console.error('Recent files module not loaded or initialized');
|
||||
window.ui.showError(`
|
||||
ui.showError(`
|
||||
<i class="fas fa-exclamation-circle empty-state-icon error"></i>
|
||||
<p>Error loading the recent module</p>
|
||||
`);
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToPhotosSection() {
|
||||
@@ -290,19 +296,19 @@ function switchToPhotosSection() {
|
||||
breadcrumb?.classList.add('hidden');
|
||||
|
||||
// Hide actions-bar (photos has its own upload via selection bar)
|
||||
window.setActionsBarMode('hidden');
|
||||
setActionsBarMode('hidden');
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
// Hide file containers
|
||||
toggleFileContainer(false);
|
||||
|
||||
// Show photos view
|
||||
if (window.photosView) {
|
||||
window.photosView.show();
|
||||
if (photosView) {
|
||||
photosView.show();
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToTrashSection() {
|
||||
@@ -319,15 +325,15 @@ function switchToTrashSection() {
|
||||
setActionsBarMode('trash');
|
||||
|
||||
//reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
//ensure buttons match the current view
|
||||
syncViewContainers();
|
||||
|
||||
// Load trash items
|
||||
window.loadTrashItems();
|
||||
loadTrashItems();
|
||||
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
function switchToMusicSection() {
|
||||
@@ -341,27 +347,29 @@ function switchToMusicSection() {
|
||||
toggleFileContainer(false);
|
||||
|
||||
// Hide actions-bar
|
||||
window.setActionsBarMode('hidden');
|
||||
setActionsBarMode('hidden');
|
||||
|
||||
// Reset files view + remove any error
|
||||
window.ui.resetFilesList();
|
||||
ui.resetFilesList();
|
||||
|
||||
// Hide list header (created by resetFilesList)
|
||||
const listHeader = document.querySelector('.list-header');
|
||||
listHeader?.classList.add('hidden');
|
||||
|
||||
// Show music view
|
||||
if (window.musicView) {
|
||||
window.musicView.show();
|
||||
if (musicView) {
|
||||
musicView.show();
|
||||
}
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
}
|
||||
|
||||
window.switchToFilesSection = switchToFilesSection;
|
||||
window.switchToSharedSection = switchToSharedSection;
|
||||
window.switchToFavoritesSection = switchToFavoritesSection;
|
||||
window.switchToRecentFilesSection = switchToRecentFilesSection;
|
||||
window.switchToPhotosSection = switchToPhotosSection;
|
||||
window.switchToTrashSection = switchToTrashSection;
|
||||
window.switchToMusicSection = switchToMusicSection;
|
||||
window.syncViewContainers = syncViewContainers;
|
||||
export {
|
||||
switchToFavoritesSection,
|
||||
switchToFilesSection,
|
||||
switchToMusicSection,
|
||||
switchToPhotosSection,
|
||||
switchToRecentFilesSection,
|
||||
switchToSharedSection,
|
||||
switchToTrashSection,
|
||||
syncViewContainers
|
||||
};
|
||||
|
||||
@@ -2,16 +2,23 @@
|
||||
* Search view orchestration logic
|
||||
*/
|
||||
|
||||
async function performSearch(query, sortBy) {
|
||||
const app = window.app;
|
||||
import { search } from '../features/files/search.js';
|
||||
import { resolveHomeFolder } from './authSession.js';
|
||||
import { app } from './state.js';
|
||||
import { ui } from './ui.js';
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string} [sortBy]
|
||||
*/
|
||||
async function performSearch(query, sortBy) {
|
||||
console.log(`Performing search for: "${query}" (sort: ${sortBy || 'relevance'})`);
|
||||
|
||||
try {
|
||||
app.isSearchMode = true;
|
||||
window.ui.updateBreadcrumb(`Search: "${query}"`);
|
||||
ui.updateBreadcrumb(`Search: "${query}"`);
|
||||
|
||||
window.ui.showError(`<h3><i class="fas fa-spinner fa-spin search-spinner"></i> Searching for "${query}"...</h3>`);
|
||||
ui.showError(`<h3><i class="fas fa-spinner fa-spin search-spinner"></i> Searching for "${query}"...</h3>`);
|
||||
|
||||
const options = {
|
||||
recursive: true,
|
||||
@@ -22,7 +29,7 @@ async function performSearch(query, sortBy) {
|
||||
if (!app.isTrashView) {
|
||||
// Ensure we have a valid folder_id before searching
|
||||
if (!app.currentPath || app.currentPath === '') {
|
||||
await window.resolveHomeFolder();
|
||||
await resolveHomeFolder();
|
||||
}
|
||||
|
||||
// Only set folder_id if we have a valid value
|
||||
@@ -32,11 +39,11 @@ async function performSearch(query, sortBy) {
|
||||
// If still no valid folder_id, search will be global (without folder_id)
|
||||
}
|
||||
|
||||
const searchResults = await window.search.searchFiles(query, options);
|
||||
window.search.displaySearchResults(searchResults);
|
||||
const searchResults = await search.searchFiles(query, options);
|
||||
search.displaySearchResults(searchResults);
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
window.ui.showNotification('Error', 'Error performing search');
|
||||
ui.showNotification('Error', 'Error performing search');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +54,4 @@ document.addEventListener('search-resort', (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
window.performSearch = performSearch;
|
||||
export { performSearch };
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Centralized mutable state for app and cached DOM references.
|
||||
*/
|
||||
|
||||
window.app = {
|
||||
export const app = {
|
||||
currentView: 'grid',
|
||||
currentPath: '',
|
||||
currentFolder: null,
|
||||
@@ -29,4 +29,4 @@ window.app = {
|
||||
viewFile: null // current file in inline view
|
||||
};
|
||||
|
||||
window.appElements = {};
|
||||
export const appElements = {};
|
||||
|
||||
+29
-28
@@ -2,13 +2,20 @@
|
||||
* Trash view loading and rendering logic
|
||||
*/
|
||||
|
||||
import { escapeHtml, formatDateTime } from '../core/formatters.js';
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { fileOps } from '../features/files/fileOperations.js';
|
||||
import { multiSelect } from '../features/files/multiSelect.js';
|
||||
import { appElements } from './state.js';
|
||||
import { ui } from './ui.js';
|
||||
|
||||
async function loadTrashItems() {
|
||||
const elements = window.appElements;
|
||||
const elements = appElements;
|
||||
|
||||
try {
|
||||
if (window.multiSelect) window.multiSelect.clear();
|
||||
window.ui.resetFilesList(); // ensure also list visible & error hidden
|
||||
const _tt = window.i18n?.t ? window.i18n.t : (k) => k.split('.').pop();
|
||||
if (multiSelect) multiSelect.clear();
|
||||
ui.resetFilesList(); // ensure also list visible & error hidden
|
||||
const _tt = i18n?.t ? i18n.t : (k) => k.split('.').pop();
|
||||
elements.filesList.innerHTML = `
|
||||
<div class="list-header trash-header">
|
||||
<div data-i18n="files.name">${_tt('files.name')}</div>
|
||||
@@ -19,14 +26,14 @@ async function loadTrashItems() {
|
||||
</div>
|
||||
`;
|
||||
|
||||
window.ui.updateBreadcrumb('');
|
||||
ui.updateBreadcrumb('');
|
||||
|
||||
const trashItems = await window.fileOps.getTrashItems();
|
||||
const trashItems = await fileOps.getTrashItems();
|
||||
|
||||
if (trashItems.length === 0) {
|
||||
window.ui.showError(`
|
||||
ui.showError(`
|
||||
<i class="fas fa-trash empty-state-icon"></i>
|
||||
<p>${window.i18n ? window.i18n.t('trash.empty_state') : 'The trash is empty'}</p>
|
||||
<p>${i18n ? i18n.t('trash.empty_state') : 'The trash is empty'}</p>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
@@ -36,33 +43,27 @@ async function loadTrashItems() {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading trash items:', error);
|
||||
window.ui.showNotification('Error', 'Error loading trash items');
|
||||
ui.showNotification('Error', 'Error loading trash items');
|
||||
}
|
||||
}
|
||||
|
||||
function addTrashItemToView(item) {
|
||||
const elements = window.appElements;
|
||||
const elements = appElements;
|
||||
const isFile = item.item_type === 'file';
|
||||
|
||||
const formattedDate = window.formatDateTime(item.trashed_at);
|
||||
const formattedDate = formatDateTime(item.trashed_at);
|
||||
|
||||
let iconClass;
|
||||
let typeLabel;
|
||||
let iconSpecialClass = '';
|
||||
if (!isFile) {
|
||||
iconClass = item.icon_class || 'fas fa-folder';
|
||||
typeLabel = window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder';
|
||||
typeLabel = i18n ? i18n.t('files.file_types.folder') : 'Folder';
|
||||
} else {
|
||||
iconClass = item.icon_class || (window.ui?.getIconClass ? window.ui.getIconClass(item.name) : 'fas fa-file');
|
||||
iconSpecialClass = window.ui?.getIconSpecialClass ? window.ui.getIconSpecialClass(item.name) : '';
|
||||
iconClass = item.icon_class || (ui?.getIconClass ? ui.getIconClass(item.name) : 'fas fa-file');
|
||||
iconSpecialClass = ui?.getIconSpecialClass ? ui.getIconSpecialClass(item.name) : '';
|
||||
const cat = item.category || '';
|
||||
typeLabel = cat
|
||||
? window.i18n
|
||||
? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat
|
||||
: cat
|
||||
: window.i18n
|
||||
? window.i18n.t('files.file_types.document')
|
||||
: 'Document';
|
||||
typeLabel = cat ? (i18n ? i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat) : i18n ? i18n.t('files.file_types.document') : 'Document';
|
||||
}
|
||||
|
||||
const isFolder = !isFile;
|
||||
@@ -85,10 +86,10 @@ function addTrashItemToView(item) {
|
||||
<div class="path-cell">${escapeHtml(item.original_path || '--')}</div>
|
||||
<div class="date-cell">${escapeHtml(formattedDate)}</div>
|
||||
<div class="actions-cell">
|
||||
<button class="btn-restore" title="${window.i18n ? window.i18n.t('trash.restore') : 'Restore'}">
|
||||
<button class="btn-restore" title="${i18n ? i18n.t('trash.restore') : 'Restore'}">
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
<button class="btn-delete" title="${window.i18n ? window.i18n.t('trash.delete_permanently') : 'Delete permanently'}">
|
||||
<button class="btn-delete" title="${i18n ? i18n.t('trash.delete_permanently') : 'Delete permanently'}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -96,19 +97,19 @@ function addTrashItemToView(item) {
|
||||
|
||||
listElement.querySelector('.btn-restore').addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
if (await window.fileOps.restoreFromTrash(item.id)) {
|
||||
window.loadTrashItems();
|
||||
if (await fileOps.restoreFromTrash(item.id)) {
|
||||
loadTrashItems();
|
||||
}
|
||||
});
|
||||
|
||||
listElement.querySelector('.btn-delete').addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
if (await window.fileOps.deletePermanently(item.id)) {
|
||||
window.loadTrashItems();
|
||||
if (await fileOps.deletePermanently(item.id)) {
|
||||
loadTrashItems();
|
||||
}
|
||||
});
|
||||
|
||||
elements.filesList.appendChild(listElement);
|
||||
}
|
||||
|
||||
window.loadTrashItems = loadTrashItems;
|
||||
export { loadTrashItems };
|
||||
|
||||
+116
-111
@@ -5,6 +5,26 @@
|
||||
|
||||
// @ts-check
|
||||
|
||||
import { escapeHtml, formatDateTime, formatFileSize } from '../core/formatters.js';
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { OxiIcons, replaceIconsInElement } from '../core/icons.js';
|
||||
import { contextMenus } from '../features/files/contextMenus.js';
|
||||
import { fileOps } from '../features/files/fileOperations.js';
|
||||
import { inlineViewer } from '../features/files/inlineViewer.js';
|
||||
import { multiSelect } from '../features/files/multiSelect.js';
|
||||
import { wopiEditor } from '../features/files/wopiEditor.js';
|
||||
import { favorites } from '../features/library/favorites.js';
|
||||
import { recent } from '../features/library/recent.js';
|
||||
import { fileSharing } from '../features/sharing/fileSharing.js';
|
||||
import { loadFiles } from './filesView.js';
|
||||
import { updateHistory } from './main.js';
|
||||
import { syncViewContainers } from './navigation.js';
|
||||
import { app } from './state.js';
|
||||
import { uiFileTypes } from './uiFileTypes.js';
|
||||
import { uiNotifications } from './uiNotifications.js';
|
||||
|
||||
let __rubberBandJustFinished = false;
|
||||
|
||||
// UI Module
|
||||
const ui = {
|
||||
/** @type {HTMLDListElement | null} */
|
||||
@@ -297,13 +317,13 @@ const ui = {
|
||||
document.body.appendChild(playlistDialog);
|
||||
|
||||
document.getElementById('playlist-cancel-btn').addEventListener('click', () => {
|
||||
if (window.contextMenus) window.contextMenus.closePlaylistDialog();
|
||||
if (contextMenus) contextMenus.closePlaylistDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// Assign events to menu items
|
||||
if (window.contextMenus) {
|
||||
window.contextMenus.assignMenuEvents();
|
||||
if (contextMenus) {
|
||||
contextMenus.assignMenuEvents();
|
||||
} else {
|
||||
console.warn('contextMenus module not loaded');
|
||||
}
|
||||
@@ -478,10 +498,10 @@ const ui = {
|
||||
switchToGridView() {
|
||||
this._hydrateViewIfNeeded();
|
||||
|
||||
window.app.currentView = 'grid';
|
||||
app.currentView = 'grid';
|
||||
localStorage.setItem('oxicloud-view', 'grid');
|
||||
|
||||
window.syncViewContainers();
|
||||
syncViewContainers();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -490,10 +510,10 @@ const ui = {
|
||||
switchToListView() {
|
||||
this._hydrateViewIfNeeded();
|
||||
|
||||
window.app.currentView = 'list';
|
||||
app.currentView = 'list';
|
||||
localStorage.setItem('oxicloud-view', 'list');
|
||||
|
||||
window.syncViewContainers();
|
||||
syncViewContainers();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -504,12 +524,12 @@ const ui = {
|
||||
updateBreadcrumb() {
|
||||
const breadcrumb = document.querySelector('.breadcrumb');
|
||||
breadcrumb.innerHTML = '';
|
||||
const path = window.app.breadcrumbPath; // [{id, name}, ...]
|
||||
const path = app.breadcrumbPath; // [{id, name}, ...]
|
||||
|
||||
// Helper function to safely get translation text
|
||||
const getTranslatedText = (key, defaultValue) => {
|
||||
if (!window.i18n?.t) return defaultValue;
|
||||
return window.i18n.t(key);
|
||||
if (!i18n?.t) return defaultValue;
|
||||
return i18n.t(key);
|
||||
};
|
||||
|
||||
// -- Home icon (always present, clickable to go to root) --
|
||||
@@ -519,24 +539,24 @@ const ui = {
|
||||
homeIcon.title = getTranslatedText('breadcrumb.home', 'Home');
|
||||
|
||||
// Home is always clickable if we have a home folder
|
||||
if (window.app.userHomeFolderId) {
|
||||
if (app.userHomeFolderId) {
|
||||
homeIcon.classList.add('breadcrumb-link');
|
||||
homeIcon.addEventListener('click', () => {
|
||||
window.app.breadcrumbPath = [];
|
||||
window.app.currentPath = window.app.userHomeFolderId;
|
||||
app.breadcrumbPath = [];
|
||||
app.currentPath = app.userHomeFolderId;
|
||||
this.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
});
|
||||
}
|
||||
breadcrumb.appendChild(homeIcon);
|
||||
|
||||
// -- Root/Home folder name (if available) is always the first element of the breadcrumb --
|
||||
// TODO clarify the difference between homeIcon & this first element
|
||||
if (window.app.userHomeFolderName) {
|
||||
if (path.length === 0 || path[0].id !== window.app.userHomeFolderId) {
|
||||
if (app.userHomeFolderName) {
|
||||
if (path.length === 0 || path[0].id !== app.userHomeFolderId) {
|
||||
path.unshift({
|
||||
name: window.app.userHomeFolderName,
|
||||
id: window.app.userHomeFolderId
|
||||
name: app.userHomeFolderName,
|
||||
id: app.userHomeFolderId
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -561,10 +581,10 @@ const ui = {
|
||||
// Intermediate segment: clickable – truncate path to this level
|
||||
item.classList.add('breadcrumb-link');
|
||||
item.addEventListener('click', () => {
|
||||
window.app.breadcrumbPath = path.slice(0, index + 1);
|
||||
window.app.currentPath = segment.id;
|
||||
app.breadcrumbPath = path.slice(0, index + 1);
|
||||
app.currentPath = segment.id;
|
||||
this.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
});
|
||||
|
||||
// can drag files on this folder
|
||||
@@ -611,7 +631,7 @@ const ui = {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isViewableFile(file) {
|
||||
return window.uiFileTypes.isViewableFile(file);
|
||||
return uiFileTypes.isViewableFile(file);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -620,7 +640,7 @@ const ui = {
|
||||
* (e.g. trash items).
|
||||
*/
|
||||
getIconClass(fileName) {
|
||||
return window.uiFileTypes.getIconClass(fileName);
|
||||
return uiFileTypes.getIconClass(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -628,7 +648,7 @@ const ui = {
|
||||
* Used as fallback when the backend DTO doesn't include icon_special_class.
|
||||
*/
|
||||
getIconSpecialClass(fileName) {
|
||||
return window.uiFileTypes.getIconSpecialClass(fileName);
|
||||
return uiFileTypes.getIconSpecialClass(fileName);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -637,7 +657,7 @@ const ui = {
|
||||
* @param {string} message - Notification message
|
||||
*/
|
||||
showNotification(title, message) {
|
||||
window.uiNotifications.show(title, message);
|
||||
uiNotifications.show(title, message);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -647,7 +667,7 @@ const ui = {
|
||||
const menu = document.getElementById('folder-context-menu');
|
||||
if (menu) {
|
||||
menu.style.display = 'none';
|
||||
window.app.contextMenuTargetFolder = null;
|
||||
app.contextMenuTargetFolder = null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -658,7 +678,7 @@ const ui = {
|
||||
const menu = document.getElementById('file-context-menu');
|
||||
if (menu) {
|
||||
menu.style.display = 'none';
|
||||
window.app.contextMenuTargetFile = null;
|
||||
app.contextMenuTargetFile = null;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -679,8 +699,8 @@ const ui = {
|
||||
_delegationReady: false,
|
||||
|
||||
_getActiveView() {
|
||||
if (window.app && window.app.currentView === 'list') return 'list';
|
||||
if (window.app && window.app.currentView === 'grid') return 'grid';
|
||||
if (app && app.currentView === 'list') return 'list';
|
||||
if (app && app.currentView === 'grid') return 'grid';
|
||||
|
||||
const stored = localStorage.getItem('oxicloud-view');
|
||||
return stored === 'list' ? 'list' : 'grid';
|
||||
@@ -737,9 +757,9 @@ const ui = {
|
||||
* @param {any} dataTransfer fallback if nothing is selected
|
||||
*/
|
||||
async _dropToFolder(action, targetFolderId, dataTransfer) {
|
||||
const selection = window.multiSelect.getSelection(targetFolderId);
|
||||
const selection = multiSelect.getSelection(targetFolderId);
|
||||
|
||||
window.multiSelect.clear();
|
||||
multiSelect.clear();
|
||||
|
||||
if (selection.fileIds.length === 0 && selection.folderIds.length === 0) {
|
||||
// try to use dataTransfer (direct move without selection)
|
||||
@@ -770,20 +790,20 @@ const ui = {
|
||||
let result;
|
||||
switch (action) {
|
||||
case 'copy':
|
||||
result = await window.fileOps.batchCopy(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
result = await fileOps.batchCopy(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
result = await window.fileOps.batchMove(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
result = await fileOps.batchMove(selection.fileIds, selection.folderIds, targetFolderId);
|
||||
// redraw directory
|
||||
if (result.success > 0) window.loadFiles();
|
||||
if (result.success > 0) loadFiles();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error(`drag and drop: action ${action} unknown`);
|
||||
return;
|
||||
}
|
||||
window.multiSelect.showBatchResult(action, result);
|
||||
multiSelect.showBatchResult(action, result);
|
||||
console.log(result);
|
||||
},
|
||||
|
||||
@@ -837,7 +857,7 @@ const ui = {
|
||||
|
||||
const openFile = async (file) => {
|
||||
if (!file) return;
|
||||
if (window.recent) {
|
||||
if (recent) {
|
||||
document.dispatchEvent(new CustomEvent('file-accessed', { detail: { file } }));
|
||||
}
|
||||
// WOPI editor intercept: open Office documents in the WOPI editor
|
||||
@@ -846,8 +866,8 @@ const ui = {
|
||||
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico', 'heic', 'heif', 'avif', 'tiff'];
|
||||
const isImage = file.mime_type?.startsWith('image/') || imageExts.includes(ext);
|
||||
try {
|
||||
if (!isImage && window.wopiEditor && (await window.wopiEditor.canEdit(file.name))) {
|
||||
await window.wopiEditor.openInModal(file.id, file.name, 'edit');
|
||||
if (!isImage && wopiEditor && (await wopiEditor.canEdit(file.name))) {
|
||||
await wopiEditor.openInModal(file.id, file.name, 'edit');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -855,38 +875,38 @@ const ui = {
|
||||
}
|
||||
|
||||
if (this.isViewableFile(file) || isImage) {
|
||||
if (window.inlineViewer) {
|
||||
window.inlineViewer.openFile(file);
|
||||
if (inlineViewer) {
|
||||
inlineViewer.openFile(file);
|
||||
// update history
|
||||
window.app.viewFile = file.id;
|
||||
window.updateHistory(false);
|
||||
app.viewFile = file.id;
|
||||
updateHistory(false);
|
||||
} else {
|
||||
window.fileOps.downloadFile(file.id, file.name);
|
||||
fileOps.downloadFile(file.id, file.name);
|
||||
}
|
||||
} else {
|
||||
window.fileOps.downloadFile(file.id, file.name);
|
||||
fileOps.downloadFile(file.id, file.name);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateFolder = (card) => {
|
||||
const folderId = card.dataset.folderId;
|
||||
const folderName = card.dataset.folderName;
|
||||
window.app.breadcrumbPath.push({ id: folderId, name: folderName });
|
||||
window.app.currentPath = folderId;
|
||||
app.breadcrumbPath.push({ id: folderId, name: folderName });
|
||||
app.currentPath = folderId;
|
||||
this.updateBreadcrumb();
|
||||
window.loadFiles();
|
||||
loadFiles();
|
||||
};
|
||||
|
||||
const setContextTarget = (card, info) => {
|
||||
if (info.type === 'folder') {
|
||||
window.app.contextMenuTargetFolder = {
|
||||
app.contextMenuTargetFolder = {
|
||||
id: info.id,
|
||||
name: card.dataset.folderName,
|
||||
parent_id: card.dataset.parentId || ''
|
||||
};
|
||||
} else {
|
||||
const fileData = info.data || self._items.get(info.id);
|
||||
window.app.contextMenuTargetFile = {
|
||||
app.contextMenuTargetFile = {
|
||||
id: info.id,
|
||||
name: card.dataset.fileName,
|
||||
folder_id: card.dataset.folderId || '',
|
||||
@@ -932,8 +952,8 @@ const ui = {
|
||||
}
|
||||
|
||||
// shiftkey is used to complete selection
|
||||
if (e.shiftKey && window.multiSelect) {
|
||||
window.multiSelect.handleToggleItem(card, e);
|
||||
if (e.shiftKey && multiSelect) {
|
||||
multiSelect.handleToggleItem(card, e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -962,14 +982,14 @@ const ui = {
|
||||
setContextTarget(card, info);
|
||||
const menuId = info.type === 'folder' ? 'folder-context-menu' : 'file-context-menu';
|
||||
const menu = document.getElementById(menuId);
|
||||
if (window.contextMenus && typeof window.contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
window.contextMenus.syncFavoriteOptionLabels();
|
||||
if (contextMenus && typeof contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
contextMenus.syncFavoriteOptionLabels();
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(() => {});
|
||||
if (contextMenus && typeof contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
contextMenus.syncWopiOptionVisibility().catch(() => {});
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncAddToPlaylistOption === 'function') {
|
||||
window.contextMenus.syncAddToPlaylistOption();
|
||||
if (contextMenus && typeof contextMenus.syncAddToPlaylistOption === 'function') {
|
||||
contextMenus.syncAddToPlaylistOption();
|
||||
}
|
||||
menu.style.left = `${e.pageX}px`;
|
||||
menu.style.top = `${e.pageY}px`;
|
||||
@@ -1105,7 +1125,7 @@ const ui = {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (!window.favorites) return;
|
||||
if (!favorites) return;
|
||||
|
||||
const itemId = star.dataset.itemId;
|
||||
const itemType = star.dataset.itemType;
|
||||
@@ -1115,15 +1135,15 @@ const ui = {
|
||||
|
||||
if (isActive) {
|
||||
this.setFavoriteVisualState(itemId, itemType, false);
|
||||
window.favorites.removeFromFavorites(itemId, itemType);
|
||||
favorites.removeFromFavorites(itemId, itemType);
|
||||
} else {
|
||||
this.setFavoriteVisualState(itemId, itemType, true);
|
||||
window.favorites.addToFavorites(itemId, itemName, itemType);
|
||||
favorites.addToFavorites(itemId, itemName, itemType);
|
||||
}
|
||||
|
||||
// Keep context-menu label in sync if available
|
||||
if (window.contextMenus && typeof window.contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
window.contextMenus.syncFavoriteOptionLabels();
|
||||
if (contextMenus && typeof contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
contextMenus.syncFavoriteOptionLabels();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -1142,8 +1162,8 @@ const ui = {
|
||||
|
||||
// SVG icon path (after icons.js replacement)
|
||||
const svg = starBtn.querySelector('svg');
|
||||
const filledPath = window.OxiIcons?.star;
|
||||
const outlinePath = window.OxiIcons?.['star-outline'];
|
||||
const filledPath = OxiIcons?.star;
|
||||
const outlinePath = OxiIcons?.['star-outline'];
|
||||
const targetPath = isFavorite ? filledPath : outlinePath;
|
||||
if (svg && targetPath) {
|
||||
const p = svg.querySelector('path');
|
||||
@@ -1168,9 +1188,7 @@ const ui = {
|
||||
inlineStar = document.createElement('i');
|
||||
inlineStar.className = 'fas fa-star favorite-star-inline';
|
||||
nameCell.appendChild(inlineStar);
|
||||
if (window.OxiIcons && typeof window.OxiIcons.replaceIconsInElement === 'function') {
|
||||
window.OxiIcons.replaceIconsInElement(nameCell);
|
||||
}
|
||||
replaceIconsInElement(nameCell);
|
||||
} else if (!isFavorite && inlineStar) {
|
||||
inlineStar.remove();
|
||||
}
|
||||
@@ -1190,8 +1208,8 @@ const ui = {
|
||||
el.dataset.folderName = folder.name;
|
||||
el.dataset.parentId = folder.parent_id || '';
|
||||
|
||||
const isFav = window.favorites?.isFavorite(folder.id, 'folder');
|
||||
const formattedDate = window.formatDateTime(folder.modified_at);
|
||||
const isFav = favorites?.isFavorite(folder.id, 'folder');
|
||||
const formattedDate = formatDateTime(folder.modified_at);
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="checkbox-cell"><input type="checkbox" class="item-checkbox"></div>
|
||||
@@ -1202,7 +1220,7 @@ const ui = {
|
||||
<span>${escapeHtml(folder.name)}</span>
|
||||
${isFav ? '<i class="fas fa-star favorite-star-inline"></i>' : ''}
|
||||
</div>
|
||||
<div class="type-cell">${window.i18n ? window.i18n.t('files.file_types.folder') : 'Folder'}</div>
|
||||
<div class="type-cell">${i18n ? i18n.t('files.file_types.folder') : 'Folder'}</div>
|
||||
<div class="size-cell">--</div>
|
||||
<div class="date-cell">${formattedDate}</div>
|
||||
<div class="action-cell">
|
||||
@@ -1213,7 +1231,7 @@ const ui = {
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (window.app.currentPath !== '') {
|
||||
if (app.currentPath !== '') {
|
||||
el.setAttribute('draggable', 'true');
|
||||
}
|
||||
this._bindStarClick(el);
|
||||
@@ -1225,16 +1243,10 @@ const ui = {
|
||||
const iconClass = file.icon_class || this.getIconClass(file.name);
|
||||
const iconSpecialClass = file.icon_special_class || this.getIconSpecialClass(file.name);
|
||||
const cat = file.category || '';
|
||||
const typeLabel = cat
|
||||
? window.i18n
|
||||
? window.i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat
|
||||
: cat
|
||||
: window.i18n
|
||||
? window.i18n.t('files.file_types.document')
|
||||
: 'Document';
|
||||
const fileSize = file.size_formatted || window.formatFileSize(file.size);
|
||||
const formattedDate = window.formatDateTime(file.modified_at);
|
||||
const isFav = window.favorites?.isFavorite(file.id, 'file');
|
||||
const typeLabel = cat ? (i18n ? i18n.t(`files.file_types.${cat.toLowerCase()}`) || cat : cat) : i18n ? i18n.t('files.file_types.document') : 'Document';
|
||||
const fileSize = file.size_formatted || formatFileSize(file.size);
|
||||
const formattedDate = formatDateTime(file.modified_at);
|
||||
const isFav = favorites?.isFavorite(file.id, 'file');
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'file-item';
|
||||
@@ -1293,7 +1305,7 @@ const ui = {
|
||||
<div></div><!-- actions -->
|
||||
</div>`;
|
||||
|
||||
if (window.i18n?.translateElement) window.i18n.translateElement(filesList);
|
||||
if (i18n?.translateElement) i18n.translateElement(filesList);
|
||||
|
||||
filesList.classList.remove('hidden');
|
||||
filesContainerError?.classList.add('hidden');
|
||||
@@ -1317,7 +1329,7 @@ const ui = {
|
||||
const filesList = document.getElementById('files-list');
|
||||
if (filesContainerError) filesContainerError.innerHTML = content;
|
||||
|
||||
if (window.i18n?.translateElement) window.i18n.translateElement(filesContainerError);
|
||||
if (i18n?.translateElement) i18n.translateElement(filesContainerError);
|
||||
|
||||
filesContainerError?.classList.remove('hidden');
|
||||
filesList?.classList.add('hidden');
|
||||
@@ -1403,8 +1415,8 @@ const ui = {
|
||||
* Routes through the multiSelect module so batch actions know about selected items.
|
||||
*/
|
||||
function toggleCardSelection(card, event) {
|
||||
if (window.multiSelect) {
|
||||
window.multiSelect.handleToggleItem(card, event);
|
||||
if (multiSelect) {
|
||||
multiSelect.handleToggleItem(card, event);
|
||||
} else {
|
||||
card.classList.toggle('selected');
|
||||
}
|
||||
@@ -1435,14 +1447,14 @@ function showContextMenuAtElement(triggerElement, menuId) {
|
||||
top = rect.top - 4 + window.scrollY; // flip above if no room
|
||||
}
|
||||
|
||||
if (window.contextMenus && typeof window.contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
window.contextMenus.syncFavoriteOptionLabels();
|
||||
if (contextMenus && typeof contextMenus.syncFavoriteOptionLabels === 'function') {
|
||||
contextMenus.syncFavoriteOptionLabels();
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
window.contextMenus.syncWopiOptionVisibility().catch(() => {});
|
||||
if (contextMenus && typeof contextMenus.syncWopiOptionVisibility === 'function') {
|
||||
contextMenus.syncWopiOptionVisibility().catch(() => {});
|
||||
}
|
||||
if (window.contextMenus && typeof window.contextMenus.syncAddToPlaylistOption === 'function') {
|
||||
window.contextMenus.syncAddToPlaylistOption();
|
||||
if (contextMenus && typeof contextMenus.syncAddToPlaylistOption === 'function') {
|
||||
contextMenus.syncAddToPlaylistOption();
|
||||
}
|
||||
|
||||
menu.style.left = `${left}px`;
|
||||
@@ -1532,16 +1544,16 @@ function initRubberBandSelection() {
|
||||
card.classList.add('selected');
|
||||
|
||||
// Sync with multiSelect module
|
||||
if (window.multiSelect) {
|
||||
const info = window.multiSelect._extractInfo(card);
|
||||
if (info) window.multiSelect.select(info.id, info.name, info.type, info.parentId);
|
||||
if (multiSelect) {
|
||||
const info = multiSelect._extractInfo(card);
|
||||
if (info) multiSelect.select(info.id, info.name, info.type, info.parentId);
|
||||
}
|
||||
} else {
|
||||
card.classList.remove('selected');
|
||||
// Deselect from multiSelect module
|
||||
if (window.multiSelect) {
|
||||
const info = window.multiSelect._extractInfo(card);
|
||||
if (info) window.multiSelect.deselect(info.id);
|
||||
if (multiSelect) {
|
||||
const info = multiSelect._extractInfo(card);
|
||||
if (info) multiSelect.deselect(info.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1553,13 +1565,13 @@ function initRubberBandSelection() {
|
||||
const hadSelection = selRect.style.display === 'block';
|
||||
selRect.style.display = 'none';
|
||||
// Update the batch bar after rubber band selection completes
|
||||
if (window.multiSelect) window.multiSelect._syncUI();
|
||||
if (multiSelect) multiSelect._syncUI();
|
||||
// Suppress the click event that follows mouseup so the global
|
||||
// deselect handler doesn't immediately clear the selection.
|
||||
if (hadSelection) {
|
||||
window.__rubberBandJustFinished = true;
|
||||
__rubberBandJustFinished = true;
|
||||
requestAnimationFrame(() => {
|
||||
window.__rubberBandJustFinished = false;
|
||||
__rubberBandJustFinished = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1572,11 +1584,6 @@ if (document.readyState === 'loading') {
|
||||
initRubberBandSelection();
|
||||
}
|
||||
|
||||
// Expose helpers globally
|
||||
window.toggleCardSelection = toggleCardSelection;
|
||||
window.showContextMenuAtElement = showContextMenuAtElement;
|
||||
window.initRubberBandSelection = initRubberBandSelection;
|
||||
|
||||
/**
|
||||
* Show a modern confirm dialog (replaces native confirm())
|
||||
* @param {Object} options
|
||||
@@ -1588,9 +1595,9 @@ window.initRubberBandSelection = initRubberBandSelection;
|
||||
* @returns {Promise<boolean>} true if confirmed, false if cancelled
|
||||
*/
|
||||
function showConfirmDialog({ title, message, confirmText, cancelText, danger = true } = {}) {
|
||||
const ct = confirmText || (window.i18n ? window.i18n.t('actions.delete') : 'Delete');
|
||||
const cc = cancelText || (window.i18n ? window.i18n.t('actions.cancel') : 'Cancel');
|
||||
const t = title || (window.i18n ? window.i18n.t('dialogs.confirm_title') : 'Confirm action');
|
||||
const ct = confirmText || (i18n ? i18n.t('actions.delete') : 'Delete');
|
||||
const cc = cancelText || (i18n ? i18n.t('actions.cancel') : 'Cancel');
|
||||
const t = title || (i18n ? i18n.t('dialogs.confirm_title') : 'Confirm action');
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Remove any previous confirm dialog
|
||||
@@ -1633,7 +1640,5 @@ function showConfirmDialog({ title, message, confirmText, cancelText, danger = t
|
||||
});
|
||||
});
|
||||
}
|
||||
window.showConfirmDialog = showConfirmDialog;
|
||||
|
||||
// Expose UI module globally
|
||||
window.ui = ui;
|
||||
export { initRubberBandSelection, showConfirmDialog, showContextMenuAtElement, toggleCardSelection, ui };
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Isolated icon and preview classification helpers used by ui.js.
|
||||
*/
|
||||
|
||||
import { isTextViewable } from '../core/formatters.js';
|
||||
|
||||
const uiFileTypes = {
|
||||
// TODO: 'd better to use a canViw() method in inlineViewer
|
||||
isViewableFile(file) {
|
||||
@@ -11,7 +13,7 @@ const uiFileTypes = {
|
||||
if (file.mime_type === 'application/pdf') return true;
|
||||
if (file.mime_type.startsWith('audio/')) return true;
|
||||
if (file.mime_type.startsWith('video/')) return true;
|
||||
return window.isTextViewable ? window.isTextViewable(file.mime_type) : false;
|
||||
return isTextViewable(file.mime_type);
|
||||
},
|
||||
|
||||
getIconClass(fileName) {
|
||||
@@ -170,4 +172,4 @@ const uiFileTypes = {
|
||||
}
|
||||
};
|
||||
|
||||
window.uiFileTypes = uiFileTypes;
|
||||
export { uiFileTypes };
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
* Isolates notification rendering policy from ui.js.
|
||||
*/
|
||||
|
||||
import { notifications } from '../core/notifications.js';
|
||||
|
||||
const uiNotifications = {
|
||||
show(title, message) {
|
||||
if (window.notifications && typeof window.notifications.addNotification === 'function') {
|
||||
if (notifications && typeof notifications.addNotification === 'function') {
|
||||
const normalizedTitle = String(title || '').toLowerCase();
|
||||
let icon = 'fa-info-circle';
|
||||
let iconClass = 'upload';
|
||||
@@ -27,7 +29,7 @@ const uiNotifications = {
|
||||
iconClass = 'success';
|
||||
}
|
||||
|
||||
window.notifications.addNotification({
|
||||
notifications.addNotification({
|
||||
icon,
|
||||
iconClass,
|
||||
title: title || '',
|
||||
@@ -58,4 +60,4 @@ const uiNotifications = {
|
||||
}
|
||||
};
|
||||
|
||||
window.uiNotifications = uiNotifications;
|
||||
export { uiNotifications };
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
* User menu, profile modal and logout logic
|
||||
*/
|
||||
|
||||
import { getCsrfHeaders } from '../core/csrf.js';
|
||||
import { formatFileSize, formatQuotaSize } from '../core/formatters.js';
|
||||
import { i18n } from '../core/i18n.js';
|
||||
import { ui } from './ui.js';
|
||||
|
||||
function setupUserMenu() {
|
||||
const wrapper = document.getElementById('user-menu-wrapper');
|
||||
const avatarBtn = document.getElementById('user-avatar-btn');
|
||||
@@ -102,7 +107,7 @@ function setupUserMenu() {
|
||||
}
|
||||
}
|
||||
|
||||
window.ui.showNotification(newIsDark ? '🌙' : '☀️', newIsDark ? 'Dark mode enabled' : 'Light mode enabled');
|
||||
ui.showNotification(newIsDark ? '🌙' : '☀️', newIsDark ? 'Dark mode enabled' : 'Light mode enabled');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,8 +178,8 @@ function updateUserMenuData() {
|
||||
|
||||
if (storageFill) storageFill.style.width = `${percentage}%`;
|
||||
if (storageText) {
|
||||
const used = window.formatFileSize(usedBytes);
|
||||
const total = window.formatQuotaSize(quotaBytes);
|
||||
const used = formatFileSize(usedBytes);
|
||||
const total = formatQuotaSize(quotaBytes);
|
||||
storageText.textContent = `${quotaBytes > 0 ? `${percentage}% · ` : ''}${used} / ${total}`;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +211,7 @@ function showUserProfileModal() {
|
||||
const percentage = quotaBytes > 0 ? Math.min(Math.round((usedBytes / quotaBytes) * 100), 100) : 0;
|
||||
const barColor = percentage > 90 ? '#ef4444' : percentage > 70 ? '#f59e0b' : '#22c55e';
|
||||
|
||||
const t = (key, fallback) => (window.i18n?.t ? window.i18n.t(key) || fallback : fallback);
|
||||
const t = (key, fallback) => (i18n?.t ? i18n.t(key) || fallback : fallback);
|
||||
|
||||
const existing = document.getElementById('profile-modal-overlay');
|
||||
if (existing) existing.remove();
|
||||
@@ -229,7 +234,7 @@ function showUserProfileModal() {
|
||||
<div class="about-modal-bar-bg">
|
||||
<div class="about-modal-bar-fill" id="about-bar-fill"></div>
|
||||
</div>
|
||||
<div class="about-modal-bar-text">${percentage}% · ${window.formatFileSize(usedBytes)} / ${window.formatQuotaSize(quotaBytes)}</div>
|
||||
<div class="about-modal-bar-text">${percentage}% · ${formatFileSize(usedBytes)} / ${formatQuotaSize(quotaBytes)}</div>
|
||||
</div>
|
||||
<div class="about-modal-footer">
|
||||
<button id="profile-modal-close" class="about-modal-close-btn">${t('actions.close', 'Close')}</button>
|
||||
@@ -283,6 +288,4 @@ async function logout() {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
|
||||
window.setupUserMenu = setupUserMenu;
|
||||
window.showUserProfileModal = showUserProfileModal;
|
||||
window.logout = logout;
|
||||
export { logout, setupUserMenu, showUserProfileModal };
|
||||
|
||||
Reference in New Issue
Block a user