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.
This commit is contained in:
@@ -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<ColorAdjustments | null>(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<ColorAdjustments>) => {
|
||||
// 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}
|
||||
|
||||
Reference in New Issue
Block a user