Bin types store a name, physical W×H in mm, and optional description. When editing a bin, a type can be selected from a dropdown; this pre-fills and locks the dimension inputs. Custom dimensions remain available when no type is selected. - lib/storage.mjs: bin type CRUD with bt: prefix - server.mjs: /api/bin-types CRUD routes; type_id accepted on bin create/update routes; DELETE protected if any bin references the type; type dims copied onto bin when type_id is set - public/lib/api.mjs: bin type wrappers; rename_bin → update_bin (accepts any fields) - public/templates.html: Types tab in bins section; t-bin-type-row; t-dialog-bin-type; type selector in bin editor dialog - public/app.mjs: all_bin_types state loaded at startup; render_bin_types_list(); open_bin_type_dialog(); type selector in open_bin_editor(); /bins/types routing - public/style.css: bin types list styles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
740 lines
25 KiB
HTML
740 lines
25 KiB
HTML
<!-- ===== COMPONENTS SECTION ===== -->
|
||
<template id="t-section-components">
|
||
<section class="section" id="section-components">
|
||
<div class="split-layout">
|
||
<div class="list-pane" id="list-pane">
|
||
<input type="search" id="component-search" class="search-input" placeholder="Search…">
|
||
<input type="text" id="quick-add" class="quick-add-input" placeholder="New component… ↵" autocomplete="off" spellcheck="false">
|
||
<div id="component-list" class="component-list"></div>
|
||
</div>
|
||
<div class="split-resizer" id="split-resizer"></div>
|
||
<div class="detail-pane" id="detail-pane"></div>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-component-row">
|
||
<div class="component-row" tabindex="0">
|
||
<div class="component-name"></div>
|
||
<div class="component-tags"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-field-tag">
|
||
<span class="field-tag"><span class="tag-name"></span><span class="tag-value"></span></span>
|
||
</template>
|
||
|
||
<!-- ===== DETAIL PANEL ===== -->
|
||
<template id="t-detail-placeholder">
|
||
<div class="detail-placeholder">Select a component to view details</div>
|
||
</template>
|
||
|
||
<template id="t-detail-content">
|
||
<div class="detail-content">
|
||
<div class="detail-header">
|
||
<div>
|
||
<h2 class="detail-name"></h2>
|
||
<p class="detail-description"></p>
|
||
</div>
|
||
<div class="detail-header-actions">
|
||
<button class="btn btn-secondary detail-edit-btn">Edit</button>
|
||
<button class="btn btn-secondary detail-duplicate-btn">Duplicate</button>
|
||
<button class="btn btn-danger detail-delete-btn">Delete</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-block">
|
||
<div class="detail-block-label">Fields</div>
|
||
<div class="detail-fields-list"></div>
|
||
</div>
|
||
|
||
<div class="detail-block">
|
||
<div class="detail-block-label">Images</div>
|
||
<div class="detail-images-row">
|
||
<div class="image-grid comp-image-grid"></div>
|
||
<label class="btn-add-image">
|
||
<input type="file" accept="image/*" multiple hidden class="comp-img-input">
|
||
+ Add image
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-block">
|
||
<div class="detail-block-label">
|
||
Files
|
||
<button class="btn btn-secondary detail-link-file-btn">+ Link file</button>
|
||
</div>
|
||
<div class="detail-files-list"></div>
|
||
</div>
|
||
|
||
<div class="detail-block">
|
||
<div class="detail-block-label">
|
||
Inventory
|
||
<button class="btn btn-secondary detail-add-inv-btn">+ Add entry</button>
|
||
</div>
|
||
<div class="detail-inventory-list"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-detail-field-row">
|
||
<div class="detail-field-row">
|
||
<span class="detail-field-name"></span>
|
||
<span class="detail-field-value"></span>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-detail-inv-entry">
|
||
<div class="detail-inv-entry">
|
||
<div class="detail-inv-header">
|
||
<span class="detail-inv-type"></span>
|
||
<span class="detail-inv-ref"></span>
|
||
<span class="detail-inv-qty"></span>
|
||
<span class="detail-inv-notes"></span>
|
||
<span class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</span>
|
||
</div>
|
||
<div class="detail-inv-images">
|
||
<div class="image-grid inv-image-grid"></div>
|
||
<label class="btn-add-image btn-add-image-sm">
|
||
<input type="file" accept="image/*" multiple hidden class="inv-img-input">
|
||
+ Image
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-image-thumb">
|
||
<div class="image-thumb">
|
||
<a class="thumb-link" target="_blank" rel="noopener">
|
||
<img class="thumb-img" alt="">
|
||
</a>
|
||
<button type="button" class="thumb-delete btn-icon btn-danger" title="Remove">✕</button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== INVENTORY SECTION ===== -->
|
||
<template id="t-section-inventory">
|
||
<section class="section" id="section-inventory">
|
||
<div class="section-toolbar">
|
||
<input type="search" id="inventory-search" class="search-input" placeholder="Search by component or location…">
|
||
<select id="inventory-type-filter" class="filter-select">
|
||
<option value="">All types</option>
|
||
<option value="physical">Physical</option>
|
||
<option value="bom">BOM / Drawing</option>
|
||
<option value="digital">Digital / Note</option>
|
||
<option value="grid">Grid cell</option>
|
||
</select>
|
||
<button class="btn btn-primary" id="btn-add-inventory">+ Add entry</button>
|
||
</div>
|
||
<table class="data-table">
|
||
<thead><tr>
|
||
<th>Component</th>
|
||
<th class="col-type">Type</th>
|
||
<th>Location / Reference</th>
|
||
<th class="col-qty">Qty</th>
|
||
<th>Notes</th>
|
||
<th class="col-actions"></th>
|
||
</tr></thead>
|
||
<tbody id="inventory-list"></tbody>
|
||
</table>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-inventory-row">
|
||
<tr class="data-row">
|
||
<td class="inv-component-name"></td>
|
||
<td class="inv-type-badge"></td>
|
||
<td class="inv-location-ref"></td>
|
||
<td class="inv-quantity"></td>
|
||
<td class="inv-notes"></td>
|
||
<td class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</td>
|
||
</tr>
|
||
</template>
|
||
|
||
<!-- ===== TEMPLATES SECTION ===== -->
|
||
<template id="t-section-templates">
|
||
<section class="section" id="section-templates">
|
||
<div class="section-toolbar">
|
||
<span class="section-note">Formatters that compute display names from component fields</span>
|
||
<button class="btn btn-primary" id="btn-add-template">+ Add template</button>
|
||
</div>
|
||
<div id="template-list" class="template-list"></div>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-template-card">
|
||
<div class="template-card">
|
||
<div class="template-card-header">
|
||
<span class="template-card-name"></span>
|
||
<span class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</span>
|
||
</div>
|
||
<pre class="template-card-formatter"></pre>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-dialog-template">
|
||
<dialog id="dialog-template" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title"></h2>
|
||
<form method="dialog" id="form-template">
|
||
<div class="form-row">
|
||
<label for="tmpl-name">Name</label>
|
||
<input type="text" id="tmpl-name" required autocomplete="off" placeholder="e.g. Resistor">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="tmpl-formatter">Formatter <span class="label-hint">(JS arrow function, return null to skip)</span></label>
|
||
<textarea id="tmpl-formatter" rows="8" class="code-input" placeholder="(c) => { const r = c.fields?.resistance; if (!r) return null; return `Resistor ${r}`; }"></textarea>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="tmpl-test-data">Test data <span class="label-hint">(optional — return a fields object to preview against)</span></label>
|
||
<textarea id="tmpl-test-data" rows="3" class="code-input" placeholder="return { resistance: '10k', mounting_tech: 'PTH' }"></textarea>
|
||
</div>
|
||
<div class="tmpl-preview-row">
|
||
<span class="label-hint">Preview:</span>
|
||
<span id="tmpl-preview" class="tmpl-preview-value">—</span>
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="tmpl-cancel">Cancel</button>
|
||
<button type="submit" class="btn btn-primary" id="tmpl-save">Save</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== FIELDS SECTION ===== -->
|
||
<template id="t-section-fields">
|
||
<section class="section" id="section-fields">
|
||
<div class="section-toolbar">
|
||
<span class="section-note">Master field index — fields available for all components</span>
|
||
<button class="btn btn-primary" id="btn-add-field">+ Add field</button>
|
||
</div>
|
||
<table class="data-table">
|
||
<thead><tr>
|
||
<th>Field name</th>
|
||
<th class="col-unit">Unit</th>
|
||
<th>Description</th>
|
||
<th class="col-actions"></th>
|
||
</tr></thead>
|
||
<tbody id="field-list"></tbody>
|
||
</table>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-field-row">
|
||
<tr class="data-row">
|
||
<td class="fdef-name"></td>
|
||
<td class="fdef-unit"></td>
|
||
<td class="fdef-description"></td>
|
||
<td class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</td>
|
||
</tr>
|
||
</template>
|
||
|
||
<!-- ===== EMPTY STATES ===== -->
|
||
<template id="t-empty-row">
|
||
<tr><td class="empty-state"></td></tr>
|
||
</template>
|
||
<template id="t-empty-block">
|
||
<div class="empty-state"></div>
|
||
</template>
|
||
|
||
<!-- ===== IMAGES SECTION ===== -->
|
||
<template id="t-section-images">
|
||
<section class="section" id="section-images">
|
||
<div class="img-admin-list" id="img-admin-list"></div>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-img-admin-row">
|
||
<div class="img-admin-row">
|
||
<a class="img-admin-thumb-link" target="_blank" rel="noopener">
|
||
<img class="img-admin-thumb" alt="">
|
||
</a>
|
||
<div class="img-admin-info">
|
||
<div class="img-admin-name"></div>
|
||
<div class="img-admin-meta"></div>
|
||
<div class="img-admin-uses"></div>
|
||
</div>
|
||
<button type="button" class="btn-icon btn-danger img-admin-delete" title="Delete">✕</button>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== GRIDS SECTION ===== -->
|
||
<template id="t-section-grids">
|
||
<section class="section" id="section-grids">
|
||
<div class="section-toolbar">
|
||
<div class="tab-bar">
|
||
<button class="tab-btn" id="btn-tab-grids">Grids</button>
|
||
<button class="tab-btn" id="btn-tab-sources">Source images</button>
|
||
</div>
|
||
<button class="btn btn-primary" id="btn-new-grid">+ New grid</button>
|
||
<label class="btn btn-secondary" id="btn-upload-sources">
|
||
+ Upload
|
||
<input type="file" accept="image/*" multiple hidden id="source-upload-input">
|
||
</label>
|
||
</div>
|
||
<div id="tab-grids-content">
|
||
<div id="grid-list" class="grid-card-list"></div>
|
||
</div>
|
||
<div id="tab-sources-content" hidden>
|
||
<div id="source-image-list" class="source-gallery"></div>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-source-card">
|
||
<div class="source-card">
|
||
<a class="source-card-link" target="_blank" rel="noopener">
|
||
<img class="source-card-img" alt="">
|
||
</a>
|
||
<div class="source-card-footer">
|
||
<div class="source-card-meta"></div>
|
||
<div class="source-card-uses"></div>
|
||
</div>
|
||
<button type="button" class="btn btn-secondary btn-sm source-card-create-bin" hidden>+ Bin</button>
|
||
<button type="button" class="btn-icon btn-danger source-card-delete" title="Delete">✕</button>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-draft-card">
|
||
<div class="draft-card">
|
||
<div class="draft-badge">Draft</div>
|
||
<div class="draft-card-info">
|
||
<div class="draft-card-name"></div>
|
||
<div class="draft-card-meta"></div>
|
||
</div>
|
||
<div class="row-actions">
|
||
<button class="btn-icon btn-danger btn-delete" title="Discard">✕</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-grid-card">
|
||
<div class="grid-card">
|
||
<div class="grid-card-preview"></div>
|
||
<div class="grid-card-info">
|
||
<div class="grid-card-name"></div>
|
||
<div class="grid-card-meta"></div>
|
||
</div>
|
||
<div class="row-actions">
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== PANEL MANAGER VIEW ===== -->
|
||
<template id="t-panel-manager">
|
||
<div class="panel-manager" id="panel-manager">
|
||
<div class="panel-manager-header">
|
||
<div>
|
||
<h2 class="pm-name"></h2>
|
||
<div class="pm-meta"></div>
|
||
</div>
|
||
<div class="pm-actions">
|
||
<button class="btn btn-secondary" id="pm-cancel">Cancel</button>
|
||
<button class="btn btn-primary" id="pm-process" disabled>Process all panels</button>
|
||
</div>
|
||
</div>
|
||
<div class="panel-slot-grid" id="panel-slot-grid"></div>
|
||
<div class="setup-progress" id="pm-progress" hidden>Processing panels…</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-panel-slot">
|
||
<div class="panel-slot" tabindex="0">
|
||
<div class="panel-slot-preview">
|
||
<img class="panel-slot-thumb" alt="" hidden>
|
||
<div class="panel-slot-empty-icon"></div>
|
||
</div>
|
||
<div class="panel-slot-label"></div>
|
||
<div class="panel-slot-range"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== GRID SETUP VIEW ===== -->
|
||
<template id="t-grid-setup">
|
||
<div class="grid-setup" id="grid-setup">
|
||
<div class="grid-setup-left">
|
||
<canvas id="grid-canvas" class="grid-canvas"></canvas>
|
||
<div class="grid-setup-hint">Scroll to zoom · drag to pan · drag handles to align</div>
|
||
</div>
|
||
<div class="grid-setup-right">
|
||
<div class="gs-panel-info" id="gs-panel-info"></div>
|
||
<div class="setup-cell-size" id="gs-cell-size"></div>
|
||
<div class="setup-actions">
|
||
<button class="btn btn-secondary" id="gs-cancel">← Back</button>
|
||
<button class="btn btn-primary" id="gs-confirm">Confirm panel</button>
|
||
</div>
|
||
<div class="setup-progress" id="gs-progress" hidden></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== GRID VIEWER ===== -->
|
||
<template id="t-grid-viewer">
|
||
<div class="grid-viewer" id="grid-viewer">
|
||
<div class="grid-viewer-header">
|
||
<div>
|
||
<h2 class="viewer-name"></h2>
|
||
<div class="viewer-meta"></div>
|
||
</div>
|
||
<div class="grid-viewer-actions">
|
||
<button class="btn btn-secondary" id="gv-back">← Back</button>
|
||
<button class="btn btn-secondary" id="gv-edit-panels">Edit panels</button>
|
||
<button class="btn btn-danger" id="gv-delete">Delete</button>
|
||
</div>
|
||
</div>
|
||
<div class="grid-cells" id="grid-cells"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-grid-cell">
|
||
<div class="grid-cell">
|
||
<div class="grid-cell-img-wrap">
|
||
<img class="grid-cell-img" alt="">
|
||
</div>
|
||
<div class="grid-cell-label"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: COMPONENT ===== -->
|
||
<template id="t-dialog-component">
|
||
<dialog id="dialog-component" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title"></h2>
|
||
<form method="dialog" id="form-component">
|
||
<div class="form-row">
|
||
<label for="c-name">Name</label>
|
||
<input type="text" id="c-name" required autocomplete="off">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="c-description">Description</label>
|
||
<input type="text" id="c-description" autocomplete="off">
|
||
</div>
|
||
<div class="form-section-label">Field values</div>
|
||
<div id="c-field-rows"></div>
|
||
<div class="form-row add-field-row">
|
||
<div class="input-with-action">
|
||
<select id="c-add-field-select" class="filter-select">
|
||
<option value="">— add a field —</option>
|
||
</select>
|
||
<button type="button" class="btn btn-secondary btn-sm" id="c-new-field">New…</button>
|
||
</div>
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="c-cancel">Cancel</button>
|
||
<button type="submit" class="btn btn-primary" id="c-save">Save</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: INVENTORY ENTRY ===== -->
|
||
<template id="t-dialog-inventory">
|
||
<dialog id="dialog-inventory" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title"></h2>
|
||
<form method="dialog" id="form-inventory">
|
||
<div class="form-row">
|
||
<label for="i-component">Component</label>
|
||
<div class="input-with-action">
|
||
<select id="i-component" required class="filter-select">
|
||
<option value="">— select component —</option>
|
||
</select>
|
||
<button type="button" class="btn btn-secondary btn-sm" id="i-new-component">New…</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="i-type">Location type</label>
|
||
<select id="i-type" required class="filter-select wide">
|
||
<option value="physical">Physical location</option>
|
||
<option value="bom">BOM / Drawing</option>
|
||
<option value="digital">Digital / Note</option>
|
||
<option value="grid">Grid cell</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="i-ref" id="i-ref-label">Location reference</label>
|
||
<input type="text" id="i-ref" autocomplete="off">
|
||
</div>
|
||
<div class="form-row" id="i-grid-row" hidden>
|
||
<label>Grid cell</label>
|
||
<div class="i-grid-cell-picker">
|
||
<select id="i-grid-select" class="filter-select"></select>
|
||
<div id="i-grid-visual" class="i-grid-visual"></div>
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="i-qty">Quantity</label>
|
||
<input type="text" id="i-qty" autocomplete="off" placeholder="e.g. 10, ~50, see BOM">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="i-notes">Notes <span class="label-hint">(for this location entry)</span></label>
|
||
<input type="text" id="i-notes" autocomplete="off">
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="i-cancel">Cancel</button>
|
||
<button type="submit" class="btn btn-primary" id="i-save">Save</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: FIELD DEFINITION ===== -->
|
||
<template id="t-dialog-field">
|
||
<dialog id="dialog-field" class="app-dialog">
|
||
<h2 class="dialog-title"></h2>
|
||
<form method="dialog" id="form-field">
|
||
<div class="form-row">
|
||
<label for="f-name">Field name</label>
|
||
<input type="text" id="f-name" required autocomplete="off" placeholder="e.g. capacitance, mouser_order_no">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="f-unit">Unit <span class="label-hint">(optional, informational)</span></label>
|
||
<input type="text" id="f-unit" autocomplete="off" placeholder="e.g. F, V, Ω, mm">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="f-description">Description</label>
|
||
<input type="text" id="f-description" autocomplete="off">
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="f-cancel">Cancel</button>
|
||
<button type="submit" class="btn btn-primary" id="f-save">Save</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: NEW GRID ===== -->
|
||
<template id="t-dialog-new-grid">
|
||
<dialog id="dialog-new-grid" class="app-dialog">
|
||
<h2 class="dialog-title">New grid</h2>
|
||
<form method="dialog" id="form-new-grid">
|
||
<div class="form-row">
|
||
<label for="ng-name">Name</label>
|
||
<input type="text" id="ng-name" autocomplete="off" placeholder="e.g. Capacitor box A">
|
||
</div>
|
||
<div class="form-row-pair">
|
||
<div class="form-row">
|
||
<label for="ng-rows">Total rows</label>
|
||
<input type="number" id="ng-rows" min="1" max="500" value="4">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="ng-cols">Total columns</label>
|
||
<input type="number" id="ng-cols" min="1" max="500" value="6">
|
||
</div>
|
||
</div>
|
||
<div class="form-section-label">Photo coverage <span class="label-hint">(cells visible in each photo)</span></div>
|
||
<div class="form-row-pair">
|
||
<div class="form-row">
|
||
<label for="ng-photo-rows">Rows per photo</label>
|
||
<input type="number" id="ng-photo-rows" min="1" max="500" value="4">
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="ng-photo-cols">Cols per photo</label>
|
||
<input type="number" id="ng-photo-cols" min="1" max="500" value="6">
|
||
</div>
|
||
</div>
|
||
<div class="ng-summary" id="ng-summary"></div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="ng-cancel">Cancel</button>
|
||
<button type="submit" class="btn btn-primary">Continue →</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: SOURCE PICKER ===== -->
|
||
<template id="t-dialog-source-picker">
|
||
<dialog id="dialog-source-picker" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title">Choose source image</h2>
|
||
<div class="picker-toolbar">
|
||
<label class="btn btn-secondary">
|
||
+ Upload new
|
||
<input type="file" accept="image/*" multiple hidden id="picker-upload-input">
|
||
</label>
|
||
<span class="picker-hint">or select an existing image below</span>
|
||
</div>
|
||
<div id="source-picker-grid" class="source-gallery picker-gallery"></div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="picker-cancel">Cancel</button>
|
||
</div>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: CONFIRM ===== -->
|
||
<template id="t-dialog-confirm">
|
||
<dialog id="dialog-confirm" class="app-dialog app-dialog-sm">
|
||
<h2 class="dialog-title">Confirm</h2>
|
||
<p id="confirm-message" class="confirm-message"></p>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="confirm-cancel">Cancel</button>
|
||
<button type="button" class="btn btn-danger" id="confirm-ok">Delete</button>
|
||
</div>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: FILE PICKER ===== -->
|
||
<template id="t-dialog-file-picker">
|
||
<dialog id="dialog-file-picker" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title">Files</h2>
|
||
<div id="fp-list" class="fp-list"></div>
|
||
<div class="fp-upload-section">
|
||
<div class="form-section-label">Upload new PDF</div>
|
||
<div class="fp-upload-row">
|
||
<input type="file" id="fp-file-input" accept=".pdf,application/pdf">
|
||
</div>
|
||
<div class="fp-upload-row">
|
||
<label class="fp-field-label">Display name</label>
|
||
<input type="text" id="fp-upload-name" placeholder="Human readable label" autocomplete="off">
|
||
</div>
|
||
<div class="fp-upload-row">
|
||
<label class="fp-field-label">Filename on disk</label>
|
||
<input type="text" id="fp-upload-filename" placeholder="e.g. lm741.pdf" autocomplete="off" spellcheck="false">
|
||
</div>
|
||
<div class="fp-upload-row">
|
||
<button type="button" class="btn btn-primary btn-sm" id="fp-upload-btn">Upload</button>
|
||
</div>
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="fp-cancel">Close</button>
|
||
</div>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== BINS SECTION ===== -->
|
||
<template id="t-section-bins">
|
||
<section class="section" id="section-bins">
|
||
<div class="section-toolbar">
|
||
<div class="tab-bar">
|
||
<button class="tab-btn" id="btn-tab-bins">Bins</button>
|
||
<button class="tab-btn" id="btn-tab-bin-sources">Source images</button>
|
||
<button class="tab-btn" id="btn-tab-bin-types">Types</button>
|
||
</div>
|
||
<label class="btn btn-secondary" id="btn-upload-bin-sources" hidden>
|
||
+ Upload
|
||
<input type="file" accept="image/*" multiple hidden id="bin-source-upload-input">
|
||
</label>
|
||
<button class="btn btn-primary" id="btn-add-bin-type" hidden>+ Add type</button>
|
||
</div>
|
||
<div id="tab-bins-content">
|
||
<div class="bin-gallery" id="bin-gallery"></div>
|
||
</div>
|
||
<div id="tab-bin-sources-content" hidden>
|
||
<div id="bin-source-image-list" class="source-gallery"></div>
|
||
</div>
|
||
<div id="tab-bin-types-content" hidden>
|
||
<div id="bin-types-list" class="bin-types-list"></div>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
|
||
<template id="t-bin-type-row">
|
||
<div class="bin-type-row">
|
||
<div class="bin-type-info">
|
||
<span class="bin-type-name"></span>
|
||
<span class="bin-type-dims"></span>
|
||
<span class="bin-type-desc"></span>
|
||
</div>
|
||
<span class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</span>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="t-bin-card">
|
||
<div class="bin-card">
|
||
<div class="bin-card-img-wrap">
|
||
<img class="bin-card-img" alt="">
|
||
<div class="bin-card-unprocessed">Not processed</div>
|
||
</div>
|
||
<div class="bin-card-footer">
|
||
<span class="bin-card-name"></span>
|
||
<span class="row-actions">
|
||
<button class="btn-icon btn-edit" title="Edit corners">✎</button>
|
||
<button class="btn-icon btn-danger btn-delete" title="Delete">✕</button>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== DIALOG: BIN EDITOR ===== -->
|
||
<template id="t-dialog-bin-editor">
|
||
<dialog id="dialog-bin-editor" class="app-dialog app-dialog-wide">
|
||
<h2 class="dialog-title">Edit bin</h2>
|
||
<div class="form-row">
|
||
<label>Name</label>
|
||
<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 class="form-row" id="bin-editor-dims-row">
|
||
<label>Dimensions (mm)</label>
|
||
<div class="bin-editor-dims">
|
||
<input type="number" id="bin-editor-width" placeholder="W" min="1" step="1">
|
||
<span>×</span>
|
||
<input type="number" id="bin-editor-height" placeholder="H" min="1" step="1">
|
||
<span class="form-hint">Leave blank to infer from corners</span>
|
||
</div>
|
||
</div>
|
||
<canvas id="bin-editor-canvas" class="bin-editor-canvas"></canvas>
|
||
<div class="dialog-actions">
|
||
<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 & process</button>
|
||
</div>
|
||
</dialog>
|
||
</template>
|
||
|
||
<template id="t-dialog-bin-type">
|
||
<dialog id="dialog-bin-type" class="app-dialog">
|
||
<h2 class="dialog-title"></h2>
|
||
<div class="form-row">
|
||
<label>Name</label>
|
||
<input type="text" id="bt-name" autocomplete="off" placeholder="e.g. Sortimo L-Boxx small">
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Dimensions (mm)</label>
|
||
<div class="bin-editor-dims">
|
||
<input type="number" id="bt-width" placeholder="W" min="1" step="1">
|
||
<span>×</span>
|
||
<input type="number" id="bt-height" placeholder="H" min="1" step="1">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<label>Description</label>
|
||
<input type="text" id="bt-description" autocomplete="off" placeholder="Optional">
|
||
</div>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="btn btn-secondary" id="bt-cancel">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="bt-save">Save</button>
|
||
</div>
|
||
</dialog>
|
||
</template>
|
||
|
||
<!-- ===== CELL INVENTORY OVERLAY ===== -->
|
||
<template id="t-cell-inventory">
|
||
<div class="cell-inventory-overlay" id="cell-inventory-overlay">
|
||
<div class="cell-inventory-header">
|
||
<span class="cell-inventory-title"></span>
|
||
<button type="button" class="btn-icon" id="cell-inv-close">✕</button>
|
||
</div>
|
||
<div class="cell-inventory-list" id="cell-inventory-list"></div>
|
||
<div class="cell-inventory-actions">
|
||
<button type="button" class="btn btn-primary btn-sm" id="cell-inv-add">+ Add entry</button>
|
||
</div>
|
||
</div>
|
||
</template>
|