Make bin editor open to image view; corners editing on demand

- Default view: processed image (full size in dialog), or placeholder if
  not yet processed. Name and type selector always visible at top.
- "Adjust corners…" button reveals the canvas editor (lazy-loaded on
  first click, so there's no canvas allocation cost on open).
- "← Back to image" returns to the image view.
- Corners are only re-processed on Save if the canvas was opened.
- Tabs reduced to Fields | Contents (Corners is no longer a tab).
- Added preview image CSS; btn-link style for the back button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 13:06:30 +00:00
parent 46ce10289e
commit e2d0079ba0
3 changed files with 120 additions and 41 deletions

View File

@@ -2359,6 +2359,8 @@ function open_bin_editor(bin) {
if (!dlg) return; if (!dlg) return;
bin_editor_bin_id = bin.id; bin_editor_bin_id = bin.id;
bin_editor_instance = null; // canvas not loaded until needed
document.getElementById('bin-editor-name').value = bin.name; document.getElementById('bin-editor-name').value = bin.name;
// Populate type selector // Populate type selector
@@ -2385,28 +2387,68 @@ function open_bin_editor(bin) {
type_sel.addEventListener('change', sync_dims_row); type_sel.addEventListener('change', sync_dims_row);
sync_dims_row(); sync_dims_row();
// Tabs // Image preview (default view)
let active_tab = 'corners'; const view_image = document.getElementById('bin-editor-view-image');
const view_corners = document.getElementById('bin-editor-view-corners');
const preview_img = document.getElementById('bin-editor-preview');
const no_image_el = document.getElementById('bin-editor-no-image');
function show_image_view() {
view_image.hidden = false;
view_corners.hidden = true;
}
function show_corners_view() {
view_image.hidden = true;
view_corners.hidden = false;
// Lazy-load canvas the first time
if (!bin_editor_instance) {
const canvas = document.getElementById('bin-editor-canvas');
bin_editor_instance = new Grid_Setup(canvas);
bin_editor_instance.set_rows(1);
bin_editor_instance.set_cols(1);
bin_editor_instance.load_image(`/img/${bin.source_id}`).then(() => {
if (bin.corners) {
bin_editor_instance.set_corners(bin.corners);
}
});
}
}
if (bin.image_filename) {
preview_img.src = `/img/${bin.image_filename}`;
preview_img.hidden = false;
no_image_el.hidden = true;
} else {
preview_img.hidden = true;
no_image_el.hidden = false;
}
document.getElementById('bin-editor-go-corners').onclick = () => {
dlg.showModal(); // already open; ensures layout is stable before canvas sizes
show_corners_view();
};
document.getElementById('bin-editor-go-back').onclick = show_image_view;
show_image_view();
// Tabs: Fields | Contents
const tab_panels = { const tab_panels = {
corners: document.getElementById('bin-editor-tab-corners'),
fields: document.getElementById('bin-editor-tab-fields'), fields: document.getElementById('bin-editor-tab-fields'),
contents: document.getElementById('bin-editor-tab-contents'), contents: document.getElementById('bin-editor-tab-contents'),
}; };
qs(dlg, '#bin-editor-tabs').onclick = (e) => { qs(dlg, '#bin-editor-tabs').onclick = (e) => {
const tab = e.target.dataset.tab; const tab = e.target.dataset.tab;
if (!tab) return; if (!tab) return;
active_tab = tab;
qs(dlg, '#bin-editor-tabs').querySelectorAll('.tab-btn').forEach(btn => { qs(dlg, '#bin-editor-tabs').querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.tab === tab); btn.classList.toggle('active', btn.dataset.tab === tab);
}); });
for (const [name, el] of Object.entries(tab_panels)) { for (const [name, el] of Object.entries(tab_panels)) {
el.hidden = name !== tab; el.hidden = name !== tab;
} }
const save_btn = document.getElementById('bin-editor-save');
save_btn.textContent = active_tab === 'corners' ? 'Save & process' : 'Save';
}; };
// Fields tab // Fields
bin_editor_get_fields = build_field_editor( bin_editor_get_fields = build_field_editor(
document.getElementById('bin-field-rows'), document.getElementById('bin-field-rows'),
document.getElementById('bin-add-field-select'), document.getElementById('bin-add-field-select'),
@@ -2414,22 +2456,11 @@ function open_bin_editor(bin) {
bin.fields ?? {} bin.fields ?? {}
).get_fields; ).get_fields;
// Contents tab // Contents
render_bin_contents(bin.id, document.getElementById('bin-contents-list')); render_bin_contents(bin.id, document.getElementById('bin-contents-list'));
document.getElementById('bin-add-content').onclick = () => open_bin_content_dialog(bin.id); document.getElementById('bin-add-content').onclick = () => open_bin_content_dialog(bin.id);
// Show dialog first so the canvas has correct layout dimensions
dlg.showModal(); dlg.showModal();
const canvas = document.getElementById('bin-editor-canvas');
bin_editor_instance = new Grid_Setup(canvas);
bin_editor_instance.set_rows(1);
bin_editor_instance.set_cols(1);
bin_editor_instance.load_image(`/img/${bin.source_id}`).then(() => {
if (bin.corners) {
bin_editor_instance.set_corners(bin.corners);
}
});
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -2678,10 +2709,9 @@ async function init() {
const type_id = document.getElementById('bin-editor-type').value || null; const type_id = document.getElementById('bin-editor-type').value || null;
const fields = bin_editor_get_fields?.() ?? {}; const fields = bin_editor_get_fields?.() ?? {};
try { try {
// Always save name, type, and fields
const corners = bin_editor_instance?.get_corners(); const corners = bin_editor_instance?.get_corners();
if (corners) { if (corners) {
// Corners tab active (or image loaded) also re-process // Canvas was opened — re-process corners too
const phys_w = parseFloat(document.getElementById('bin-editor-width').value) || null; const phys_w = parseFloat(document.getElementById('bin-editor-width').value) || null;
const phys_h = parseFloat(document.getElementById('bin-editor-height').value) || null; const phys_h = parseFloat(document.getElementById('bin-editor-height').value) || null;
await api.update_bin(id, { name, type_id, fields }); await api.update_bin(id, { name, type_id, fields });

View File

@@ -1988,6 +1988,46 @@ nav {
margin-left: 0.25rem; margin-left: 0.25rem;
} }
.bin-editor-preview-wrap {
width: 100%;
background: #0e0e0e;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
margin-bottom: 0.5rem;
overflow: hidden;
}
.bin-editor-preview-img {
max-width: 100%;
max-height: 55vh;
display: block;
object-fit: contain;
}
.bin-editor-no-image {
color: var(--text-faint);
font-size: 0.85rem;
padding: 2rem;
text-align: center;
}
.btn-link {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 0.8rem;
padding: 0.25rem 0;
text-decoration: underline;
}
.btn-link:hover {
color: var(--text);
}
.bin-editor-canvas { .bin-editor-canvas {
display: block; display: block;
border-radius: 4px; border-radius: 4px;

View File

@@ -671,24 +671,29 @@
<template id="t-dialog-bin-editor"> <template id="t-dialog-bin-editor">
<dialog id="dialog-bin-editor" class="app-dialog app-dialog-wide"> <dialog id="dialog-bin-editor" class="app-dialog app-dialog-wide">
<h2 class="dialog-title">Edit bin</h2> <h2 class="dialog-title">Edit bin</h2>
<div class="tab-bar" id="bin-editor-tabs">
<button class="tab-btn active" data-tab="corners">Corners</button> <div class="form-row">
<button class="tab-btn" data-tab="fields">Fields</button> <label>Name</label>
<button class="tab-btn" data-tab="contents">Contents</button> <input type="text" id="bin-editor-name" autocomplete="off">
</div>
<div class="form-row">
<label>Type</label>
<select id="bin-editor-type">
<option value="">— Custom —</option>
</select>
</div> </div>
<!-- Corners tab --> <!-- Default view: processed image -->
<div id="bin-editor-tab-corners"> <div id="bin-editor-view-image">
<div class="form-row"> <div class="bin-editor-preview-wrap">
<label>Name</label> <img id="bin-editor-preview" class="bin-editor-preview-img" alt="" hidden>
<input type="text" id="bin-editor-name" autocomplete="off"> <div id="bin-editor-no-image" class="bin-editor-no-image">Not yet processed — click "Adjust corners" to set up</div>
</div>
<div class="form-row">
<label>Type</label>
<select id="bin-editor-type">
<option value="">— Custom —</option>
</select>
</div> </div>
<button type="button" class="btn btn-secondary btn-sm" id="bin-editor-go-corners">Adjust corners…</button>
</div>
<!-- Corners canvas (revealed on demand) -->
<div id="bin-editor-view-corners" hidden>
<div class="form-row" id="bin-editor-dims-row"> <div class="form-row" id="bin-editor-dims-row">
<label>Dimensions (mm)</label> <label>Dimensions (mm)</label>
<div class="bin-editor-dims"> <div class="bin-editor-dims">
@@ -699,11 +704,16 @@
</div> </div>
</div> </div>
<canvas id="bin-editor-canvas" class="bin-editor-canvas"></canvas> <canvas id="bin-editor-canvas" class="bin-editor-canvas"></canvas>
<button type="button" class="btn btn-link btn-sm" id="bin-editor-go-back">← Back to image</button>
</div> </div>
<!-- Fields tab --> <!-- Tabs: Fields | Contents -->
<div id="bin-editor-tab-fields" hidden> <div class="tab-bar" id="bin-editor-tabs">
<div class="form-section-label">Field values</div> <button class="tab-btn active" data-tab="fields">Fields</button>
<button class="tab-btn" data-tab="contents">Contents</button>
</div>
<div id="bin-editor-tab-fields">
<div id="bin-field-rows"></div> <div id="bin-field-rows"></div>
<div class="form-row add-field-row"> <div class="form-row add-field-row">
<div class="input-with-action"> <div class="input-with-action">
@@ -715,7 +725,6 @@
</div> </div>
</div> </div>
<!-- Contents tab -->
<div id="bin-editor-tab-contents" hidden> <div id="bin-editor-tab-contents" hidden>
<div id="bin-contents-list"></div> <div id="bin-contents-list"></div>
<div class="form-row" style="margin-top:0.5rem"> <div class="form-row" style="margin-top:0.5rem">
@@ -725,7 +734,7 @@
<div class="dialog-actions"> <div class="dialog-actions">
<button type="button" class="btn btn-secondary" id="bin-editor-cancel">Cancel</button> <button type="button" class="btn btn-secondary" id="bin-editor-cancel">Cancel</button>
<button type="button" class="btn btn-primary" id="bin-editor-save">Save &amp; process</button> <button type="button" class="btn btn-primary" id="bin-editor-save">Save</button>
</div> </div>
</dialog> </dialog>
</template> </template>