← back to Dw Photo Capture
Front/Back tiles: tap a taken photo to adjust it in the Enhance/Crop editor; Adjust + Retake buttons below each tile (TK-12256)
69d50160659eb80e9ad45e980640137137bc7710 · 2026-09-25 12:22:23 -0700 · Steve Abrams
Editor/crop overlay raised above the add modal (z 130) so it opens from the tiles.
Adjusted pixels replace the local shot only — nothing uploads until Create.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZynS56oLsqJTcschug1a7
Files touched
Diff
commit 69d50160659eb80e9ad45e980640137137bc7710
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 25 12:22:23 2026 -0700
Front/Back tiles: tap a taken photo to adjust it in the Enhance/Crop editor; Adjust + Retake buttons below each tile (TK-12256)
Editor/crop overlay raised above the add modal (z 130) so it opens from the tiles.
Adjusted pixels replace the local shot only — nothing uploads until Create.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZynS56oLsqJTcschug1a7
---
public/index.html | 44 ++++++++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/public/index.html b/public/index.html
index 21698d8..1ae78de 100644
--- a/public/index.html
+++ b/public/index.html
@@ -238,7 +238,7 @@
.gal-empty{grid-column:1/-1;text-align:center;color:var(--muted);padding:40px 10px}
/* ===== Photo editor (opens after capture, before upload) ===== */
- .editor{position:fixed;inset:0;z-index:100;background:rgba(8,7,6,.97);display:flex;flex-direction:column}
+ .editor{position:fixed;inset:0;z-index:140;background:rgba(8,7,6,.97);display:flex;flex-direction:column}
.editor[hidden]{display:none}
.ed-stage{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:12px 12px 0;overflow:hidden}
#edcanvas{max-width:100%;max-height:100%;border-radius:12px;box-shadow:0 8px 40px rgba(0,0,0,.6);background:#000}
@@ -343,6 +343,12 @@
.shot-btn.have .shot-ico{display:none}
.shot-btn.have b,.shot-btn.have small,.shot-btn.have .shot-ico{position:relative;z-index:1;text-shadow:0 1px 3px #000,0 0 8px #000}
.shot-btn.have .shot-num,.shot-btn.have .shot-ck::after{z-index:2}
+ /* TK-12256: tap a taken photo to adjust it; Adjust / Retake buttons under each tile */
+ .shot-acts{display:flex;gap:10px;margin:-2px 0 8px}
+ .shot-acts>div{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
+ .shot-acts button{flex:1;min-width:0;padding:0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:38px;border-radius:10px;border:1px solid var(--line);background:#16140f;color:var(--ink);font-size:14px;font-weight:700;cursor:pointer}
+ .shot-acts button.adj{border-color:var(--gold);color:var(--gold)}
+ .shot-acts button:disabled{opacity:.35;cursor:default}
/* both-sides photo strip used by the Preview + Created + Update result views */
.ph-pair{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px;margin:10px 0 4px}
.ph-pair figure{margin:0;background:#0f0e0b;border:1px solid var(--line);border-radius:10px;overflow:hidden;cursor:zoom-in}
@@ -446,7 +452,7 @@
.sl .v{font:600 12px/1 ui-monospace,Menlo,monospace;color:var(--ink);text-align:right}
.ed-crop{background:#16140f;border:1px solid var(--gold);color:var(--gold)}
/* crop overlay */
- .cropov{position:fixed;inset:0;z-index:120}
+ .cropov{position:fixed;inset:0;z-index:150}
.cropov[hidden]{display:none}
.cropbox{position:fixed;border:1px solid #fff;box-shadow:0 0 0 4000px rgba(0,0,0,.55);cursor:move;touch-action:none;
background-image:linear-gradient(rgba(255,255,255,.25) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.25) 1px,transparent 1px);background-size:33.33% 33.33%}
@@ -455,10 +461,10 @@
.cropbox i[data-h=ne]{right:-3px;top:-3px;border-left:0;border-bottom:0;cursor:nesw-resize}
.cropbox i[data-h=sw]{left:-3px;bottom:-3px;border-right:0;border-top:0;cursor:nesw-resize}
.cropbox i[data-h=se]{right:-3px;bottom:-3px;border-left:0;border-top:0;cursor:nwse-resize}
- .crop-presets{position:fixed;left:50%;top:max(14px,env(safe-area-inset-top));transform:translateX(-50%);display:flex;gap:7px;z-index:121}
+ .crop-presets{position:fixed;left:50%;top:max(14px,env(safe-area-inset-top));transform:translateX(-50%);display:flex;gap:7px;z-index:151}
.crop-presets button{background:rgba(8,7,6,.75);border:1px solid var(--line);color:var(--ink);border-radius:99px;padding:8px 13px;font-size:13px;font-weight:600;cursor:pointer}
.crop-presets button.on{background:var(--gold);color:#1b1407;border-color:var(--gold)}
- .crop-bar{position:fixed;left:50%;bottom:max(24px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;gap:12px;z-index:121}
+ .crop-bar{position:fixed;left:50%;bottom:max(24px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;gap:12px;z-index:151}
.crop-bar button{border:none;border-radius:12px;padding:14px 26px;font-weight:700;font-size:15px;cursor:pointer}
#cropCancel{background:#16140f;border:1px solid var(--red);color:var(--red)}
#cropApply{background:var(--gold);color:#1b1407}
@@ -693,6 +699,10 @@
<button class="shot-btn" id="frontBtn"><img class="shot-img" id="frontImg" alt="Front photo" hidden><span class="shot-num">1</span><span class="shot-ico">📷</span><b>Front</b><small>pattern face</small><i class="shot-ck" id="frontCk"></i></button>
<button class="shot-btn" id="backBtn"><img class="shot-img" id="backImg" alt="Back photo" hidden><span class="shot-num">2</span><span class="shot-ico">🏷</span><b>Back</b><small>label / SKU</small><i class="shot-ck" id="backCk"></i></button>
</div>
+ <div class="shot-acts">
+ <div><button class="adj" id="frontAdj" disabled>✎ Adjust</button><button id="frontRetake" disabled>↻ Retake</button></div>
+ <div><button class="adj" id="backAdj" disabled>✎ Adjust</button><button id="backRetake" disabled>↻ Retake</button></div>
+ </div>
<div class="shot-status" id="shotStatus"></div>
<!-- OCR fallback: when the mfr# can't be auto-determined, offer detected code candidates to tap -->
<div class="cand-pick" id="candPick" hidden></div>
@@ -1279,11 +1289,11 @@ async function uploadPhoto(x,dataUrl){
/* ===================== Photo editor ===================== */
let ES=null, edRAF=0;
const WHEEL_R=64; // half of 128
-function openEditor(x,dataUrl){
+function openEditor(x,dataUrl,onUse){
const img=new Image();
img.onload=()=>{
const flat=document.createElement('canvas'); flat.width=img.width; flat.height=img.height;
- ES={x,img,flat,b:1,c:1,s:1,h:0,th:0,ts:0,rollMode:'off',curl:0.5};
+ ES={x,img,flat,b:1,c:1,s:1,h:0,th:0,ts:0,rollMode:'off',curl:0.5,onUse:onUse||null};
const cv=$('#edcanvas'); cv.width=img.width; cv.height=img.height;
$('#ed-sku').textContent=x.dw_sku||'';
// reset controls
@@ -1547,7 +1557,7 @@ $('#edUse').addEventListener('click',()=>{
let dataUrl;
if(ES.rollMode==='tube'||ES.rollMode==='sheet'){ edRedraw(); dataUrl=$('#edcanvas').toDataURL('image/jpeg',0.9); }
else { dataUrl=bakeAdjust(ES.img,ES.img.width,ES.img.height).toDataURL('image/jpeg',0.9); }
- closeEditor(); uploadPhoto(x,dataUrl);
+ const onUse=ES.onUse; closeEditor(); if(onUse) onUse(dataUrl); else uploadPhoto(x,dataUrl);
});
async function skip(x){await fetch('/api/skip',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dw_sku:x.dw_sku,skipped:true})});x.skipped=true;render();}
@@ -2142,7 +2152,9 @@ function renderShotTiles(){
$('#backBtn').classList.toggle('have',!!_backPhoto); $('#backBtn').classList.toggle('need',!!_frontPhoto && !_backPhoto);
// TK-12228: show the actual captured photo inside its tile (immediate preview of FRONT and BACK)
[['#frontImg',_frontPhoto],['#backImg',_backPhoto]].forEach(([id,src])=>{ const im=$(id); if(!im) return;
- if(src){ if(im.getAttribute('src')!==src) im.src=src; im.hidden=false; } else { im.removeAttribute('src'); im.hidden=true; } }); }
+ if(src){ if(im.getAttribute('src')!==src) im.src=src; im.hidden=false; } else { im.removeAttribute('src'); im.hidden=true; } });
+ $('#frontAdj').disabled=$('#frontRetake').disabled=!_frontPhoto;
+ $('#backAdj').disabled=$('#backRetake').disabled=!_backPhoto; }
// ── TK-12228: both-sides photo strip for the Preview / Created / Update result views + tap-to-enlarge ──
const SIDE_LBL={front:'Front · pattern',back:'Back · label',extra:'Extra',photo:'Photo'};
function escAttr(v){ return String(v==null?'':v).replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<'); }
@@ -2742,8 +2754,20 @@ $('#addVendor').addEventListener('change',onVendorPick);
$('#stickyVendor')&&$('#stickyVendor').addEventListener('change',onStickyPick);
// Two-photo capture wiring: dedicated FRONT (pattern) + BACK (label) tiles → the in-page live view.
// Order is enforced: the BACK tile refuses to open the camera until Photo 1 (front) exists.
-$('#frontBtn').addEventListener('click',()=>openTwoShotCam('front'));
-$('#backBtn').addEventListener('click',()=>{ if(!_frontPhoto){ toast('📷 Take Photo 1 (the FRONT) first'); return; } openTwoShotCam('back'); });
+// TK-12256: once a side has a photo, tapping it (or ✎ Adjust) opens the Enhance/Crop editor on that
+// shot; ↻ Retake reopens the camera. Adjusted pixels replace the shot locally — nothing uploads here.
+function adjustShot(side){ const src=side==='front'?_frontPhoto:_backPhoto; if(!src) return;
+ openEditor({dw_sku:side==='front'?'FRONT · pattern':'BACK · label'},src,url=>{
+ if(side==='front'){ _frontPhoto=url; _addPhoto=url; detectAndShowColor(); }
+ else { _backPhoto=url; if(_addPhoto===src) _addPhoto=url; }
+ renderShots(); saveCaptureState(); toast('✓ '+(side==='front'?'Front':'Back')+' photo adjusted'); }); }
+function retakeShot(side){ if(side==='back' && !_frontPhoto){ toast('📷 Take Photo 1 (the FRONT) first'); return; } openTwoShotCam(side); }
+$('#frontBtn').addEventListener('click',()=>{ if(_frontPhoto) adjustShot('front'); else openTwoShotCam('front'); });
+$('#backBtn').addEventListener('click',()=>{ if(_backPhoto){ adjustShot('back'); return; } if(!_frontPhoto){ toast('📷 Take Photo 1 (the FRONT) first'); return; } openTwoShotCam('back'); });
+$('#frontAdj').addEventListener('click',()=>adjustShot('front'));
+$('#backAdj').addEventListener('click',()=>adjustShot('back'));
+$('#frontRetake').addEventListener('click',()=>retakeShot('front'));
+$('#backRetake').addEventListener('click',()=>retakeShot('back'));
// frontInput/backInput remain as the NATIVE fallback (HTTP / getUserMedia-unavailable) — shotAdd applies WB there.
$('#frontInput').addEventListener('change',e=>{ const f=e.target.files[0]; e.target.value=''; shotAdd('front',f); });
$('#backInput').addEventListener('change',e=>{ const f=e.target.files[0]; e.target.value=''; shotAdd('back',f); });
← a2e8198 5x sweep 5: report — sweeps converge at cap, 3 app defects f
·
back to Dw Photo Capture
·
Editor fits iPhone width: narrow-screen layout (wheel above 7aecde5 →