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>
242 lines
5.2 KiB
JavaScript
242 lines
5.2 KiB
JavaScript
import { mkdirSync } from 'node:fs';
|
|
import { Simple_KeyValue_Store } from './kv-store.mjs';
|
|
|
|
mkdirSync('./data', { recursive: true });
|
|
|
|
const store = new Simple_KeyValue_Store('./data/inventory.ndjson', {
|
|
auto_load: true,
|
|
auto_store: true,
|
|
debounce_flush_timeout: 5000,
|
|
});
|
|
|
|
// --- Field definitions ---
|
|
|
|
export function list_fields() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('f:')) {
|
|
result.push(store.get(key));
|
|
}
|
|
}
|
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
export function get_field(id) {
|
|
return store.get(`f:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_field(field) {
|
|
store.set(`f:${field.id}`, field);
|
|
}
|
|
|
|
export function delete_field(id) {
|
|
return store.delete(`f:${id}`);
|
|
}
|
|
|
|
// --- Components ---
|
|
|
|
export function list_components() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('c:')) {
|
|
result.push(store.get(key));
|
|
}
|
|
}
|
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
export function get_component(id) {
|
|
return store.get(`c:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_component(component) {
|
|
store.set(`c:${component.id}`, component);
|
|
}
|
|
|
|
export function delete_component(id) {
|
|
return store.delete(`c:${id}`);
|
|
}
|
|
|
|
// --- Inventory entries ---
|
|
|
|
export function list_inventory() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('i:')) {
|
|
result.push(store.get(key));
|
|
}
|
|
}
|
|
return result.sort((a, b) => a.created_at - b.created_at);
|
|
}
|
|
|
|
export function get_inventory_entry(id) {
|
|
return store.get(`i:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_inventory_entry(entry) {
|
|
store.set(`i:${entry.id}`, entry);
|
|
}
|
|
|
|
export function delete_inventory_entry(id) {
|
|
return store.delete(`i:${id}`);
|
|
}
|
|
|
|
// --- Grid drafts ---
|
|
|
|
export function list_grid_drafts() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('d:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => b.updated_at - a.updated_at);
|
|
}
|
|
|
|
export function get_grid_draft(id) {
|
|
return store.get(`d:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_grid_draft(draft) {
|
|
store.set(`d:${draft.id}`, draft);
|
|
}
|
|
|
|
export function delete_grid_draft(id) {
|
|
return store.delete(`d:${id}`);
|
|
}
|
|
|
|
// --- Source images ---
|
|
|
|
export function list_source_images() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('s:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => b.created_at - a.created_at);
|
|
}
|
|
|
|
export function get_source_image(id) {
|
|
return store.get(`s:${id}`) ?? null;
|
|
}
|
|
|
|
export function add_source_image(src) {
|
|
store.set(`s:${src.id}`, src);
|
|
}
|
|
|
|
export function delete_source_image(id) {
|
|
return store.delete(`s:${id}`);
|
|
}
|
|
|
|
// --- Component templates ---
|
|
|
|
export function list_component_templates() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('ct:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
export function get_component_template(id) {
|
|
return store.get(`ct:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_component_template(tmpl) {
|
|
store.set(`ct:${tmpl.id}`, tmpl);
|
|
}
|
|
|
|
export function delete_component_template(id) {
|
|
return store.delete(`ct:${id}`);
|
|
}
|
|
|
|
// --- PDF files ---
|
|
|
|
export function list_pdfs() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('pdf:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => a.display_name.localeCompare(b.display_name));
|
|
}
|
|
|
|
export function get_pdf(id) {
|
|
return store.get(`pdf:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_pdf(pdf) {
|
|
store.set(`pdf:${pdf.id}`, pdf);
|
|
}
|
|
|
|
export function delete_pdf(id) {
|
|
return store.delete(`pdf:${id}`);
|
|
}
|
|
|
|
// Returns all components that reference the given PDF id in their file_ids array.
|
|
export function find_pdf_references(pdf_id) {
|
|
return list_components().filter(c => c.file_ids?.includes(pdf_id));
|
|
}
|
|
|
|
// --- Bin types ---
|
|
|
|
export function list_bin_types() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('bt:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
export function get_bin_type(id) {
|
|
return store.get(`bt:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_bin_type(bt) {
|
|
store.set(`bt:${bt.id}`, bt);
|
|
}
|
|
|
|
export function delete_bin_type(id) {
|
|
return store.delete(`bt:${id}`);
|
|
}
|
|
|
|
// --- Bins ---
|
|
|
|
export function list_bins() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('bin:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => b.created_at - a.created_at);
|
|
}
|
|
|
|
export function get_bin(id) {
|
|
return store.get(`bin:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_bin(bin) {
|
|
store.set(`bin:${bin.id}`, bin);
|
|
}
|
|
|
|
export function delete_bin(id) {
|
|
return store.delete(`bin:${id}`);
|
|
}
|
|
|
|
// --- Grid images ---
|
|
|
|
export function list_grid_images() {
|
|
const result = [];
|
|
for (const [key] of store.data.entries()) {
|
|
if (key.startsWith('g:')) result.push(store.get(key));
|
|
}
|
|
return result.sort((a, b) => b.created_at - a.created_at);
|
|
}
|
|
|
|
export function get_grid_image(id) {
|
|
return store.get(`g:${id}`) ?? null;
|
|
}
|
|
|
|
export function set_grid_image(grid) {
|
|
store.set(`g:${grid.id}`, grid);
|
|
}
|
|
|
|
export function delete_grid_image(id) {
|
|
return store.delete(`g:${id}`);
|
|
}
|