feat(nav): rotate diagram button — 90° view rotation (rack + store), text stays upright
ST.viewRot quarter-turns: drawRack remaps cells (rcell), edge labels (BACK/FRONT/L/R) and
facing-arrow glyphs per rotation while keeping all text upright; drawStore rotates rack
positions+yaw around centre with a matching unrotPt() so rack drag-to-move stays correct.
🔄 rotate button in the toolbar. Lets the diagram match where you're physically standing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e20be06bf0
commit
d6c08f05ce
@ -94,6 +94,7 @@
|
||||
<button class="ghost" id="lvlNext" onclick="stepLevel(1)">▶</button>
|
||||
</span>
|
||||
<button class="ghost" id="rackEditBtn" style="display:none" onclick="rackEdit()">✏️ rack</button>
|
||||
<button class="ghost" id="rotBtn" onclick="rotateView()" title="rotate the diagram 90°">🔄 rotate</button>
|
||||
<span id="selModeHint" class="selmode" style="display:none">● Select Mode — click crates in order</span>
|
||||
</div>
|
||||
|
||||
@ -163,7 +164,7 @@ const ARROW={forward:'↓',front:'↓',north:'↓',back:'↑',south:'↑',left:'
|
||||
|
||||
let ST={tab:'scanner', view:'store', space:null, racks:[], rack:null, level:1, crates:[], levels:[],
|
||||
selCrate:null, hit:[], storeTf:null, itemShown:false,
|
||||
pick:[], pickNames:{}, edit:null, crateInfo:null, pickSlot:null, pickPos:'before'};
|
||||
pick:[], pickNames:{}, edit:null, crateInfo:null, pickSlot:null, pickPos:'before', viewRot:0};
|
||||
|
||||
async function signin(){
|
||||
TOKEN=$('#tok').value.trim();
|
||||
@ -231,6 +232,7 @@ function setView(m){
|
||||
if(m==='item'){ if(ST.itemShown){ ST.view='item'; setViewBtns(); $('#relBody').scrollIntoView({behavior:'smooth',block:'nearest'}); } return; }
|
||||
}
|
||||
function redraw(){ if(ST.view==='store') drawStore(); else if(ST.rack) drawRack(); }
|
||||
function rotateView(){ ST.viewRot=((ST.viewRot||0)+90)%360; redraw(); }
|
||||
|
||||
// ───────────── STORE VIEW ─────────────
|
||||
async function toStore(){
|
||||
@ -254,9 +256,12 @@ function drawStore(){
|
||||
const pad=34, sc=Math.min((W-2*pad)/((maxX-minX)||1),(H-2*pad)/((maxZ-minZ)||1));
|
||||
ST.storeTf={minX,minZ,sc,pad};
|
||||
const X=x=>pad+(x-minX)*sc, Y=z=>pad+(z-minZ)*sc;
|
||||
const q=(((ST.viewRot||0)/90)%4+4)%4;
|
||||
ST.racks.forEach(r=>{
|
||||
const yaw=directionYaw(r.direction)+(r.rot||0)*Math.PI/180;
|
||||
const hw=(r.w||0.5)/2*sc, hd=(r.d||0.5)/2*sc, cx=X(r.x), cy=Y(r.z);
|
||||
const yaw=directionYaw(r.direction)+(r.rot||0)*Math.PI/180 + q*Math.PI/2;
|
||||
const hw=(r.w||0.5)/2*sc, hd=(r.d||0.5)/2*sc;
|
||||
let cx=X(r.x), cy=Y(r.z);
|
||||
if(q){ const a=q*Math.PI/2, co=Math.cos(a), si=Math.sin(a), dx=cx-W/2, dy=cy-H/2; cx=W/2+dx*co-dy*si; cy=H/2+dx*si+dy*co; }
|
||||
const cos=Math.cos(yaw), sin=Math.sin(yaw);
|
||||
const corners=[[-hw,-hd],[hw,-hd],[hw,hd],[-hw,hd]].map(([px,pz])=>[cx+px*cos-pz*sin, cy+px*sin+pz*cos]);
|
||||
g.beginPath(); g.moveTo(corners[0][0],corners[0][1]); corners.slice(1).forEach(c=>g.lineTo(c[0],c[1])); g.closePath();
|
||||
@ -298,23 +303,30 @@ function drawRack(){
|
||||
ST.hit=[];
|
||||
const rw=ST.rack.w||1.7, rd=ST.rack.d||0.7;
|
||||
const cols=Math.max(1,Math.floor(rw/CELL)), rows=Math.max(1,Math.round(rd/CELL));
|
||||
const pad=42, sc=0.92*Math.min((W-2*pad)/rw,(H-2*pad)/rd);
|
||||
const cW=rw*sc, cH=rd*sc, oX=(W-cW)/2, oY=(H-cH)/2, cellW=cW/cols, cellH=cH/rows;
|
||||
const q=(((ST.viewRot||0)/90)%4+4)%4; // view rotation (quarter turns CW; text stays upright)
|
||||
const DC=q%2?rows:cols, DR=q%2?cols:rows; // displayed grid dims
|
||||
const drw=q%2?rd:rw, drh=q%2?rw:rd; // displayed physical dims
|
||||
const pad=42, sc=0.92*Math.min((W-2*pad)/drw,(H-2*pad)/drh);
|
||||
const cW=drw*sc, cH=drh*sc, oX=(W-cW)/2, oY=(H-cH)/2, cellW=cW/DC, cellH=cH/DR;
|
||||
g.fillStyle='#fff'; g.strokeStyle='#222'; g.lineWidth=3; g.beginPath(); g.rect(oX,oY,cW,cH); g.fill(); g.stroke();
|
||||
const SIDES=[['BACK','R','FRONT','L'],['L','BACK','R','FRONT'],['FRONT','L','BACK','R'],['R','FRONT','L','BACK']][q]; // top,right,bottom,left
|
||||
g.fillStyle='#888'; g.font='11px system-ui'; g.textAlign='center';
|
||||
g.fillText('BACK',oX+cW/2,oY-8); g.fillText('FRONT',oX+cW/2,oY+cH+16);
|
||||
g.save(); g.translate(oX-10,oY+cH/2); g.rotate(-Math.PI/2); g.fillText('L',0,0); g.restore();
|
||||
g.save(); g.translate(oX+cW+12,oY+cH/2); g.rotate(Math.PI/2); g.fillText('R',0,0); g.restore();
|
||||
g.fillText(SIDES[0],oX+cW/2,oY-8); g.fillText(SIDES[2],oX+cW/2,oY+cH+16);
|
||||
g.save(); g.translate(oX-10,oY+cH/2); g.rotate(-Math.PI/2); g.fillText(SIDES[3],0,0); g.restore();
|
||||
g.save(); g.translate(oX+cW+12,oY+cH/2); g.rotate(Math.PI/2); g.fillText(SIDES[1],0,0); g.restore();
|
||||
g.strokeStyle='#eee'; g.lineWidth=1;
|
||||
for(let i=1;i<cols;i++){ g.beginPath(); g.moveTo(oX+i*cellW,oY); g.lineTo(oX+i*cellW,oY+cH); g.stroke(); }
|
||||
for(let i=1;i<rows;i++){ g.beginPath(); g.moveTo(oX,oY+i*cellH); g.lineTo(oX+cW,oY+i*cellH); g.stroke(); }
|
||||
for(let i=1;i<DC;i++){ g.beginPath(); g.moveTo(oX+i*cellW,oY); g.lineTo(oX+i*cellW,oY+cH); g.stroke(); }
|
||||
for(let i=1;i<DR;i++){ g.beginPath(); g.moveTo(oX,oY+i*cellH); g.lineTo(oX+cW,oY+i*cellH); g.stroke(); }
|
||||
const rcell=(col,row)=> q===0?[col,row] : q===1?[rows-1-row,col] : q===2?[cols-1-col,rows-1-row] : [row,cols-1-col];
|
||||
const rotGlyph=g0=>{ let gx=g0; for(let i=0;i<q;i++) gx=({'↑':'→','→':'↓','↓':'←','←':'↑'})[gx]||gx; return gx; };
|
||||
const here=ST.crates.filter(c=>(c.level==null?1:c.level)===ST.level);
|
||||
let autoSlot=0;
|
||||
here.forEach(c=>{
|
||||
let slot=c.slot; if(!slot||slot<1){ autoSlot++; slot=autoSlot; }
|
||||
const idx=slot-1, col=idx%cols, row=Math.floor(idx/cols);
|
||||
if(row>=rows) return;
|
||||
const x=oX+col*cellW+3, y=oY+row*cellH+3, w=cellW-6, h=cellH-6;
|
||||
const [dx,dy]=rcell(col,row);
|
||||
const x=oX+dx*cellW+3, y=oY+dy*cellH+3, w=cellW-6, h=cellH-6;
|
||||
const sel=ST.selCrate===c.id;
|
||||
const inColl=ST.tab==='collections' && ST.edit && ST.edit.crate_ids.includes(c.id);
|
||||
const tcol=c.color||'#1e90ff'; // crate-type material colour
|
||||
@ -326,7 +338,7 @@ function drawRack(){
|
||||
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':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.fillText(rotGlyph(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);
|
||||
g.fillStyle='#888'; g.textAlign='left'; g.fillText((c.items||0), x+3, y+h-12);
|
||||
@ -709,6 +721,7 @@ $('#omni').addEventListener('keydown',e=>{ if(e.key==='Enter') omni(); });
|
||||
|
||||
// ───────────── canvas: unified click + rack drag ─────────────
|
||||
function cvXY(e){ const cv=$('#cv'),r=cv.getBoundingClientRect(); return {mx:(e.clientX-r.left)*cv.width/r.width, my:(e.clientY-r.top)*cv.height/r.height}; }
|
||||
function unrotPt(mx,my){ const q=(((ST.viewRot||0)/90)%4+4)%4; if(!q) return [mx,my]; const cv=$('#cv'),W=cv.width,H=cv.height,a=-q*Math.PI/2,c=Math.cos(a),s=Math.sin(a),dx=mx-W/2,dy=my-H/2; return [W/2+dx*c-dy*s, H/2+dx*s+dy*c]; }
|
||||
function hitRack(mx,my){ let best=null,bd=1e9; for(const h of ST.hit){ if(h.type!=='rack')continue; const dd=Math.hypot(mx-h.cx,my-h.cy); if(dd<h.r&&dd<bd){bd=dd;best=h;} } return best; }
|
||||
function hitCrate(mx,my){ for(const h of ST.hit){ if(h.type==='crate'&&mx>=h.x&&mx<=h.x+h.w&&my>=h.y&&my<=h.y+h.h) return h; } return null; }
|
||||
let down=null;
|
||||
@ -720,8 +733,8 @@ $('#cv').addEventListener('mousemove',e=>{
|
||||
if(!down||!down.dragRack) return;
|
||||
const {mx,my}=cvXY(e);
|
||||
if(Math.hypot(mx-down.mx,my-down.my)>5) down.moved=true;
|
||||
if(down.moved){ const tf=ST.storeTf, r=ST.racks.find(x=>x.id===down.dragRack);
|
||||
r.x=tf.minX+(mx-tf.pad)/tf.sc; r.z=tf.minZ+(my-tf.pad)/tf.sc; drawStore(); }
|
||||
if(down.moved){ const tf=ST.storeTf, r=ST.racks.find(x=>x.id===down.dragRack), [wmx,wmy]=unrotPt(mx,my);
|
||||
r.x=tf.minX+(wmx-tf.pad)/tf.sc; r.z=tf.minZ+(wmy-tf.pad)/tf.sc; drawStore(); }
|
||||
});
|
||||
window.addEventListener('mouseup',async e=>{
|
||||
if(!down) return; const d=down; down=null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user