diff --git a/plugins/voyage/playground/voyage-playground.html b/plugins/voyage/playground/voyage-playground.html index 65e8b90..6ad4fcb 100644 --- a/plugins/voyage/playground/voyage-playground.html +++ b/plugins/voyage/playground/voyage-playground.html @@ -76,6 +76,40 @@ outline-offset: 2px; } + /* v4.3 Step 12 — drag-drop overlay (hidden until dragenter). */ + .voyage-dropzone { + position: fixed; + inset: 0; + z-index: 200; + background: rgba(0, 0, 0, 0.55); + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + } + .voyage-dropzone[hidden] { display: none; } + .voyage-dropzone--active { + pointer-events: auto; + } + .voyage-dropzone__inner { + background: var(--color-surface); + color: var(--color-text-primary); + border: 2px dashed var(--color-primary-500); + border-radius: var(--radius-md); + padding: var(--space-6) var(--space-8, 32px); + text-align: center; + max-width: 520px; + } + .voyage-dropzone__title { + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); + margin-bottom: var(--space-2); + } + .voyage-dropzone__hint { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + } + /* Render-pipeline layout (left 70% viewport / right 30% sidebar reserved for Step 10). The viewport panel mounts the rendered markdown. */ .voyage-layout { @@ -518,6 +552,23 @@ aria-hidden="true" tabindex="-1" /> + + +
Drag-drop deaktivert' + + '
' + escapeHtml(msg) + '
'; + var main = document.getElementById('main-content'); + if (main) main.insertBefore(warn, main.firstChild); + } + + function showOverlay() { + dropzone.hidden = false; + dropzone.classList.add('voyage-dropzone--active'); + dropzone.setAttribute('aria-hidden', 'false'); + } + function hideOverlay() { + dropzone.hidden = true; + dropzone.classList.remove('voyage-dropzone--active'); + dropzone.setAttribute('aria-hidden', 'true'); + } + + document.addEventListener('dragenter', function (e) { + if (e.dataTransfer && Array.from(e.dataTransfer.types || []).indexOf('Files') !== -1) { + showOverlay(); + } + }); + dropzone.addEventListener('dragover', function (e) { + e.preventDefault(); + if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; + }); + dropzone.addEventListener('dragleave', function (e) { + // Only hide when leaving the overlay entirely (relatedTarget outside). + if (!e.relatedTarget || e.relatedTarget === document.documentElement) { + hideOverlay(); + } + }); + dropzone.addEventListener('drop', function (e) { + e.preventDefault(); + hideOverlay(); + + if (isFirefox150Windows) { + showWarnPanel('Drag-drop deaktivert i Firefox 150 på Windows pga kjent crash-bug. Bruk knappen «Velg prosjektmappe» i stedet.'); + return; + } + + var items = e.dataTransfer && e.dataTransfer.items; + if (!items || !items.length) return; + var collected = []; + var basePath = ''; + var pending = 0; + var allDone = false; + + function maybeFinish() { + if (allDone && pending === 0) { + if (typeof loadProjectDirectory === 'function') { + loadProjectDirectory(collected, basePath); + } else { + try { console.log('[voyage] drag-drop: ' + collected.length + ' files in ' + basePath); } catch (_) {} + } + } + } + + // Recursive walker — readEntries() returns max 100 entries per call, + // so loop until it returns an empty array (Chromium spec). + function walkEntry(entry, prefix) { + if (!entry) return; + if (entry.isFile) { + pending++; + entry.file(function (file) { + try { + Object.defineProperty(file, 'webkitRelativePath', { + value: prefix + entry.name, + configurable: true + }); + } catch (_) { /* property may already exist */ } + collected.push(file); + pending--; + maybeFinish(); + }, function () { + pending--; + maybeFinish(); + }); + } else if (entry.isDirectory) { + if (!basePath) basePath = entry.name; + var reader = entry.createReader(); + function readBatch() { + pending++; + reader.readEntries(function (entries) { + pending--; + if (!entries.length) { + maybeFinish(); + return; + } + for (var i = 0; i < entries.length; i++) { + walkEntry(entries[i], prefix + entry.name + '/'); + } + readBatch(); + }, function () { + pending--; + maybeFinish(); + }); + } + readBatch(); + } + } + + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var entry = item.webkitGetAsEntry && item.webkitGetAsEntry(); + if (entry) walkEntry(entry, ''); + } + allDone = true; + maybeFinish(); + }); + } + // ---- v4.3 Step 9 — renderPageShell -------------------------------- // Universal page-header for dashboard + artifact-detail flater. // Returns an HTML string wrapping body content with DS Tier 3 @@ -1650,6 +1833,10 @@ playground first-run shows a complete round-trip-able artifact. // Step 11 (v4.3) — webkitdirectory project-loader wiring (button + // hidden file-input + browser-support detection). wireProjectLoader(); + + // Step 12 (v4.3) — drag-drop overlay with webkitGetAsEntry recursive + // walker + Firefox 150 Windows UA-guard. + wireDragDrop(); } function setThemeLabel(theme) {