diff --git a/db/schema.sql b/db/schema.sql index 9f5a07f..d27c19c 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -312,6 +312,10 @@ CREATE TABLE IF NOT EXISTS printers ( date_modified TIMESTAMPTZ ); +-- What each printer last reported loaded, slot by slot, for its card. +ALTER TABLE printers ADD COLUMN IF NOT EXISTS loaded JSONB; +ALTER TABLE printers ADD COLUMN IF NOT EXISTS loaded_at TIMESTAMPTZ; + -- The one printer that used to live in Settings becomes the first entry here. INSERT INTO printers (host, serial, access_code) SELECT h.value, s.value, c.value diff --git a/lib/printers.js b/lib/printers.js index 2174867..e34e3cf 100644 --- a/lib/printers.js +++ b/lib/printers.js @@ -85,6 +85,21 @@ async function remove(id) { return true; } +// Keeps what a printer reported loaded — enough to draw its card, nothing that +// identifies a spool. +async function recordLoaded(id, trays) { + const loaded = (trays || []).map((t) => ({ + unit: t.unit ?? null, + slot: t.slot ?? null, + empty: Boolean(t.empty), + material: t.material || null, + colour: t.colour || null, + tagged: Boolean(t.uuid), + })); + await db.query('UPDATE printers SET loaded = $2::jsonb, loaded_at = now() WHERE id = $1', + [id, JSON.stringify(loaded)]); +} + const measures = (amsType) => Boolean(AMS_TYPES[amsType]); -module.exports = { AMS_TYPES, list, listWithCodes, get, create, update, remove, measures, publicPrinter }; +module.exports = { AMS_TYPES, list, listWithCodes, get, create, update, remove, recordLoaded, measures, publicPrinter }; diff --git a/public/app.js b/public/app.js index 0b3e6f2..1c4ba8a 100644 --- a/public/app.js +++ b/public/app.js @@ -2617,6 +2617,38 @@ function printerName(p) { return (p.nickname || '').trim() || p.model || (p.host ? `Printer at ${p.host}` : 'Printer'); } +// The first AMS unit's four slots, in order, for colouring in the drawing. +function drawnSlots(p) { + const units = (p.loaded || []).filter((t) => t.unit != null); + if (!units.length) return []; + const first = Math.min(...units.map((t) => t.unit)); + const slots = []; + for (const t of units) if (t.unit === first && t.slot >= 0 && t.slot < 4) slots[t.slot] = t; + return slots; +} + +// What's in each slot, as last reported, under the drawing. +function loadedHtml(p) { + if (!p.loaded) return p.id ? '
Nothing reported yet — “Test & sync” to read it.
' : ''; + // The tracked spool in that slot names the colour; otherwise the nearest Bambu colour. + const colourName = (t) => { + const s = spools.find((x) => x.ams_slot === `${p.serial}/${t.unit == null ? 'ext' : t.unit}/${t.slot == null ? 0 : t.slot}`); + return (s && s.colour) || (t.colour ? paletteName(t.colour, t.material) : '') || ''; + }; + const chip = (t, label) => { + const what = t.empty ? 'empty' : `${escapeHtml([t.material || '?', colourName(t)].filter(Boolean).join(' '))}${t.tagged ? '' : ' (untagged)'}`; + const dot = t.empty + ? '' + : ``; + return `As of ${escapeHtml(new Date(p.loaded_at).toLocaleString([], { dateStyle: 'medium', timeStyle: 'short' }))}
` : ''; + return rows.length ? `Nothing loaded.
${when}`; +} + function printerCardHtml(p) { const key = p.id || 'new'; const models = [''] @@ -2627,7 +2659,10 @@ function printerCardHtml(p) { .join(''); return `