← Commit history

moleculize: capture dropped .kicad_pcb on the loaded widget too (whole-card drop target) + prevent the browser opening the file in a new tab

Ray ·d564e98204 ·2mo ago ·parent 567e24c
2 files changed +56−12
files/moleculize-widget.js+28−6
@@ -195,15 +195,36 @@       const input = root.querySelector('[data-file]');       if (!input) return;       input.onchange = () => { if (input.files && input.files[0]) this._loadFile(input.files[0]); };-      const drop = root.querySelector('[data-drop]');-      if (drop) {-        drop.onclick = () => input.click();-        drop.ondragover = e => { e.preventDefault(); drop.classList.add('over'); };-        drop.ondragleave = () => drop.classList.remove('over');-        drop.ondrop = e => { e.preventDefault(); drop.classList.remove('over'); const f = e.dataTransfer && e.dataTransfer.files[0]; if (f) this._loadFile(f); };++      // Accept a dropped .kicad_pcb ANYWHERE on the card — in the empty drop-zone+      // AND once a board is loaded. Without this, a drop on the loaded widget falls+      // through to the browser, which just opens the file in a new tab.+      const card = root.querySelector('.card');+      if (card) {+        const over = e => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; card.classList.add('over'); };+        card.addEventListener('dragenter', over);+        card.addEventListener('dragover', over);+        card.addEventListener('dragleave', e => { if (!card.contains(e.relatedTarget)) card.classList.remove('over'); });+        card.addEventListener('drop', e => {+          e.preventDefault(); e.stopPropagation(); card.classList.remove('over');+          const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];+          if (f) this._loadFile(f);+        });       }+      // Click affordances: the empty drop zone opens the picker; so does the swap.+      const drop = root.querySelector('[data-drop]');+      if (drop) drop.onclick = () => input.click();       const swap = root.querySelector('[data-swap]');       if (swap) swap.onclick = e => { e.stopPropagation(); input.click(); };++      // Belt-and-braces: stop the browser navigating to a file dropped just OUTSIDE+      // the card (the sandboxed readme otherwise opens it in a new tab). Bound once.+      if (typeof window !== 'undefined' && !window.__adomMoleculizeNoOpen) {+        window.__adomMoleculizeNoOpen = true;+        const prevent = e => { e.preventDefault(); };+        window.addEventListener('dragover', prevent, false);+        window.addEventListener('drop', prevent, false);+      }     }      _css() {@@ -215,6 +236,7 @@       :host([theme="dark"]){--bg:#0d1420;--panel:#121a26;--line:#26324a;--line2:#33425f;--text:#dbe2ec;--dim:#8b97ac;--dim2:#66738c;--accent:#00e6dc;--wing:rgba(0,214,204,.07);--board:#2b3a52;--boardln:#4c9dff;--mp:#ffb454;--grid:#2a3651}       *{box-sizing:border-box}       .card{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:16px;color:var(--text);font:14px/1.5 -apple-system,'Segoe UI',Roboto,Arial,sans-serif}+      .card.over{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}       .hd{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}       .hd .t{font:700 16px ui-monospace,'SF Mono',Menlo,monospace}       .hd .badge{font:700 10px ui-monospace,monospace;letter-spacing:.06em;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);padding:2px 8px;border-radius:6px}
readme.html+28−6
@@ -1021,15 +1021,36 @@ h2 .anchor:hover{opacity:1;color:var(--teal)}       const input = root.querySelector('[data-file]');       if (!input) return;       input.onchange = () => { if (input.files && input.files[0]) this._loadFile(input.files[0]); };-      const drop = root.querySelector('[data-drop]');-      if (drop) {-        drop.onclick = () => input.click();-        drop.ondragover = e => { e.preventDefault(); drop.classList.add('over'); };-        drop.ondragleave = () => drop.classList.remove('over');-        drop.ondrop = e => { e.preventDefault(); drop.classList.remove('over'); const f = e.dataTransfer && e.dataTransfer.files[0]; if (f) this._loadFile(f); };++      // Accept a dropped .kicad_pcb ANYWHERE on the card — in the empty drop-zone+      // AND once a board is loaded. Without this, a drop on the loaded widget falls+      // through to the browser, which just opens the file in a new tab.+      const card = root.querySelector('.card');+      if (card) {+        const over = e => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; card.classList.add('over'); };+        card.addEventListener('dragenter', over);+        card.addEventListener('dragover', over);+        card.addEventListener('dragleave', e => { if (!card.contains(e.relatedTarget)) card.classList.remove('over'); });+        card.addEventListener('drop', e => {+          e.preventDefault(); e.stopPropagation(); card.classList.remove('over');+          const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];+          if (f) this._loadFile(f);+        });       }+      // Click affordances: the empty drop zone opens the picker; so does the swap.+      const drop = root.querySelector('[data-drop]');+      if (drop) drop.onclick = () => input.click();       const swap = root.querySelector('[data-swap]');       if (swap) swap.onclick = e => { e.stopPropagation(); input.click(); };++      // Belt-and-braces: stop the browser navigating to a file dropped just OUTSIDE+      // the card (the sandboxed readme otherwise opens it in a new tab). Bound once.+      if (typeof window !== 'undefined' && !window.__adomMoleculizeNoOpen) {+        window.__adomMoleculizeNoOpen = true;+        const prevent = e => { e.preventDefault(); };+        window.addEventListener('dragover', prevent, false);+        window.addEventListener('drop', prevent, false);+      }     }      _css() {@@ -1041,6 +1062,7 @@ h2 .anchor:hover{opacity:1;color:var(--teal)}       :host([theme="dark"]){--bg:#0d1420;--panel:#121a26;--line:#26324a;--line2:#33425f;--text:#dbe2ec;--dim:#8b97ac;--dim2:#66738c;--accent:#00e6dc;--wing:rgba(0,214,204,.07);--board:#2b3a52;--boardln:#4c9dff;--mp:#ffb454;--grid:#2a3651}       *{box-sizing:border-box}       .card{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:16px;color:var(--text);font:14px/1.5 -apple-system,'Segoe UI',Roboto,Arial,sans-serif}+      .card.over{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}       .hd{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}       .hd .t{font:700 16px ui-monospace,'SF Mono',Menlo,monospace}       .hd .badge{font:700 10px ui-monospace,monospace;letter-spacing:.06em;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);padding:2px 8px;border-radius:6px}