feat(panel): one RESET on every tab, back to the app's defaults

RESET was look-relative: it only put the sliders back on the values the
active recipe ships with, so the look itself (a saved recipe, a film sim)
survived the tap. It now restores the state a clean start leaves — the
PROVIA startup look, every parameter neutral, frame off, the custom and
GPS watermarks back to their defaults — and it sits on every tab, pinned
outside the scrolling chip row so it can never scroll out of reach.

Mode and aspect ratio stay put: they frame the shot being worked on, not
the look.
This commit is contained in:
2026-09-10 22:18:43 +07:00
parent 02bc3aa4da
commit 7454ea1218
2 changed files with 56 additions and 53 deletions
+27 -3
View File
@@ -499,6 +499,32 @@ export default function App() {
setGpsWm({ ...DEFAULT_GPS_WM });
};
// RESET (panel chip, any tab): the state a default start would leave — the
// startup look, every parameter back to neutral, frame off and both
// watermarks back to their defaults. Same pieces as resetEditsToRecipe, but
// the look goes back to the app's own default rather than the recipe's.
const handleResetAll = () => {
const provia = FILM_SIMS.find((f) => f.id === 'sim-provia');
if (provia) {
applyRecipe(filmSimToRecipe(provia));
} else {
setSelectedRecipe(null);
setAdjustments({ ...DEFAULT_ADJUSTMENTS });
setSelectedFrame('none');
}
setCustomWm({ ...DEFAULT_CUSTOM_WM });
setCustomWmApplied({ ...DEFAULT_CUSTOM_WM });
setGpsWm({ ...DEFAULT_GPS_WM });
// The rest of the look's state, each back to the value a clean start ships
// with: the GPS stamp off, both watermark rotations upright, the library
// photo and its wall frame back to portrait. ponytail: mode + aspect stay
// put — they frame the shot you are working on, not the look.
setUseGeotag(false);
setWmRotation(0);
setPhotoRotation(0);
setWallLandscape(false);
};
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
@@ -1249,9 +1275,7 @@ export default function App() {
recipes={recipes}
currentRecipeId={selectedRecipe?.id || ''}
adjustments={adjustments}
lookDefaults={
(selectedRecipe && pristineRecipe(selectedRecipe)?.adjustments) || DEFAULT_ADJUSTMENTS
}
onResetAll={handleResetAll}
selectedFrame={selectedFrame}
cropRatio={cropRatio}
onUpdateCropRatio={setCropRatio}
+29 -50
View File
@@ -36,9 +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;
// RESET: the app's own defaults, from any tab — the startup look, every
// parameter, the frame and the watermarks.
onResetAll: () => void;
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),
@@ -166,7 +166,7 @@ export default function AdjustmentPanel({
recipes,
currentRecipeId,
adjustments,
lookDefaults,
onResetAll,
selectedFrame,
cropRatio,
onUpdateCropRatio,
@@ -250,17 +250,6 @@ 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'
@@ -317,19 +306,16 @@ export default function AdjustmentPanel({
})
);
// Every tab carries the same RESET. It is pinned OUTSIDE the scroll row on
// purpose: the rows keep their scroll offset across tabs, so a chip inside
// one can sit off-screen just when it is wanted.
const resetChip = (): ChipDef => ({
key: 'reset-params',
key: 'reset-all',
label: 'RESET',
active: false,
onPress: () => onUpdateAdjustments({ ...lookDefaults }),
onPress: onResetAll,
});
// 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 ? <View className="pl-2">{renderChip(resetChip())}</View> : null;
const choiceChips = (
keyPrefix: string,
options: { v: string; d: string }[],
@@ -549,28 +535,22 @@ export default function AdjustmentPanel({
// opens its AUTO/DR100/DR200/DR400 strip above (same as the chrome
// groups on WB).
return (
<View className="flex-row items-center">
{resetChipFixed()}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ flex: 1 }}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{paramChips(paramDefs.iq)}
{renderChip(groupChip('dr'))}
</ScrollView>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{paramChips(paramDefs.iq)}
{renderChip(groupChip('dr'))}
</ScrollView>
);
case 'wb':
return (
<View className="flex-row items-center">
{resetChipFixed()}
<View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ flex: 1 }}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 6 }}
>
{choiceChips(
@@ -595,17 +575,13 @@ export default function AdjustmentPanel({
case 'filters':
return (
<View className="flex-row items-center">
{resetChipFixed()}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ flex: 1 }}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8 }}
>
{paramChips(paramDefs.filters)}
</ScrollView>
</View>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{ alignItems: 'center', paddingHorizontal: 8, paddingVertical: 8 }}
>
{paramChips(paramDefs.filters)}
</ScrollView>
);
case 'frame':
@@ -798,7 +774,10 @@ export default function AdjustmentPanel({
/>
</View>
)}
{chipsRow}
<View className="flex-row items-center">
{renderChip(resetChip())}
<View className="flex-1">{chipsRow}</View>
</View>
<RecipeCreateModal
visible={createVisible || !!editingRecipe}
currentBase={currentBaseFilter}