CallMeDaniel Claude Opus 4.6 (1M context) commited on
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>

Files changed (1) hide show
  1. 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
- html += '<button class="gallery-card fade-in" onclick="loadGalleryItem(\'' + escapeHtml(item.name) + '\')">';
1846
- html += '<div class="gallery-card-name">' + escapeHtml(item.name) + '</div>';
 
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></button>';
 
 
 
 
 
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');