Spaces:
Sleeping
Sleeping
Commit ·
1224d2f
1
Parent(s): d5d5b46
feat: add STEP/STL download links to gallery cards, persist models from server
Browse files- Gallery cards now show STEP and STL download buttons
- Loading a gallery item updates the viewer download bar
- Gallery loads existing models from server on page load
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- web/index.html +56 -3
web/index.html
CHANGED
|
@@ -1054,6 +1054,31 @@
|
|
| 1054 |
gap: 8px;
|
| 1055 |
}
|
| 1056 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1057 |
/* ---- ANIMATIONS ---- */
|
| 1058 |
|
| 1059 |
@keyframes fade-in-up {
|
|
@@ -1842,15 +1867,21 @@ function renderGallery() {
|
|
| 1842 |
|
| 1843 |
let html = '';
|
| 1844 |
for (const item of galleryItems) {
|
| 1845 |
-
|
| 1846 |
-
html += '<div class="gallery-card-
|
|
|
|
| 1847 |
html += '<div class="gallery-card-meta">';
|
| 1848 |
if (item.faces) html += '<span>' + item.faces + ' faces</span>';
|
| 1849 |
if (item.machinable !== undefined) {
|
| 1850 |
html += '<span style="color:' + (item.machinable ? 'var(--success)' : 'var(--error)') + '">'
|
| 1851 |
+ (item.machinable ? '\u2713 CNC' : '\u2717 CNC') + '</span>';
|
| 1852 |
}
|
| 1853 |
-
html += '</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1854 |
}
|
| 1855 |
|
| 1856 |
grid.innerHTML = html;
|
|
@@ -1861,6 +1892,8 @@ async function loadGalleryItem(name) {
|
|
| 1861 |
setViewerLoading(true, 'LOADING MODEL...');
|
| 1862 |
try {
|
| 1863 |
await loadSTL('/api/models/' + name + '.stl');
|
|
|
|
|
|
|
| 1864 |
} catch (e) {
|
| 1865 |
console.warn('Failed to load:', e);
|
| 1866 |
}
|
|
@@ -1970,6 +2003,25 @@ document.addEventListener('keydown', (e) => {
|
|
| 1970 |
}
|
| 1971 |
});
|
| 1972 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1973 |
// ── INIT ──────────────────────────────────────────────
|
| 1974 |
|
| 1975 |
initViewer();
|
|
@@ -1977,6 +2029,7 @@ checkServer();
|
|
| 1977 |
setInterval(checkServer, 15000);
|
| 1978 |
|
| 1979 |
loadState();
|
|
|
|
| 1980 |
// Re-render restored messages
|
| 1981 |
if (chatHistory.length > 0) {
|
| 1982 |
const examples = document.getElementById('quick-examples');
|
|
|
|
| 1054 |
gap: 8px;
|
| 1055 |
}
|
| 1056 |
|
| 1057 |
+
.gallery-card-downloads {
|
| 1058 |
+
display: flex;
|
| 1059 |
+
gap: 6px;
|
| 1060 |
+
margin-top: 4px;
|
| 1061 |
+
}
|
| 1062 |
+
|
| 1063 |
+
.gallery-dl {
|
| 1064 |
+
all: unset;
|
| 1065 |
+
font-family: var(--font-mono);
|
| 1066 |
+
font-size: 9px;
|
| 1067 |
+
font-weight: 600;
|
| 1068 |
+
padding: 3px 8px;
|
| 1069 |
+
border: 1px solid var(--border);
|
| 1070 |
+
border-radius: 3px;
|
| 1071 |
+
color: var(--accent);
|
| 1072 |
+
cursor: pointer;
|
| 1073 |
+
text-decoration: none;
|
| 1074 |
+
transition: all 0.15s;
|
| 1075 |
+
}
|
| 1076 |
+
|
| 1077 |
+
.gallery-dl:hover {
|
| 1078 |
+
background: var(--accent-glow);
|
| 1079 |
+
border-color: var(--accent-dim);
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
/* ---- ANIMATIONS ---- */
|
| 1083 |
|
| 1084 |
@keyframes fade-in-up {
|
|
|
|
| 1867 |
|
| 1868 |
let html = '';
|
| 1869 |
for (const item of galleryItems) {
|
| 1870 |
+
const name = escapeHtml(item.name);
|
| 1871 |
+
html += '<div class="gallery-card fade-in">';
|
| 1872 |
+
html += '<div class="gallery-card-name" onclick="loadGalleryItem(\'' + name + '\')" style="cursor:pointer">' + name + '</div>';
|
| 1873 |
html += '<div class="gallery-card-meta">';
|
| 1874 |
if (item.faces) html += '<span>' + item.faces + ' faces</span>';
|
| 1875 |
if (item.machinable !== undefined) {
|
| 1876 |
html += '<span style="color:' + (item.machinable ? 'var(--success)' : 'var(--error)') + '">'
|
| 1877 |
+ (item.machinable ? '\u2713 CNC' : '\u2717 CNC') + '</span>';
|
| 1878 |
}
|
| 1879 |
+
html += '</div>';
|
| 1880 |
+
html += '<div class="gallery-card-downloads">';
|
| 1881 |
+
html += '<a class="gallery-dl" href="/api/models/' + name + '.step" download>STEP</a>';
|
| 1882 |
+
html += '<a class="gallery-dl" href="/api/models/' + name + '.stl" download>STL</a>';
|
| 1883 |
+
html += '</div>';
|
| 1884 |
+
html += '</div>';
|
| 1885 |
}
|
| 1886 |
|
| 1887 |
grid.innerHTML = html;
|
|
|
|
| 1892 |
setViewerLoading(true, 'LOADING MODEL...');
|
| 1893 |
try {
|
| 1894 |
await loadSTL('/api/models/' + name + '.stl');
|
| 1895 |
+
currentPartName = name;
|
| 1896 |
+
updateDownloads(name);
|
| 1897 |
} catch (e) {
|
| 1898 |
console.warn('Failed to load:', e);
|
| 1899 |
}
|
|
|
|
| 2003 |
}
|
| 2004 |
});
|
| 2005 |
|
| 2006 |
+
// ── LOAD SERVER MODELS INTO GALLERY ──────────────────
|
| 2007 |
+
|
| 2008 |
+
async function loadServerModels() {
|
| 2009 |
+
try {
|
| 2010 |
+
const resp = await fetch('/api/models');
|
| 2011 |
+
const data = await resp.json();
|
| 2012 |
+
if (data.models && data.models.length > 0) {
|
| 2013 |
+
const existingNames = new Set(galleryItems.map(i => i.name));
|
| 2014 |
+
for (const model of data.models) {
|
| 2015 |
+
if (!existingNames.has(model.name)) {
|
| 2016 |
+
galleryItems.push({ name: model.name });
|
| 2017 |
+
}
|
| 2018 |
+
}
|
| 2019 |
+
}
|
| 2020 |
+
} catch (e) {
|
| 2021 |
+
console.warn('Failed to load server models:', e);
|
| 2022 |
+
}
|
| 2023 |
+
}
|
| 2024 |
+
|
| 2025 |
// ── INIT ──────────────────────────────────────────────
|
| 2026 |
|
| 2027 |
initViewer();
|
|
|
|
| 2029 |
setInterval(checkServer, 15000);
|
| 2030 |
|
| 2031 |
loadState();
|
| 2032 |
+
loadServerModels();
|
| 2033 |
// Re-render restored messages
|
| 2034 |
if (chatHistory.length > 0) {
|
| 2035 |
const examples = document.getElementById('quick-examples');
|