From 02bc3aa4da6b1bbe1748773b2f7cb82c508aceac Mon Sep 17 00:00:00 2001 From: locphamtran Date: Thu, 10 Sep 2026 22:06:36 +0700 Subject: [PATCH] feat(panel): editor keeps the values when the sim changes; one-tap RESET back to the look --- App.tsx | 3 ++ src/components/AdjustmentPanel.tsx | 70 +++++++++++++++++++++------- src/components/RecipeCreateModal.tsx | 5 +- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/App.tsx b/App.tsx index ec330d7..dfd166f 100644 --- a/App.tsx +++ b/App.tsx @@ -1249,6 +1249,9 @@ export default function App() { recipes={recipes} currentRecipeId={selectedRecipe?.id || ''} adjustments={adjustments} + lookDefaults={ + (selectedRecipe && pristineRecipe(selectedRecipe)?.adjustments) || DEFAULT_ADJUSTMENTS + } selectedFrame={selectedFrame} cropRatio={cropRatio} onUpdateCropRatio={setCropRatio} diff --git a/src/components/AdjustmentPanel.tsx b/src/components/AdjustmentPanel.tsx index 058867c..d3554dc 100644 --- a/src/components/AdjustmentPanel.tsx +++ b/src/components/AdjustmentPanel.tsx @@ -36,6 +36,9 @@ interface AdjustmentPanelProps { recipes: Recipe[]; currentRecipeId: string; adjustments: ColorAdjustments; + // The values the active look ships with — a saved recipe's own set, a film + // sim's canonical set, or the neutral defaults. RESET puts them back. + lookDefaults: ColorAdjustments; selectedFrame: FrameId; // FRAME tab CROP. Only meaningful on the library's plain frames (the camera // composes through its ratio band, polaroid/wall through their own window), @@ -163,6 +166,7 @@ export default function AdjustmentPanel({ recipes, currentRecipeId, adjustments, + lookDefaults, selectedFrame, cropRatio, onUpdateCropRatio, @@ -246,6 +250,17 @@ export default function AdjustmentPanel({ const a = adjustments; + // RESET (parameter tabs): every number of the active look back on the value + // it ships with. Only the parameters count as "changed" — the groups below + // are part of the same look, so they light it too. + const lookDirty = + [...PARAM_DEFS.iq, ...PARAM_DEFS.wb, ...PARAM_DEFS.filters].some( + (d) => d.get(a) !== d.get(lookDefaults) + ) || + (a.colorChrome || 'none') !== (lookDefaults.colorChrome || 'none') || + (a.colorChromeBlue || 'none') !== (lookDefaults.colorChromeBlue || 'none') || + String(a.dynamicRange ?? 'auto') !== String(lookDefaults.dynamicRange ?? 'auto'); + const renderChip = (c: ChipDef) => { const tone = c.active ? 'border-amber-500/70 bg-amber-500/15' @@ -302,6 +317,19 @@ export default function AdjustmentPanel({ }) ); + const resetChip = (): ChipDef => ({ + key: 'reset-params', + label: 'RESET', + active: false, + onPress: () => onUpdateAdjustments({ ...lookDefaults }), + }); + + // Pinned OUTSIDE the scroll row: the rows keep their scroll offset across + // tabs, so a RESET sitting inside one can be scrolled off just when it is + // wanted — after a tweak halfway along the strip. + const resetChipFixed = () => + lookDirty ? {renderChip(resetChip())} : null; + const choiceChips = ( keyPrefix: string, options: { v: string; d: string }[], @@ -521,22 +549,28 @@ export default function AdjustmentPanel({ // opens its AUTO/DR100/DR200/DR400 strip above (same as the chrome // groups on WB). return ( - - {paramChips(paramDefs.iq)} - {renderChip(groupChip('dr'))} - + + {resetChipFixed()} + + {paramChips(paramDefs.iq)} + {renderChip(groupChip('dr'))} + + ); case 'wb': return ( - + + {resetChipFixed()} {choiceChips( @@ -561,13 +595,17 @@ export default function AdjustmentPanel({ case 'filters': return ( - - {paramChips(paramDefs.filters)} - + + {resetChipFixed()} + + {paramChips(paramDefs.filters)} + + ); case 'frame': diff --git a/src/components/RecipeCreateModal.tsx b/src/components/RecipeCreateModal.tsx index 9b69a07..d10770c 100644 --- a/src/components/RecipeCreateModal.tsx +++ b/src/components/RecipeCreateModal.tsx @@ -228,9 +228,12 @@ export default function RecipeCreateModal({ setIso(null); }, [visible, currentBase, initial]); - // Switching the simulation reloads its standard values into the form. + // Switching the simulation reloads its standard values into the form — not + // in the editor though: there the numbers on screen are the user's own, and + // swapping the base look under them must not throw them away. const selectSim = (b: BaseFilter) => { setSim(b); + if (initial) return; const v = valuesOf(b); setDr(v.dr); setGrain(v.grain);