feat(dragdrop): drag & drop complet de fichiers/dossiers & intégration IA (#89)
This commit is contained in:
Vendored
+144
-6
@@ -9,8 +9,10 @@
|
||||
*/
|
||||
|
||||
import { api } from './auth.js';
|
||||
import { t } from './i18n.js';
|
||||
import { showToast } from './ui.js';
|
||||
import { refreshSidebarTreePreservingState } from './sidebar.js';
|
||||
import { readDataTransferEntries, handleVaultDropUpload } from './upload.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
@@ -180,14 +182,21 @@ function onDragStart(e) {
|
||||
}
|
||||
|
||||
function onDragOver(e) {
|
||||
if (!dragState) return;
|
||||
const isExternalFiles = e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files');
|
||||
|
||||
if (!dragState && !isExternalFiles) return;
|
||||
e.preventDefault(); // Required to allow drop
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
|
||||
if (isExternalFiles) {
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
|
||||
clearAllHighlights();
|
||||
|
||||
const target = getDropTarget(e.target);
|
||||
if (!target || isInvalidDrop(dragState, target)) {
|
||||
if (!target || (!isExternalFiles && isInvalidDrop(dragState, target))) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
@@ -224,17 +233,33 @@ async function onDrop(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!dragState) return;
|
||||
const isExternalFiles = e.dataTransfer && (
|
||||
(e.dataTransfer.files && e.dataTransfer.files.length > 0) ||
|
||||
(e.dataTransfer.items && e.dataTransfer.items.length > 0) ||
|
||||
Array.from(e.dataTransfer.types || []).includes('Files')
|
||||
);
|
||||
|
||||
if (!dragState && !isExternalFiles) return;
|
||||
|
||||
const target = getDropTarget(e.target);
|
||||
if (!target || isInvalidDrop(dragState, target)) {
|
||||
if (!target || (!isExternalFiles && isInvalidDrop(dragState, target))) {
|
||||
onDragEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
const { vault, path, type } = dragState;
|
||||
const { vault: targetVault, targetDir } = target;
|
||||
|
||||
if (isExternalFiles) {
|
||||
onDragEnd();
|
||||
const items = await readDataTransferEntries(e.dataTransfer.items || e.dataTransfer.files);
|
||||
if (items.length > 0) {
|
||||
await handleVaultDropUpload(targetVault, targetDir, items);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const { vault, path, type } = dragState;
|
||||
|
||||
// Don't move if source and target are the same vault
|
||||
if (vault !== targetVault) {
|
||||
showToast(t('dragdrop.cross_vault_unsupported'), 'error');
|
||||
@@ -312,6 +337,119 @@ export function initDragDrop() {
|
||||
makeAllDraggable();
|
||||
});
|
||||
observer.observe(container, { childList: true, subtree: true });
|
||||
|
||||
// Initialize global drop overlay
|
||||
initGlobalDropOverlay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a global dropzone overlay across the entire window
|
||||
* when dragging files/folders from the OS.
|
||||
*/
|
||||
export function initGlobalDropOverlay() {
|
||||
if (document.getElementById('obsigate-global-drop-overlay')) return;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'obsigate-global-drop-overlay';
|
||||
overlay.className = 'obsigate-drop-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="obsigate-drop-backdrop"></div>
|
||||
<div class="obsigate-drop-modal">
|
||||
<h2 class="obsigate-drop-title">${t('dragdrop.overlay_title') || 'Déposer des fichiers ou dossiers'}</h2>
|
||||
<div class="obsigate-drop-zones">
|
||||
<div class="obsigate-drop-zone obsigate-drop-vault" data-zone="vault">
|
||||
<div class="obsigate-drop-zone-icon">📁</div>
|
||||
<div class="obsigate-drop-zone-title">${t('dragdrop.overlay_vault_zone') || 'Importer dans le Vault'}</div>
|
||||
<div class="obsigate-drop-zone-desc" id="obsigate-drop-vault-target"></div>
|
||||
</div>
|
||||
<div class="obsigate-drop-zone obsigate-drop-ai" data-zone="ai">
|
||||
<div class="obsigate-drop-zone-icon">🧠</div>
|
||||
<div class="obsigate-drop-zone-title">${t('dragdrop.overlay_ai_zone') || "Analyser avec l'Assistant IA"}</div>
|
||||
<div class="obsigate-drop-zone-desc">Envoyer au chat BooksLM</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
let dragCounter = 0;
|
||||
|
||||
window.addEventListener('dragenter', (e) => {
|
||||
if (dragState) return; // Don't show global overlay for internal tree reordering
|
||||
const types = Array.from(e.dataTransfer?.types || []);
|
||||
if (!types.includes('Files')) return;
|
||||
|
||||
dragCounter++;
|
||||
if (dragCounter === 1) {
|
||||
// Update target vault description
|
||||
const activeVault = window.state?.activeVault || (document.querySelector('.tree-item.vault-item')?.dataset?.vault) || '';
|
||||
const vaultDesc = overlay.querySelector('#obsigate-drop-vault-target');
|
||||
if (vaultDesc) {
|
||||
vaultDesc.textContent = activeVault ? `Vault : ${activeVault}` : 'Vault par défaut';
|
||||
}
|
||||
overlay.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('dragleave', (e) => {
|
||||
if (dragState) return;
|
||||
const types = Array.from(e.dataTransfer?.types || []);
|
||||
if (!types.includes('Files')) return;
|
||||
|
||||
dragCounter--;
|
||||
if (dragCounter <= 0) {
|
||||
dragCounter = 0;
|
||||
overlay.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('dragover', (e) => {
|
||||
const types = Array.from(e.dataTransfer?.types || []);
|
||||
if (!types.includes('Files')) return;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
const zones = overlay.querySelectorAll('.obsigate-drop-zone');
|
||||
zones.forEach((zone) => {
|
||||
zone.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
zone.classList.add('hover');
|
||||
});
|
||||
|
||||
zone.addEventListener('dragleave', () => {
|
||||
zone.classList.remove('hover');
|
||||
});
|
||||
|
||||
zone.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dragCounter = 0;
|
||||
overlay.classList.remove('active');
|
||||
zone.classList.remove('hover');
|
||||
|
||||
const zoneType = zone.dataset.zone;
|
||||
const items = await readDataTransferEntries(e.dataTransfer.items || e.dataTransfer.files);
|
||||
if (!items.length) return;
|
||||
|
||||
if (zoneType === 'vault') {
|
||||
const activeVault = window.state?.activeVault || (document.querySelector('.tree-item.vault-item')?.dataset?.vault);
|
||||
if (activeVault) {
|
||||
await handleVaultDropUpload(activeVault, '', items);
|
||||
} else {
|
||||
showToast('Aucun vault sélectionné', 'error');
|
||||
}
|
||||
} else if (zoneType === 'ai') {
|
||||
// Dispatch or pass to BooksLM assistant
|
||||
if (window.BooksLMInstance) {
|
||||
window.BooksLMInstance.handleExternalDroppedItems(items);
|
||||
} else {
|
||||
showToast("Assistant IA non initialisé", 'warning');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user