From 13e5200b8d25c9b2a1a5da06bff9ea9e0aa99d81 Mon Sep 17 00:00:00 2001 From: type-two Date: Tue, 23 Jun 2026 11:26:34 +1000 Subject: [PATCH] feat(nav): colour crates by type in the rack diagram (material_color) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /nav/rack returns each crate's virtual_crate_type.material_color + type name; drawRack fills/strokes/arrows each crate in its real colour (Blue Crate #0000FF, Wooden Rack Bin #BA906A, White Tub #FFFFFF, Black Tub #191970, Shelf #C0C0C0…) instead of all-blue. Legend lists the crate types present on the rack. Co-Authored-By: Claude Opus 4.8 --- app/navigator_routes.py | 3 ++- site/search.html | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/navigator_routes.py b/app/navigator_routes.py index be2c8b0..c070d57 100644 --- a/app/navigator_routes.py +++ b/app/navigator_routes.py @@ -67,8 +67,9 @@ async def rack_detail(rack_id: int, ident=Depends(require_token), db=Depends(get crates = [dict(r) for r in (await db.execute(text(""" SELECT c.id, c.name, c.label_text, c.rack_level_index::int AS level, c.slot_number::int AS slot, c.direction, c.pos_x::float AS x, c.pos_z::float AS z, c.crate_purpose, + ct.name AS crate_type, ct.material_color AS color, (SELECT count(*) FROM inventory i WHERE i.crate_id = c.id AND i.in_stock) AS items - FROM virtual_crate c + FROM virtual_crate c LEFT JOIN virtual_crate_type ct ON ct.id = c.crate_type_id WHERE c.rack_id = :id AND c.visible = 'y' ORDER BY c.rack_level_index NULLS FIRST, c.slot_number NULLS LAST """), {"id": rack_id})).mappings()] diff --git a/site/search.html b/site/search.html index 5614674..bbae63e 100644 --- a/site/search.html +++ b/site/search.html @@ -145,6 +145,10 @@ const money=n=>n==null?'—':'$'+Number(n).toFixed(2); const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); const imgTag=it=> it.release_id ? `` : (it.thumb?``:''); function chip(label,ondel,num){ return `${esc(label)} `; } +// crate-type colour (material_color from virtual_crate_type): blue crate #0000FF, wooden #BA906A, white tub #FFFFFF… +function hexRgb(h){ h=String(h||'').replace('#',''); if(h.length===3)h=h.split('').map(c=>c+c).join(''); const n=parseInt(h,16); return isNaN(n)?[30,144,255]:[(n>>16)&255,(n>>8)&255,n&255]; } +function rgba(h,a){ const [r,g,b]=hexRgb(h); return `rgba(${r},${g},${b},${a})`; } +function darken(h,f){ const [r,g,b]=hexRgb(h); return `rgb(${Math.round(r*f)},${Math.round(g*f)},${Math.round(b*f)})`; } // facing convention (RECORDGOD_NAVIGATOR_PLAN §3) const CELL=0.34; @@ -262,7 +266,9 @@ function drawRack(){ const {lv,label}=levelMeta(); $('#lvlCtl').style.display = lv>1?'inline-flex':'none'; $('#lvlLabel').textContent=label; $('#lvlPrev').disabled=ST.level<=1; $('#lvlNext').disabled=ST.level>=lv; - $('#legend').innerHTML='crate↑↓←→ = facing · green=items'+(ST.tab==='reorganize'?' · click in order to pick':ST.tab==='collections'&&ST.edit?' · click to add to collection':'')+''; + const types=[...new Map((ST.crates||[]).map(c=>[c.crate_type||'crate', c.color||'#1e90ff'])).entries()]; + $('#legend').innerHTML=types.map(([n,c])=>`${esc(n)}`).join('') + +'↑↓←→ = facing'+(ST.tab==='reorganize'?'click in order to pick':ST.tab==='collections'&&ST.edit?'click to add to collection':''); const cv=$('#cv'), g=cv.getContext('2d'), W=cv.width, H=cv.height; g.clearRect(0,0,W,H); g.fillStyle='#fbfbfd'; g.fillRect(0,0,W,H); ST.hit=[]; @@ -286,16 +292,16 @@ function drawRack(){ if(row>=rows) return; const x=oX+col*cellW+3, y=oY+row*cellH+3, w=cellW-6, h=cellH-6; const sel=ST.selCrate===c.id; - const picked=ST.tab==='reorganize' && ST.pick.indexOf(c.id); const inColl=ST.tab==='collections' && ST.edit && ST.edit.crate_ids.includes(c.id); - g.fillStyle = inColl?'rgba(70,180,90,.28)' : sel?'rgba(255,46,147,.30)' : 'rgba(30,144,255,.16)'; - g.strokeStyle = inColl?'#2e8b57' : sel?'#d10f7a' : '#1e90ff'; g.lineWidth=sel?3:1.5; + const tcol=c.color||'#1e90ff'; // crate-type material colour + g.fillStyle = inColl?'rgba(70,180,90,.28)' : sel?'rgba(255,46,147,.30)' : rgba(tcol,0.34); + g.strokeStyle = inColl?'#2e8b57' : sel?'#d10f7a' : darken(tcol,0.55); g.lineWidth=sel?3:1.5; g.beginPath(); g.rect(x,y,w,h); g.fill(); g.stroke(); g.fillStyle='#1b1b22'; g.font='bold 10px system-ui'; g.textAlign='center'; g.textBaseline='top'; g.fillText('#'+c.id, x+w/2, y+4); const gl=(c.label_text||c.crate_purpose||'').toString().split(',')[0].slice(0,12); g.fillStyle='#555'; g.font='9px system-ui'; g.fillText(gl||'—', x+w/2, y+16); - g.fillStyle=sel?'#d10f7a':'#1e90ff'; g.font='bold 20px system-ui'; g.textBaseline='middle'; + g.fillStyle=sel?'#d10f7a':darken(tcol,0.55); g.font='bold 20px system-ui'; g.textBaseline='middle'; g.fillText(ARROW[(c.direction||'').toLowerCase()]||'•', x+w/2, y+h-13); g.fillStyle='#1a8f54'; g.font='9px system-ui'; g.textAlign='right'; g.textBaseline='top'; g.fillText(slot, x+w-3, y+3);