Spaces:
Running on Zero
Running on Zero
Fix input, inspector, and responsive UI issues
#1
by PLUS-WAVE - opened
app.py
CHANGED
|
@@ -318,16 +318,13 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 318 |
/* ─────────────── Workspace ─────────────── */
|
| 319 |
.workspace {
|
| 320 |
display: grid;
|
| 321 |
-
grid-template-columns:
|
| 322 |
gap: 1px;
|
| 323 |
background: var(--border);
|
| 324 |
min-height: 0;
|
| 325 |
overflow: hidden;
|
| 326 |
}
|
| 327 |
-
@media (max-width: 1100px) { .workspace { grid-template-columns:
|
| 328 |
-
@media (max-width: 900px) {
|
| 329 |
-
.workspace { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; }
|
| 330 |
-
}
|
| 331 |
|
| 332 |
.col {
|
| 333 |
display: flex; flex-direction: column;
|
|
@@ -376,6 +373,9 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 376 |
min-height: 0;
|
| 377 |
}
|
| 378 |
.panel-body.tight { padding: 8px; }
|
|
|
|
|
|
|
|
|
|
| 379 |
|
| 380 |
/* ─────────────── Drop zone ─────────────── */
|
| 381 |
.drop {
|
|
@@ -390,11 +390,12 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 390 |
transition: border-color 150ms ease, background 150ms ease;
|
| 391 |
}
|
| 392 |
.drop.dragover { border-color: var(--accent); background: var(--accent-soft); }
|
| 393 |
-
.drop.has-image { border-style: solid; border-color: var(--border); cursor:
|
|
|
|
|
|
|
| 394 |
.drop input[type="file"] {
|
| 395 |
-
position: absolute; inset: 0; opacity: 0; cursor: pointer;
|
| 396 |
}
|
| 397 |
-
.drop.has-image input[type="file"] { pointer-events: none; }
|
| 398 |
.drop-empty {
|
| 399 |
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
| 400 |
color: var(--muted); text-align: center; padding: 16px;
|
|
@@ -410,9 +411,29 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 410 |
.drop-empty strong { color: var(--text); font-weight: 600; font-size: 12px; }
|
| 411 |
.drop-empty span { font-size: 11px; color: var(--muted); }
|
| 412 |
.drop-preview {
|
| 413 |
-
width: 100%; height: 100%; object-fit:
|
|
|
|
| 414 |
background: #0a0d10; display: block;
|
|
|
|
| 415 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
.drop-meta {
|
| 417 |
margin-top: 10px;
|
| 418 |
display: grid; grid-template-columns: auto 1fr; gap: 4px 10px;
|
|
@@ -613,6 +634,7 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 613 |
}
|
| 614 |
.section.collapsed .chev { transform: rotate(-90deg); }
|
| 615 |
.section.collapsed .section-body { display: none; }
|
|
|
|
| 616 |
.section-body { padding: 10px 12px; }
|
| 617 |
|
| 618 |
/* Scene tree */
|
|
@@ -682,17 +704,52 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 682 |
border: 1px solid var(--border);
|
| 683 |
border-radius: var(--radius-sm);
|
| 684 |
overflow: hidden;
|
| 685 |
-
cursor:
|
| 686 |
background: var(--surface-2);
|
| 687 |
transition: border-color 120ms ease, transform 120ms ease;
|
| 688 |
}
|
| 689 |
.ex-thumb:hover { border-color: var(--accent); transform: translateY(-1px); }
|
|
|
|
|
|
|
| 690 |
.ex-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
}
|
| 697 |
|
| 698 |
/* ─────────────── Status bar ─────────────── */
|
|
@@ -786,7 +843,7 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 786 |
<main class="workspace">
|
| 787 |
<!-- LEFT: Input -->
|
| 788 |
<section class="col">
|
| 789 |
-
<div class="panel">
|
| 790 |
<div class="panel-head">
|
| 791 |
<div class="bar"></div>
|
| 792 |
<h3>Input</h3>
|
|
@@ -794,7 +851,7 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 794 |
</div>
|
| 795 |
<div class="panel-body">
|
| 796 |
<label class="drop" id="drop">
|
| 797 |
-
<input type="file" id="file" accept="image/*" />
|
| 798 |
<div class="drop-empty" id="dropEmpty">
|
| 799 |
<div class="icon">
|
| 800 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -807,6 +864,14 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 807 |
<span>JPG · PNG · WebP</span>
|
| 808 |
</div>
|
| 809 |
<img class="drop-preview hidden" id="preview" alt="" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
</label>
|
| 811 |
<dl class="drop-meta">
|
| 812 |
<dt>FILE</dt><dd class="mono" id="inputFileName">—</dd>
|
|
@@ -815,6 +880,16 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 815 |
</dl>
|
| 816 |
</div>
|
| 817 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 818 |
</section>
|
| 819 |
|
| 820 |
<!-- CENTER: Viewport -->
|
|
@@ -951,21 +1026,10 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 951 |
<h4>Pipeline log</h4>
|
| 952 |
<span class="chev">▾</span>
|
| 953 |
</div>
|
| 954 |
-
<div class="log mono" id="log">
|
| 955 |
<div class="log-empty">ready · awaiting input</div>
|
| 956 |
</div>
|
| 957 |
</div>
|
| 958 |
-
|
| 959 |
-
<div class="section">
|
| 960 |
-
<div class="section-head" data-toggle>
|
| 961 |
-
<h4>Examples</h4>
|
| 962 |
-
<span class="meta mono" id="examplesCount">—</span>
|
| 963 |
-
<span class="chev">▾</span>
|
| 964 |
-
</div>
|
| 965 |
-
<div class="section-body">
|
| 966 |
-
<div class="examples-grid" id="gallery"></div>
|
| 967 |
-
</div>
|
| 968 |
-
</div>
|
| 969 |
</aside>
|
| 970 |
</main>
|
| 971 |
|
|
@@ -1028,11 +1092,12 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1028 |
|
| 1029 |
function logLine(stage, kind, msg) {
|
| 1030 |
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
|
|
| 1031 |
const line = document.createElement("div");
|
| 1032 |
line.className = "log-line";
|
| 1033 |
line.innerHTML = `
|
| 1034 |
<span class="log-time">${time}</span>
|
| 1035 |
-
<span class="log-stage ${kind}">${
|
| 1036 |
<span class="log-msg">${msg}</span>
|
| 1037 |
`;
|
| 1038 |
// Remove empty placeholder
|
|
@@ -1215,12 +1280,25 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1215 |
return s.toFixed(2) + "s";
|
| 1216 |
}
|
| 1217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1218 |
fileInput.addEventListener("change", (e) => {
|
| 1219 |
const f = e.target.files && e.target.files[0];
|
| 1220 |
if (f) {
|
| 1221 |
pendingFile = f;
|
| 1222 |
showPreview(f);
|
| 1223 |
setReady(true);
|
|
|
|
| 1224 |
}
|
| 1225 |
});
|
| 1226 |
|
|
@@ -1230,7 +1308,17 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1230 |
["dragleave", "drop"].forEach(ev =>
|
| 1231 |
drop.addEventListener(ev, (e) => { e.preventDefault(); drop.classList.remove("dragover"); })
|
| 1232 |
);
|
| 1233 |
-
drop.addEventListener("drop", (e) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1234 |
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
| 1235 |
if (f && f.type.startsWith("image/")) {
|
| 1236 |
pendingFile = f;
|
|
@@ -1241,8 +1329,20 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1241 |
|
| 1242 |
// Collapsible sections
|
| 1243 |
document.querySelectorAll(".section-head[data-toggle]").forEach((head) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1244 |
head.addEventListener("click", () => {
|
| 1245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1246 |
});
|
| 1247 |
});
|
| 1248 |
|
|
@@ -1397,28 +1497,40 @@ INDEX_HTML = r"""<!doctype html>
|
|
| 1397 |
{ label: "Animate", path: "/__examples/animate_room.jpg" },
|
| 1398 |
{ label: "My bedroom", path: "/__examples/my_bedroom.JPG" },
|
| 1399 |
{ label: "Cave", path: "/__examples/cave_ai.jpg" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1400 |
];
|
| 1401 |
gallery.innerHTML = EXAMPLES.map((ex) => `
|
| 1402 |
-
<div class="ex-thumb" data-path="${ex.path}" title="${ex.label}">
|
| 1403 |
-
<img loading="lazy" src="${ex.path}" alt="${ex.label}" />
|
| 1404 |
-
<div class="ex-label">${ex.label}</div>
|
| 1405 |
</div>
|
| 1406 |
`).join("");
|
| 1407 |
examplesCount.textContent = `${EXAMPLES.length}`;
|
| 1408 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1409 |
gallery.addEventListener("click", async (e) => {
|
| 1410 |
const t = e.target.closest(".ex-thumb");
|
| 1411 |
if (!t) return;
|
| 1412 |
const path = t.dataset.path;
|
| 1413 |
try {
|
| 1414 |
-
|
| 1415 |
-
const blob = await resp.blob();
|
| 1416 |
-
const f = new File([blob], path.split("/").pop(), { type: blob.type });
|
| 1417 |
-
pendingFile = f;
|
| 1418 |
-
showPreview(f);
|
| 1419 |
-
setReady(true);
|
| 1420 |
-
logLine("input", "info", `loaded example: ${path.split("/").pop()}`);
|
| 1421 |
-
toastMsg("Example loaded");
|
| 1422 |
} catch (err) {
|
| 1423 |
toastMsg("Failed to load example", true);
|
| 1424 |
logLine("input", "err", err.message || String(err));
|
|
|
|
| 318 |
/* ─────────────── Workspace ─────────────── */
|
| 319 |
.workspace {
|
| 320 |
display: grid;
|
| 321 |
+
grid-template-columns: 300px 1fr 280px;
|
| 322 |
gap: 1px;
|
| 323 |
background: var(--border);
|
| 324 |
min-height: 0;
|
| 325 |
overflow: hidden;
|
| 326 |
}
|
| 327 |
+
@media (max-width: 1100px) { .workspace { grid-template-columns: 280px 1fr 240px; } }
|
|
|
|
|
|
|
|
|
|
| 328 |
|
| 329 |
.col {
|
| 330 |
display: flex; flex-direction: column;
|
|
|
|
| 373 |
min-height: 0;
|
| 374 |
}
|
| 375 |
.panel-body.tight { padding: 8px; }
|
| 376 |
+
.input-panel { flex: 0 0 auto; }
|
| 377 |
+
.examples-panel { flex: 1 1 auto; }
|
| 378 |
+
.examples-panel .panel-body { overflow: auto; }
|
| 379 |
|
| 380 |
/* ─────────────── Drop zone ─────────────── */
|
| 381 |
.drop {
|
|
|
|
| 390 |
transition: border-color 150ms ease, background 150ms ease;
|
| 391 |
}
|
| 392 |
.drop.dragover { border-color: var(--accent); background: var(--accent-soft); }
|
| 393 |
+
.drop.has-image { border-style: solid; border-color: var(--border); cursor: pointer; }
|
| 394 |
+
.drop.has-image:hover,
|
| 395 |
+
.drop.has-image:focus-within { border-color: var(--accent); }
|
| 396 |
.drop input[type="file"] {
|
| 397 |
+
position: absolute; inset: 0; z-index: 3; opacity: 0; cursor: pointer;
|
| 398 |
}
|
|
|
|
| 399 |
.drop-empty {
|
| 400 |
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
| 401 |
color: var(--muted); text-align: center; padding: 16px;
|
|
|
|
| 411 |
.drop-empty strong { color: var(--text); font-weight: 600; font-size: 12px; }
|
| 412 |
.drop-empty span { font-size: 11px; color: var(--muted); }
|
| 413 |
.drop-preview {
|
| 414 |
+
width: 100%; height: 100%; object-fit: cover;
|
| 415 |
+
object-position: center center;
|
| 416 |
background: #0a0d10; display: block;
|
| 417 |
+
transition: opacity 150ms ease;
|
| 418 |
}
|
| 419 |
+
.drop.has-image:hover .drop-preview,
|
| 420 |
+
.drop.has-image:focus-within .drop-preview { opacity: 0.78; }
|
| 421 |
+
.drop-replace {
|
| 422 |
+
position: absolute; left: 8px; right: 8px; bottom: 8px; z-index: 2;
|
| 423 |
+
min-height: 30px;
|
| 424 |
+
display: flex; align-items: center; justify-content: center; gap: 6px;
|
| 425 |
+
padding: 0 10px;
|
| 426 |
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
| 427 |
+
border-radius: var(--radius-sm);
|
| 428 |
+
background: rgba(10, 13, 16, 0.84);
|
| 429 |
+
color: var(--text);
|
| 430 |
+
font-size: 11px; font-weight: 600;
|
| 431 |
+
pointer-events: none;
|
| 432 |
+
opacity: 0; transform: translateY(4px);
|
| 433 |
+
transition: opacity 150ms ease, transform 150ms ease;
|
| 434 |
+
}
|
| 435 |
+
.drop.has-image .drop-replace { opacity: 1; transform: translateY(0); }
|
| 436 |
+
.drop-replace svg { width: 13px; height: 13px; color: var(--accent); }
|
| 437 |
.drop-meta {
|
| 438 |
margin-top: 10px;
|
| 439 |
display: grid; grid-template-columns: auto 1fr; gap: 4px 10px;
|
|
|
|
| 634 |
}
|
| 635 |
.section.collapsed .chev { transform: rotate(-90deg); }
|
| 636 |
.section.collapsed .section-body { display: none; }
|
| 637 |
+
.section:last-child.collapsed { flex: 0 0 auto; min-height: auto; }
|
| 638 |
.section-body { padding: 10px 12px; }
|
| 639 |
|
| 640 |
/* Scene tree */
|
|
|
|
| 704 |
border: 1px solid var(--border);
|
| 705 |
border-radius: var(--radius-sm);
|
| 706 |
overflow: hidden;
|
| 707 |
+
cursor: grab;
|
| 708 |
background: var(--surface-2);
|
| 709 |
transition: border-color 120ms ease, transform 120ms ease;
|
| 710 |
}
|
| 711 |
.ex-thumb:hover { border-color: var(--accent); transform: translateY(-1px); }
|
| 712 |
+
.ex-thumb:active { cursor: grabbing; }
|
| 713 |
+
.ex-thumb.dragging { border-color: var(--accent); opacity: 0.55; }
|
| 714 |
.ex-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
| 715 |
+
|
| 716 |
+
@media (max-width: 900px) {
|
| 717 |
+
body { overflow: auto; }
|
| 718 |
+
.app {
|
| 719 |
+
height: auto;
|
| 720 |
+
min-height: 100vh;
|
| 721 |
+
grid-template-rows: auto auto 24px;
|
| 722 |
+
}
|
| 723 |
+
.workspace {
|
| 724 |
+
display: flex;
|
| 725 |
+
flex-direction: column;
|
| 726 |
+
overflow: visible;
|
| 727 |
+
}
|
| 728 |
+
.col { overflow: visible; }
|
| 729 |
+
.input-panel, .examples-panel { flex: none; }
|
| 730 |
+
.examples-panel .panel-body { max-height: 360px; }
|
| 731 |
+
.examples-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 732 |
+
.viewport { height: max(420px, 60vh); }
|
| 733 |
+
.inspector, .section:last-child { flex: none; }
|
| 734 |
+
.log { max-height: 240px; }
|
| 735 |
+
}
|
| 736 |
+
@media (max-width: 640px) {
|
| 737 |
+
.toolbar {
|
| 738 |
+
grid-template-columns: minmax(0, 1fr) auto;
|
| 739 |
+
grid-template-rows: 34px 28px;
|
| 740 |
+
gap: 0 8px;
|
| 741 |
+
padding: 4px 8px;
|
| 742 |
+
}
|
| 743 |
+
.tb-brand { grid-column: 1; grid-row: 1; border-right: 0; }
|
| 744 |
+
.tb-scene { grid-column: 1 / -1; grid-row: 2; }
|
| 745 |
+
.toolbar > .tb-actions:nth-of-type(3) { grid-column: 2; grid-row: 1; }
|
| 746 |
+
.toolbar > .tb-actions:last-child { display: none; }
|
| 747 |
+
.tb-shortcut { display: none; }
|
| 748 |
+
.examples-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
| 749 |
+
.statusbar { gap: 12px; overflow-x: auto; }
|
| 750 |
+
}
|
| 751 |
+
@media (max-width: 420px) {
|
| 752 |
+
.examples-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
| 753 |
}
|
| 754 |
|
| 755 |
/* ─────────────── Status bar ─────────────── */
|
|
|
|
| 843 |
<main class="workspace">
|
| 844 |
<!-- LEFT: Input -->
|
| 845 |
<section class="col">
|
| 846 |
+
<div class="panel input-panel">
|
| 847 |
<div class="panel-head">
|
| 848 |
<div class="bar"></div>
|
| 849 |
<h3>Input</h3>
|
|
|
|
| 851 |
</div>
|
| 852 |
<div class="panel-body">
|
| 853 |
<label class="drop" id="drop">
|
| 854 |
+
<input type="file" id="file" accept="image/*" aria-label="Upload or replace input image" title="Upload or replace input image" />
|
| 855 |
<div class="drop-empty" id="dropEmpty">
|
| 856 |
<div class="icon">
|
| 857 |
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
| 864 |
<span>JPG · PNG · WebP</span>
|
| 865 |
</div>
|
| 866 |
<img class="drop-preview hidden" id="preview" alt="" />
|
| 867 |
+
<span class="drop-replace" aria-hidden="true">
|
| 868 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
| 869 |
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
| 870 |
+
<polyline points="17 8 12 3 7 8"/>
|
| 871 |
+
<line x1="12" y1="3" x2="12" y2="15"/>
|
| 872 |
+
</svg>
|
| 873 |
+
<span>Replace image</span>
|
| 874 |
+
</span>
|
| 875 |
</label>
|
| 876 |
<dl class="drop-meta">
|
| 877 |
<dt>FILE</dt><dd class="mono" id="inputFileName">—</dd>
|
|
|
|
| 880 |
</dl>
|
| 881 |
</div>
|
| 882 |
</div>
|
| 883 |
+
<div class="panel examples-panel">
|
| 884 |
+
<div class="panel-head">
|
| 885 |
+
<div class="bar"></div>
|
| 886 |
+
<h3>Example images</h3>
|
| 887 |
+
<span class="meta mono" id="examplesCount">—</span>
|
| 888 |
+
</div>
|
| 889 |
+
<div class="panel-body tight">
|
| 890 |
+
<div class="examples-grid" id="gallery"></div>
|
| 891 |
+
</div>
|
| 892 |
+
</div>
|
| 893 |
</section>
|
| 894 |
|
| 895 |
<!-- CENTER: Viewport -->
|
|
|
|
| 1026 |
<h4>Pipeline log</h4>
|
| 1027 |
<span class="chev">▾</span>
|
| 1028 |
</div>
|
| 1029 |
+
<div class="section-body log mono" id="log">
|
| 1030 |
<div class="log-empty">ready · awaiting input</div>
|
| 1031 |
</div>
|
| 1032 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1033 |
</aside>
|
| 1034 |
</main>
|
| 1035 |
|
|
|
|
| 1092 |
|
| 1093 |
function logLine(stage, kind, msg) {
|
| 1094 |
const time = new Date().toLocaleTimeString("en-US", { hour12: false });
|
| 1095 |
+
const stageLabel = stage === "reconstruct" ? "recon." : stage;
|
| 1096 |
const line = document.createElement("div");
|
| 1097 |
line.className = "log-line";
|
| 1098 |
line.innerHTML = `
|
| 1099 |
<span class="log-time">${time}</span>
|
| 1100 |
+
<span class="log-stage ${kind}">${stageLabel}</span>
|
| 1101 |
<span class="log-msg">${msg}</span>
|
| 1102 |
`;
|
| 1103 |
// Remove empty placeholder
|
|
|
|
| 1280 |
return s.toFixed(2) + "s";
|
| 1281 |
}
|
| 1282 |
|
| 1283 |
+
async function loadExample(path) {
|
| 1284 |
+
const resp = await fetch(path);
|
| 1285 |
+
if (!resp.ok) throw new Error(`Example request failed (${resp.status})`);
|
| 1286 |
+
const blob = await resp.blob();
|
| 1287 |
+
const f = new File([blob], path.split("/").pop(), { type: blob.type });
|
| 1288 |
+
pendingFile = f;
|
| 1289 |
+
showPreview(f);
|
| 1290 |
+
setReady(true);
|
| 1291 |
+
logLine("input", "info", `loaded example: ${f.name}`);
|
| 1292 |
+
toastMsg("Example loaded");
|
| 1293 |
+
}
|
| 1294 |
+
|
| 1295 |
fileInput.addEventListener("change", (e) => {
|
| 1296 |
const f = e.target.files && e.target.files[0];
|
| 1297 |
if (f) {
|
| 1298 |
pendingFile = f;
|
| 1299 |
showPreview(f);
|
| 1300 |
setReady(true);
|
| 1301 |
+
e.target.value = "";
|
| 1302 |
}
|
| 1303 |
});
|
| 1304 |
|
|
|
|
| 1308 |
["dragleave", "drop"].forEach(ev =>
|
| 1309 |
drop.addEventListener(ev, (e) => { e.preventDefault(); drop.classList.remove("dragover"); })
|
| 1310 |
);
|
| 1311 |
+
drop.addEventListener("drop", async (e) => {
|
| 1312 |
+
const examplePath = e.dataTransfer.getData("application/x-infinisplat-example");
|
| 1313 |
+
if (examplePath) {
|
| 1314 |
+
try {
|
| 1315 |
+
await loadExample(examplePath);
|
| 1316 |
+
} catch (err) {
|
| 1317 |
+
toastMsg("Failed to load example", true);
|
| 1318 |
+
logLine("input", "err", err.message || String(err));
|
| 1319 |
+
}
|
| 1320 |
+
return;
|
| 1321 |
+
}
|
| 1322 |
const f = e.dataTransfer.files && e.dataTransfer.files[0];
|
| 1323 |
if (f && f.type.startsWith("image/")) {
|
| 1324 |
pendingFile = f;
|
|
|
|
| 1329 |
|
| 1330 |
// Collapsible sections
|
| 1331 |
document.querySelectorAll(".section-head[data-toggle]").forEach((head) => {
|
| 1332 |
+
head.setAttribute("role", "button");
|
| 1333 |
+
head.setAttribute("tabindex", "0");
|
| 1334 |
+
head.setAttribute("aria-expanded", "true");
|
| 1335 |
+
const toggleSection = () => {
|
| 1336 |
+
const collapsed = head.parentElement.classList.toggle("collapsed");
|
| 1337 |
+
head.setAttribute("aria-expanded", String(!collapsed));
|
| 1338 |
+
};
|
| 1339 |
head.addEventListener("click", () => {
|
| 1340 |
+
toggleSection();
|
| 1341 |
+
});
|
| 1342 |
+
head.addEventListener("keydown", (e) => {
|
| 1343 |
+
if (e.key !== "Enter" && e.key !== " ") return;
|
| 1344 |
+
e.preventDefault();
|
| 1345 |
+
toggleSection();
|
| 1346 |
});
|
| 1347 |
});
|
| 1348 |
|
|
|
|
| 1497 |
{ label: "Animate", path: "/__examples/animate_room.jpg" },
|
| 1498 |
{ label: "My bedroom", path: "/__examples/my_bedroom.JPG" },
|
| 1499 |
{ label: "Cave", path: "/__examples/cave_ai.jpg" },
|
| 1500 |
+
{ label: "Old living", path: "/__examples/old_livingroom.png" },
|
| 1501 |
+
{ label: "Courtyard", path: "/__examples/eth3d_courtyard.png" },
|
| 1502 |
+
{ label: "Ghibli real", path: "/__examples/ghibli_realroom.jpg" },
|
| 1503 |
+
{ label: "Flower room", path: "/__examples/flower_room.jpg" },
|
| 1504 |
+
{ label: "Beggar home", path: "/__examples/beggar_home.jpg" },
|
| 1505 |
+
{ label: "ScanNet++", path: "/__examples/scannetpp_fe94fc30cf.JPG" },
|
| 1506 |
+
{ label: "Masi room", path: "/__examples/pexels-masi.jpg" },
|
| 1507 |
+
{ label: "Maksim room", path: "/__examples/maksim-shutov-unsplash.jpg" },
|
| 1508 |
];
|
| 1509 |
gallery.innerHTML = EXAMPLES.map((ex) => `
|
| 1510 |
+
<div class="ex-thumb" data-path="${ex.path}" title="${ex.label}" draggable="true">
|
| 1511 |
+
<img loading="lazy" src="${ex.path}" alt="${ex.label}" draggable="false" />
|
|
|
|
| 1512 |
</div>
|
| 1513 |
`).join("");
|
| 1514 |
examplesCount.textContent = `${EXAMPLES.length}`;
|
| 1515 |
|
| 1516 |
+
gallery.addEventListener("dragstart", (e) => {
|
| 1517 |
+
const t = e.target.closest(".ex-thumb");
|
| 1518 |
+
if (!t || !e.dataTransfer) return;
|
| 1519 |
+
e.dataTransfer.effectAllowed = "copy";
|
| 1520 |
+
e.dataTransfer.setData("application/x-infinisplat-example", t.dataset.path);
|
| 1521 |
+
e.dataTransfer.setData("text/uri-list", new URL(t.dataset.path, window.location.href).href);
|
| 1522 |
+
t.classList.add("dragging");
|
| 1523 |
+
});
|
| 1524 |
+
gallery.addEventListener("dragend", (e) => {
|
| 1525 |
+
e.target.closest(".ex-thumb")?.classList.remove("dragging");
|
| 1526 |
+
});
|
| 1527 |
+
|
| 1528 |
gallery.addEventListener("click", async (e) => {
|
| 1529 |
const t = e.target.closest(".ex-thumb");
|
| 1530 |
if (!t) return;
|
| 1531 |
const path = t.dataset.path;
|
| 1532 |
try {
|
| 1533 |
+
await loadExample(path);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1534 |
} catch (err) {
|
| 1535 |
toastMsg("Failed to load example", true);
|
| 1536 |
logLine("input", "err", err.message || String(err));
|