2026-03-22 22:55:42 +01:00
|
|
|
// @ts-check
|
|
|
|
|
|
|
|
|
|
// TODO move to features/files/fileOperations.js ?
|
2026-02-20 12:27:52 +01:00
|
|
|
/**
|
2026-03-22 22:55:42 +01:00
|
|
|
* @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<FolderInfo>}
|
2026-02-20 12:27:52 +01:00
|
|
|
*/
|
2026-03-22 22:55:42 +01:00
|
|
|
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;
|
2026-02-20 12:27:52 +01:00
|
|
|
|
2026-03-22 22:55:42 +01:00
|
|
|
// 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}) {
|
2026-02-20 12:27:52 +01:00
|
|
|
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;
|
|
|
|
|
|
2026-03-23 13:16:31 +01:00
|
|
|
// 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 = `
|
|
|
|
|
<div class="files-loading-spinner">
|
|
|
|
|
<div class="spinner"></div>
|
|
|
|
|
<span>${window.i18n ? window.i18n.t('files.loading') : 'Loading files…'}</span>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}, 100);
|
2026-02-20 12:27:52 +01:00
|
|
|
|
|
|
|
|
if (!app.userHomeFolderId) {
|
|
|
|
|
await window.resolveHomeFolder();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const timestamp = new Date().getTime();
|
2026-03-22 22:55:42 +01:00
|
|
|
|
|
|
|
|
await rebuildBreadCrumb();
|
|
|
|
|
|
|
|
|
|
// request a breadcrumb paint
|
|
|
|
|
window.ui.updateBreadcrumb();
|
|
|
|
|
|
|
|
|
|
window.updateHistory( options.insertHistory || false);
|
|
|
|
|
|
2026-02-20 12:27:52 +01:00
|
|
|
let url;
|
|
|
|
|
|
|
|
|
|
if (!app.currentPath || app.currentPath === '') {
|
|
|
|
|
if (app.userHomeFolderId) {
|
|
|
|
|
url = `/api/folders/${app.userHomeFolderId}/listing?t=${timestamp}`;
|
|
|
|
|
app.currentPath = app.userHomeFolderId;
|
2026-02-21 00:19:04 +01:00
|
|
|
app.breadcrumbPath = [];
|
|
|
|
|
window.ui.updateBreadcrumb();
|
2026-02-20 12:27:52 +01:00
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 22:55:42 +01:00
|
|
|
/** @type {HeadersInit} */
|
2026-02-20 12:27:52 +01:00
|
|
|
const headers = {
|
|
|
|
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
|
|
|
'Pragma': 'no-cache'
|
|
|
|
|
};
|
2026-03-22 22:55:42 +01:00
|
|
|
|
|
|
|
|
/** @type {RequestInit} */
|
2026-02-20 12:27:52 +01:00
|
|
|
const requestOptions = {
|
|
|
|
|
headers,
|
2026-03-03 01:10:50 +01:00
|
|
|
credentials: 'same-origin',
|
2026-02-20 12:27:52 +01:00
|
|
|
cache: 'no-store'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (forceRefresh) {
|
|
|
|
|
url += `&force_refresh=true`;
|
2026-03-22 22:55:42 +01:00
|
|
|
// @ts-ignore
|
2026-02-20 12:27:52 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-03-23 13:16:31 +01:00
|
|
|
// not required anymore
|
|
|
|
|
clearTimeout( loadingFiles);
|
|
|
|
|
|
2026-02-20 12:27:52 +01:00
|
|
|
if (response.status === 401 || response.status === 403) {
|
|
|
|
|
console.warn("Auth error when loading files, showing empty list");
|
|
|
|
|
elements.filesGrid.innerHTML = '<div class="empty-state"><p>Could not load files</p></div>';
|
|
|
|
|
elements.filesListView.innerHTML = `
|
|
|
|
|
<div class="list-header">
|
|
|
|
|
<div class="list-header-checkbox"><input type="checkbox" id="select-all-checkbox" title="Select all"></div>
|
|
|
|
|
<div>Name</div>
|
|
|
|
|
<div>Type</div>
|
|
|
|
|
<div>Size</div>
|
|
|
|
|
<div>Modified</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
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 = `
|
|
|
|
|
<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">${_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>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
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 : [];
|
|
|
|
|
|
2026-03-09 00:08:34 +01:00
|
|
|
if (folderList.length === 0 && fileList.length === 0) {
|
|
|
|
|
const emptyState = document.createElement('div');
|
|
|
|
|
emptyState.className = 'empty-state';
|
|
|
|
|
emptyState.innerHTML = `
|
|
|
|
|
<i class="fas fa-folder-open empty-state-icon"></i>
|
|
|
|
|
<p>${_t('files.no_files')}</p>
|
|
|
|
|
<p>${_t('files.empty_hint')}</p>
|
|
|
|
|
`;
|
|
|
|
|
elements.filesGrid.appendChild(emptyState);
|
|
|
|
|
} else {
|
|
|
|
|
window.ui.renderFolders(folderList);
|
|
|
|
|
window.ui.renderFiles(fileList);
|
|
|
|
|
}
|
2026-02-20 12:27:52 +01:00
|
|
|
|
|
|
|
|
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;
|