feat: improve drag & drop

* permits multiple drag & drop
 * synchronize grid & list view on selection
 * permits copy during ddrag & drop (use sift/alt key according your OS)
 * use batch move / copy on drag & drop
This commit is contained in:
Edouard Vanbelle
2026-03-25 18:49:38 +01:00
parent 5b821a7eab
commit 534d4dc190
10 changed files with 386 additions and 173 deletions
+62 -19
View File
@@ -76,6 +76,61 @@ const multiSelect = {
}
},
/**
* @typedef {Object} ItemSelection
* @property {string[]} fileIds list of files' id
* @property {string[]} folderIds list of folders' id
*/
/**
* get selection
* @param {string} [targtFolderId] an optional targget (will be removed from selected item)
* @return {ItemSelection}
*/
getSelection(targtFolderId) {
let fileIds=[];
let folderIds=[];
// TODO optimize & check if _selected is a better use
document.querySelectorAll(`div.file-item.selected`).forEach( (item) => {
if (item.dataset.fileId) {
fileIds.push(item.dataset.fileId);
}
else {
// ignore selectedItem if this is the target
if (targtFolderId && targtFolderId !== item.dataset.folderId)
folderIds.push(item.dataset.folderId);
}
});
return {
"fileIds": fileIds,
"folderIds": folderIds,
};
},
/**
* @param {string} action move|copy
* @param {BatchResult} result result of batch
*/
showBatchResult(action, result) {
if (action === "copy") {
if (result.errors > 0) {
window.ui.showNotification('Batch copy', `${result.success} copied, ${result.errors} failed`);
} else {
window.ui.showNotification('Items copied',
`${result.success} item${result.success !== 1 ? 's' : ''} copied successfully`);
}
} else {
if (result.errors > 0) {
window.ui.showNotification('Batch move', `${result.success} moved, ${result.errors} failed`);
} else {
window.ui.showNotification('Items moved',
`${result.success} item${result.success !== 1 ? 's' : ''} moved successfully`);
}
}
},
// ── DOM helpers ─────────────────────────────────────────
_selectElement(el) {
@@ -94,13 +149,6 @@ const multiSelect = {
_getAllVisibleItems() {
return [...document.querySelectorAll('.file-item, .file-card')];
/*
const grid = document.getElementById('files-grid');
if (grid && grid.style.display !== 'none') {
return [...grid.querySelectorAll('.file-card')];
}
return [...document.querySelectorAll('#files-list-view .file-item')];
*/
},
_extractInfo(el) {
@@ -192,6 +240,7 @@ const multiSelect = {
const n = this._selected.size;
const batchSelectionBar = document.getElementById('batch-selection-bar');
const actionsBar = document.getElementById('actions-bar');
if (n > 0) {
this._barVisible = true;
@@ -201,14 +250,18 @@ const multiSelect = {
: (this._t('batch.n_selected', { count: n }) || `${n} items selected`);
document.getElementById("batch-bar-count").innerText = countText;
batchSelectionBar.classList.add('visible');
actionsBar.classList.add('hidden');
batchSelectionBar.classList.remove('hidden');
} else {
this._barVisible = false;
// Hide grid bar
batchSelectionBar.classList.remove('visible');
batchSelectionBar.classList.add('hidden');
if (actionsBar.dataset.mode !== "hidden")
actionsBar.classList.remove('hidden');
}
// Sync individual item checkboxes
@@ -425,9 +478,6 @@ const multiSelect = {
// Wire the initial select-all checkbox
this._injectListHeaderCheckbox();
// Global deselect on empty-area click
this._hookGlobalDeselect();
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.closest('input, textarea, [contenteditable], .rename-dialog, .share-dialog, .confirm-dialog')) return;
@@ -459,13 +509,6 @@ const multiSelect = {
cb.addEventListener('change', () => this.toggleAll());
},
_hookGlobalDeselect() {
document.addEventListener('click', (e) => {
if (window.__rubberBandJustFinished) return;
if (e.target.closest('.file-card, .file-item, .context-menu, .batch-selection-bar, .list-header.selection-mode, .about-modal, .rename-dialog, .share-dialog, .confirm-dialog, .modal-overlay, input, button')) return;
if (this.hasSelection) this.clear();
});
}
};
// Expose globally