// @ts-check // TODO move to features/files/fileOperations.js ? /** * @typedef {Object} FolderInfo * @property {string} category * @property {number} created_at - timestamp * @property {string} icon_class * @property {string} icon_special_class * @property {string} id the uniq id of the folder * @property {boolean} is_root * @property {number} modified_at * @property {string} name * @property {string} owner_id * @property {string|null} parent_id the folder parent (null if is_root) * @property {string} path the full path */ /** * getFolder information * @param {string} id the id of the folder * @returns {Promise} */ async function getFolder( id) { /** @type {HeadersInit} */ const headers = { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache' }; /** @type {RequestInit} */ const requestOptions = { headers, credentials: 'same-origin', cache: 'no-store' }; let folderInformations = await fetch( `/api/folders/${id}`, requestOptions); if (folderInformations.ok) { return folderInformations.json() } else { console.warn(`Error fetching folder ${id}`); return Promise.reject(null); } } /** * 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} */ let currentFolderInfo = null; // rebuild full breadcrumb, // TODO: to optimize, data may already be known / or ETAG could be interesting to reduce load app.breadcrumbPath = []; /** @type {string | null} */ let id = app.currentPath; // recurse from selected folder to root while (id !== null) { console.log(`fetching folder information for folder ${id}`); try { let folderInfo = await getFolder(id); // store the Leaf which is the current folder if (currentFolderInfo === null) { currentFolderInfo = folderInfo; } // XXX do not enter root into bread crumb updateBreadcrumb() method always display it if(!folderInfo.is_root) { app.breadcrumbPath.unshift( {id: folderInfo.id, name: folderInfo.name}); } // iterate to parent folder id = folderInfo.parent_id; } 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' ); app.breadcrumbPath = []; id = app.userHomeFolderId; app.currentPath = id; } } // store informations on the current folder app.currentFolderInfo = currentFolderInfo; } /** * Files view loading logic * * @param {Object} options * @param {boolean} [options.insertHistory] add browser history (default true) * @param {boolean} [options.forceRefresh] force refresh of content */ async function loadFiles(options = { insertHistory: true}) { const app = window.app; const elements = window.appElements; try { console.log("Starting loadFiles() - loading files...", options); const forceRefresh = options.forceRefresh || false; if (window.isLoadingFiles) { console.log("A file load is already in progress, ignoring request"); return; } window.isLoadingFiles = true; // This to avoid blinking page, a better solution would be to put loading on an overlay and remove timeout let loadingFiles = setTimeout( () => { // display loader after few delay (will be canceled if result take less time) elements.filesGrid.innerHTML = `
${window.i18n ? window.i18n.t('files.loading') : 'Loading files…'}
`; }, 100); if (!app.userHomeFolderId) { await window.resolveHomeFolder(); } const timestamp = new Date().getTime(); await rebuildBreadCrumb(); // request a breadcrumb paint window.ui.updateBreadcrumb(); window.updateHistory( options.insertHistory || false); let url; if (!app.currentPath || app.currentPath === '') { if (app.userHomeFolderId) { url = `/api/folders/${app.userHomeFolderId}/listing?t=${timestamp}`; app.currentPath = app.userHomeFolderId; app.breadcrumbPath = []; window.ui.updateBreadcrumb(); console.log(`Loading user folder: ${app.userHomeFolderName} (${app.userHomeFolderId})`); } else { url = `/api/folders?t=${timestamp}`; console.warn("Emergency fallback to root folder - this should not normally happen"); } } else { url = `/api/folders/${app.currentPath}/listing?t=${timestamp}`; console.log(`Loading subfolder content: ${app.currentPath}`); } /** @type {HeadersInit} */ const headers = { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache' }; /** @type {RequestInit} */ const requestOptions = { headers, credentials: 'same-origin', cache: 'no-store' }; if (forceRefresh) { url += `&force_refresh=true`; // @ts-ignore requestOptions.headers['X-Force-Refresh'] = 'true'; console.log('Forcing complete refresh ignoring cache'); } console.log(`Loading listing from ${url}`); const response = await fetch(url, requestOptions); // not required anymore clearTimeout( loadingFiles); if (response.status === 401 || response.status === 403) { console.warn("Auth error when loading files, showing empty list"); elements.filesGrid.innerHTML = '

Could not load files

'; elements.filesListView.innerHTML = `
Name
Type
Size
Modified
`; return; } if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } const listing = await response.json(); if (window.multiSelect) window.multiSelect.clear(); window.ui._items.clear(); elements.filesGrid.innerHTML = ''; const _t = (window.i18n && window.i18n.t) ? window.i18n.t : k => k.split('.').pop(); elements.filesListView.innerHTML = `
${_t('files.name')}
${_t('files.type')}
${_t('files.size')}
${_t('files.modified')}
`; const selectAllCb = document.getElementById('select-all-checkbox'); if (selectAllCb && window.multiSelect) { selectAllCb.addEventListener('change', () => window.multiSelect.toggleAll()); } const folderList = Array.isArray(listing.folders) ? listing.folders : []; const fileList = Array.isArray(listing.files) ? listing.files : []; if (folderList.length === 0 && fileList.length === 0) { const emptyState = document.createElement('div'); emptyState.className = 'empty-state'; emptyState.innerHTML = `

${_t('files.no_files')}

${_t('files.empty_hint')}

`; elements.filesGrid.appendChild(emptyState); } else { window.ui.renderFolders(folderList); window.ui.renderFiles(fileList); } 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'); } finally { window.isLoadingFiles = false; } } window.loadFiles = loadFiles;