feat(voyage): add drag-drop with webkitGetAsEntry + Firefox 150 Win guard
This commit is contained in:
parent
e04915882e
commit
974835537a
1 changed files with 187 additions and 0 deletions
|
|
@ -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"
|
||||
/>
|
||||
|
||||
<!-- v4.3 Step 12 — drag-drop overlay. Hidden by default; shown via JS on
|
||||
document-level dragenter. Origin-disclosure tooltip warns operators
|
||||
that Chromium taints drags from non-OS sources. -->
|
||||
<div
|
||||
class="voyage-dropzone"
|
||||
data-drop-target
|
||||
role="region"
|
||||
aria-label="Slipp prosjektmappe her"
|
||||
aria-hidden="true"
|
||||
hidden
|
||||
>
|
||||
<div class="voyage-dropzone__inner">
|
||||
<div class="voyage-dropzone__title">Slipp prosjektmappe her</div>
|
||||
<div class="voyage-dropzone__hint">Dra direkte fra OS-filutforsker for korrekt path-info.</div>
|
||||
</div>
|
||||
</div>
|
||||
<main id="main-content">
|
||||
<section
|
||||
class="guide-panel guide-panel--info"
|
||||
|
|
@ -913,6 +964,138 @@ playground first-run shows a complete round-trip-able artifact.
|
|||
});
|
||||
}
|
||||
|
||||
// ---- v4.3 Step 12 — drag-drop with webkitGetAsEntry ---------------
|
||||
// Document-level dragenter shows the overlay; drop iterates
|
||||
// dataTransfer.items, walks DirectoryEntry.readEntries() recursively
|
||||
// (Chromium 100-entry-cap → loop until empty array), converts to
|
||||
// File[] with synthetic webkitRelativePath, then forwards to
|
||||
// loadProjectDirectory (Step 13). Firefox 150 on Windows triggers a
|
||||
// warn-panel and returns early due to the upstream crash bug.
|
||||
function wireDragDrop() {
|
||||
var dropzone = document.querySelector('[data-drop-target]');
|
||||
if (!dropzone) return;
|
||||
|
||||
var ua = navigator.userAgent || '';
|
||||
var platform = navigator.platform || '';
|
||||
var isFirefox150Windows = /Firefox\/150/.test(ua) && /Win/.test(platform);
|
||||
|
||||
function showWarnPanel(msg) {
|
||||
var warn = document.createElement('section');
|
||||
warn.className = 'guide-panel guide-panel--warn';
|
||||
warn.setAttribute('role', 'status');
|
||||
warn.innerHTML =
|
||||
'<div class="guide-panel__title">Drag-drop deaktivert</div>' +
|
||||
'<div class="guide-panel__body">' + escapeHtml(msg) + '</div>';
|
||||
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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue