From a6626744786c4e36a166ad3567a2fcb6ba2e1824 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Sat, 12 Sep 2026 08:51:43 +0700 Subject: [PATCH] Show the pre-edit values while the finger holds, and pass the FRAME tab flag Pressing and holding on the picture with the LIGHT/WB/FX panel open now rewinds the last edit, and the panel rewinds with it: the chip numbers and the open slider read the same pre-edit snapshot, so before and after can be compared by eye and not just by looking at the picture. Only the displayed values move -- the stored adjustments, RECIPE and the export keep the real ones, and a gap longer than PEEK_GAP_MS starts a new snapshot for the next gesture. Also pass frameTabActive, without which the FRAME preview zoom added in Viewfinder.tsx could never turn on. --- App.tsx | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/App.tsx b/App.tsx index f44ed40..c17f0cb 100644 --- a/App.tsx +++ b/App.tsx @@ -578,6 +578,30 @@ export default function App() { cropRatio !== 'none' || differs(cropRect, DEFAULT_CROP_RECT); + // ---- Hold-to-compare ----------------------------------------------------- + // While a LIGHT/WB/FX panel is open, pressing and holding on the picture + // shows it with the last edit undone, so the user can flick between before + // and after. The picture and the parameter display both follow the peek; + // the stored values, RECIPE and the export never move. + const PEEK_GAP_MS = 700; // a longer pause = a new gesture, so re-snapshot + const peekRef = useRef<{ before: ColorAdjustments | null; at: number }>({ + before: null, + at: 0, + }); + const [peekAdjustments, setPeekAdjustments] = useState(null); + const peekArmed = openTab === 'iq' || openTab === 'wb' || openTab === 'filters'; + const handleComparePeek = useCallback((on: boolean) => { + setPeekAdjustments(on ? peekRef.current.before : null); + }, []); + // The values the panel and the picture SHOW. While the finger is down they + // fall back to the pre-edit snapshot, so the chip numbers and the open slider + // travel back with the picture and the before/after is readable, not just + // visible. The real values stay untouched: writes, RECIPE and the on-image + // drag baseline all keep reading `adjustments`. + const shownAdjustments = peekArmed && peekAdjustments ? peekAdjustments : adjustments; + const withPeek = (r: Recipe): Recipe => + shownAdjustments === r.adjustments ? r : { ...r, adjustments: shownAdjustments }; + const handleUpdateAdjustments = (updates: Partial) => { // Storm guard, generic across every param: same-value writes (slider // micro-jitter / repeated pointer-move events) still build a fresh @@ -586,6 +610,15 @@ export default function App() { // skipped identical EV needs no re-send. const keys = Object.keys(updates) as (keyof ColorAdjustments)[]; if (keys.every((k) => updates[k] === adjustments[k])) return; + // Remember where THIS edit started. A pause longer than PEEK_GAP_MS counts + // as a new gesture, so the snapshot is retaken; within one drag the earlier + // value survives and the peek shows the drag's own starting point. + // ponytail: a >700ms stall in the middle of one drag re-snapshots mid-way. + const peekNow = Date.now(); + if (peekNow - peekRef.current.at > PEEK_GAP_MS) { + peekRef.current.before = adjustments; + } + peekRef.current.at = peekNow; // B8: never call a setState inside another setState's updater. const next = { ...adjustments, ...updates }; setAdjustments(next); @@ -1300,7 +1333,7 @@ export default function App() { ref={viewfinderRef} mode={mode} facing={cameraFacing} - recipe={ + recipe={withPeek( selectedRecipe || { id: 'temp', name: 'Custom', @@ -1309,7 +1342,7 @@ export default function App() { frameId: selectedFrame, useGeotag, } - } + )} selectedFrame={selectedFrame} useGeotag={useGeotag} gpsInfo={gpsInfo} @@ -1331,6 +1364,7 @@ export default function App() { onExposureChange={(ev) => handleUpdateAdjustments({ exposureCompensation: ev })} meteringAdjustments={meteringAdj} imageAdjustTarget={imageAdjustTarget} + onComparePeek={peekArmed ? handleComparePeek : undefined} customWm={customWm} wmRotation={wmRotation} gpsWm={gpsWm} @@ -1340,6 +1374,7 @@ export default function App() { // The mark only captures image touches while its panel is open, so a // watermark left ON cannot block tap-to-focus or pinch/zoom. wmEditing={openTab === 'frame' && wmPanelOpen} + frameTabActive={openTab === 'frame'} onWmPositionChange={(x, y) => setCustomWm((p) => ({ ...p, x, y }))} onWmEdit={() => { // Double-tap on the mark re-opens its editor (and with it the @@ -1381,7 +1416,7 @@ export default function App() { activeTab={openTab} recipes={recipes} currentRecipeId={selectedRecipe?.id || ''} - adjustments={adjustments} + adjustments={shownAdjustments} onResetAll={handleResetAll} resetDirty={resetDirty} selectedFrame={selectedFrame}