diff --git a/public/app.mjs b/public/app.mjs index 0dfd441..1e54709 100644 --- a/public/app.mjs +++ b/public/app.mjs @@ -29,7 +29,8 @@ let all_drafts = []; let all_templates = []; let all_pdfs = []; let all_bins = []; -let bin_editor_instance = null; // { bin, setup: Grid_Setup } +let bin_tab = 'bins'; // 'bins' | 'sources' +let bin_editor_instance = null; let bin_editor_bin_id = null; // --------------------------------------------------------------------------- @@ -1947,47 +1948,111 @@ function confirm_delete(message, on_confirm) { // --------------------------------------------------------------------------- function render_bins() { - const main = document.getElementById('main'); - main.innerHTML = ''; - const sec = document.getElementById('t-section-bins').content.cloneNode(true); - main.appendChild(sec); + let sec = document.getElementById('section-bins'); + if (!sec) { + const main = document.getElementById('main'); + main.replaceChildren(document.getElementById('t-section-bins').content.cloneNode(true)); + sec = document.getElementById('section-bins'); + qs(sec, '#btn-tab-bins').addEventListener('click', () => { + bin_tab = 'bins'; + history.replaceState(null, '', '/bins'); + update_bin_tabs(sec); + }); + qs(sec, '#btn-tab-bin-sources').addEventListener('click', () => { + bin_tab = 'sources'; + history.replaceState(null, '', '/bins/sources'); + update_bin_tabs(sec); + }); + qs(sec, '#bin-source-upload-input').addEventListener('change', async (e) => { + const files = [...e.target.files]; + if (!files.length) return; + for (const file of files) { + try { + const result = await api.upload_bin(file, file.name.replace(/\.[^.]+$/, '')); + all_bins.unshift(result.bin); + const src = all_sources.find(s => s.id === result.bin.source_id); + if (!src) { + const r2 = await api.get_source_images(); + all_sources = r2.sources; + } + } catch (err) { + alert(err.message); + } + } + e.target.value = ''; + render_bin_source_list(); + }); + } + + update_bin_tabs(sec); + render_bin_list(); + render_bin_source_list(); +} + +function update_bin_tabs(sec) { + qs(sec, '#btn-tab-bins').classList.toggle('active', bin_tab === 'bins'); + qs(sec, '#btn-tab-bin-sources').classList.toggle('active', bin_tab === 'sources'); + qs(sec, '#btn-upload-bin-sources').hidden = (bin_tab !== 'sources'); + qs(sec, '#tab-bins-content').hidden = (bin_tab !== 'bins'); + qs(sec, '#tab-bin-sources-content').hidden = (bin_tab !== 'sources'); +} + +function render_bin_list() { const gallery = document.getElementById('bin-gallery'); + if (!gallery) return; + gallery.replaceChildren(); for (const bin of all_bins) { - const card = document.getElementById('t-bin-card').content.cloneNode(true); - const img = card.querySelector('.bin-card-img'); - const img_wrap = card.querySelector('.bin-card-img-wrap'); - card.querySelector('.bin-card-name').textContent = bin.name; + const card = clone('t-bin-card'); + const img = qs(card, '.bin-card-img'); + const img_wrap = qs(card, '.bin-card-img-wrap'); + qs(card, '.bin-card-name').textContent = bin.name; if (bin.image_filename) { img.src = `/img/${bin.image_filename}`; img_wrap.classList.add('has-image'); } else { img.hidden = true; } - card.querySelector('.btn-edit').addEventListener('click', () => open_bin_editor(bin)); - card.querySelector('.btn-delete').addEventListener('click', () => { + qs(card, '.btn-edit').addEventListener('click', () => open_bin_editor(bin)); + qs(card, '.btn-delete').addEventListener('click', () => { confirm_delete(`Delete bin "${bin.name}"?`, async () => { await api.delete_bin(bin.id); all_bins = all_bins.filter(b => b.id !== bin.id); - render_bins(); + render_bin_list(); }); }); gallery.appendChild(card); } +} - document.getElementById('bin-upload-input').addEventListener('change', async (e) => { - const file = e.target.files[0]; - if (!file) return; - try { - const name = file.name.replace(/\.[^.]+$/, ''); - const result = await api.upload_bin(file, name); - all_bins.unshift(result.bin); - render_bins(); - open_bin_editor(result.bin); - } catch (err) { - alert(err.message); - } - }); +function render_bin_source_list() { + const list_el = document.getElementById('bin-source-image-list'); + if (!list_el) return; + const bin_sources = all_sources.filter(s => s.uses?.includes('bin')); + if (bin_sources.length === 0) { + const el = clone('t-empty-block'); + el.textContent = 'No bin source images yet. Upload photos of your bins.'; + list_el.replaceChildren(el); + return; + } + list_el.replaceChildren(...bin_sources.map(src => { + const card = build_source_card(src, false); + const create_btn = card.querySelector('.source-card-create-bin'); + create_btn.hidden = false; + create_btn.addEventListener('click', async () => { + try { + const result = await api.create_bin_from_source(src.id); + all_bins.unshift(result.bin); + render_bin_list(); + open_bin_editor(result.bin); + bin_tab = 'bins'; + update_bin_tabs(document.getElementById('section-bins')); + } catch (err) { + alert(err.message); + } + }); + return card; + })); } function open_bin_editor(bin) { @@ -2021,6 +2086,7 @@ function parse_url() { section = 'components'; grid_view_state = 'list'; grid_tab = 'grids'; + bin_tab = 'bins'; current_grid_id = null; current_panel_idx = null; grid_draft = null; @@ -2045,6 +2111,7 @@ function parse_url() { section = 'templates'; } else if (p0 === 'bins') { section = 'bins'; + bin_tab = p1 === 'sources' ? 'sources' : 'bins'; } else if (p0 === 'grids') { section = 'grids'; if (p1 === 'sources') { diff --git a/public/lib/api.mjs b/public/lib/api.mjs index 6a23090..fa38b67 100644 --- a/public/lib/api.mjs +++ b/public/lib/api.mjs @@ -62,7 +62,8 @@ export async function upload_pdf(file, display_name, filename) { } // Bins -export const get_bins = () => req('GET', '/api/bins'); +export const get_bins = () => req('GET', '/api/bins'); +export const create_bin_from_source = (source_id, name) => req('POST', '/api/bins/from-source', { source_id, name }); export const get_bin = (id) => req('GET', `/api/bins/${id}`); export const rename_bin = (id, name) => req('PUT', `/api/bins/${id}`, { name }); export const update_bin_corners = (id, corners) => req('PUT', `/api/bins/${id}/corners`, { corners }); diff --git a/public/templates.html b/public/templates.html index fa96d62..dc6facc 100644 --- a/public/templates.html +++ b/public/templates.html @@ -280,6 +280,7 @@
+ @@ -592,12 +593,21 @@