From db4535ed6ab81100ade3725b009b2f6db931dd7c Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Wed, 25 Mar 2026 17:47:23 +0100 Subject: [PATCH] feat: capability to move a card into a breadcrumb's folder --- static/css/components/breadcrumb.css | 6 +++ static/js/app/ui.js | 73 +++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/static/css/components/breadcrumb.css b/static/css/components/breadcrumb.css index 2db03859..2d487d18 100755 --- a/static/css/components/breadcrumb.css +++ b/static/css/components/breadcrumb.css @@ -12,6 +12,7 @@ .breadcrumb-item { padding: 2px 4px; border-radius: 4px; + border: 2px solid transparent; transition: background 0.15s, color 0.15s; } @@ -20,6 +21,11 @@ color: #5a6f8a; } +.breadcrumb-link.drop-target { + background-color: rgba(255, 193, 7, 0.1); + border: 2px dashed #ffc107; +} + .breadcrumb-link:hover { text-decoration: underline; color: #ff5e3a; diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 32d6f098..b3f284bc 100755 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -538,6 +538,7 @@ const ui = { const item = document.createElement('span'); item.className = 'breadcrumb-item'; item.textContent = segment.name; + item.dataset.folderId = segment.id; if (!isLast) { // Intermediate segment: clickable – truncate path to this level @@ -548,6 +549,40 @@ const ui = { self.updateBreadcrumb(); window.loadFiles(); }); + + // can drag files on this folder + // dragover – only folders are valid drop targets + item.addEventListener('dragover', (e) => { + const card = e.target.closest("span"); + if (!card || !card.dataset.folderId) return; + e.preventDefault(); + card.classList.add('drop-target'); + }); + + // dragleave + item.addEventListener('dragleave', (e) => { + console.log( "dragleave ",e ); + const card = e.target.closest("span"); + if (!card || !card.dataset.folderId) return; + card.classList.remove('drop-target'); + }); + + // drop – only folders accept drops + item.addEventListener('drop', async (e) => { + const card = e.target.closest("span"); + if (!card) return; + const targetFolderId = card.dataset.folderId; + if (!targetFolderId) return; + + e.preventDefault(); + card.classList.remove('drop-target'); + + const id = e.dataTransfer.getData('text/plain'); + const isFolder = + e.dataTransfer.getData('application/oxicloud-folder') === 'true'; + + await self.move( id, isFolder, targetFolderId); + }); } else { // Last segment: current location, not clickable item.classList.add('breadcrumb-current'); @@ -556,6 +591,32 @@ const ui = { }); }, + + // TODO: support multiple elements to move ? (API does) + /** + * proceed to the drag & drop + * @param {string} sourceId to move (can be a uniq object or) + * @param {boolean} sourceIsAFolder + * @param {string} targetFolderId + */ + async move(sourceId, sourceIsAFolder, targetFolderId) { + if (!sourceId) return; + + console.log(`request move ${ sourceIsAFolder ? "folder": "file"} ${sourceId} to folder ${targetFolderId}`); + + if (sourceIsAFolder) { + if (sourceId === targetFolderId) { + alert("You cannot move a folder to itself"); + return; + } + // TODO: handle errors... + await fileOps.moveFolder(sourceId, targetFolderId); + } else { + // TODO: handle errors... + await fileOps.moveFile(sourceId, targetFolderId); + } + }, + /** * Check if a file can be previewed in the viewer * @param {Object} file - File object with mime_type property @@ -932,17 +993,7 @@ const ui = { const isFolder = e.dataTransfer.getData('application/oxicloud-folder') === 'true'; - if (id) { - if (isFolder) { - if (id === targetFolderId) { - alert("You cannot move a folder to itself"); - return; - } - await fileOps.moveFolder(id, targetFolderId); - } else { - await fileOps.moveFile(id, targetFolderId); - } - } + await self.move( id, isFolder, targetFolderId); }); } },