Skip to content

Commit 3c0a41f

Browse files
committed
add merge & replace support to presets
1 parent 35f6e83 commit 3c0a41f

3 files changed

Lines changed: 70 additions & 22 deletions

File tree

src/components/modals/AddPresetModal.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import Switch from '../ui/Switch';
66
interface PresetModalProps {
77
isOpen: boolean;
88
onClose(): void;
9-
onSave(name: string, includeMasks: boolean, includeCropTransform: boolean): void;
9+
onSave(name: string, includeMasks: boolean, includeCropTransform: boolean, isAdditive: boolean): void;
1010
}
1111

1212
export default function AddPresetModal({ isOpen, onClose, onSave }: PresetModalProps) {
1313
const [name, setName] = useState('');
1414
const [includeMasks, setIncludeMasks] = useState(false);
1515
const [includeCropTransform, setIncludeCropTransform] = useState(false);
16+
const [isAdditive, setIsAdditive] = useState(false);
1617
const [isMounted, setIsMounted] = useState(false);
1718
const [show, setShow] = useState(false);
1819

@@ -28,17 +29,18 @@ export default function AddPresetModal({ isOpen, onClose, onSave }: PresetModalP
2829
setName('');
2930
setIncludeMasks(false);
3031
setIncludeCropTransform(false);
32+
setIsAdditive(false);
3133
}, 300);
3234
return () => clearTimeout(timer);
3335
}
3436
}, [isOpen]);
3537

3638
const handleSave = useCallback(() => {
3739
if (name.trim()) {
38-
onSave(name.trim(), includeMasks, includeCropTransform);
40+
onSave(name.trim(), includeMasks, includeCropTransform, isAdditive);
3941
onClose();
4042
}
41-
}, [name, includeMasks, includeCropTransform, onSave, onClose]);
43+
}, [name, includeMasks, includeCropTransform, isAdditive, onSave, onClose]);
4244

4345
const handleKeyDown = useCallback(
4446
(e: any) => {
@@ -91,6 +93,12 @@ export default function AddPresetModal({ isOpen, onClose, onSave }: PresetModalP
9193
<div className="mt-5 space-y-4 p-1">
9294
<Switch label="Include Masks" checked={includeMasks} onChange={setIncludeMasks} />
9395
<Switch label="Include Crop & Transform" checked={includeCropTransform} onChange={setIncludeCropTransform} />
96+
<Switch
97+
label="Merge Changes"
98+
data-tooltip="Only save changed adjustments"
99+
checked={isAdditive}
100+
onChange={setIsAdditive}
101+
/>
94102
</div>
95103

96104
<div className="flex justify-end gap-3 mt-6">

src/components/panel/right/PresetsPanel.tsx

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,23 +102,40 @@ function PresetItemDisplay({ preset, previewUrl, isGeneratingPreviews }: PresetI
102102

103103
const supportsMasks = preset.includeMasks ?? (preset.adjustments?.masks && preset.adjustments.masks.length > 0);
104104
const supportsGeometry =
105-
preset.includeCropTransform ?? geometryKeys.some((key) => preset.adjustments?.[key] !== undefined);
105+
preset.includeCropTransform ??
106+
geometryKeys.some((key) => geometryKeys.some((key) => preset.adjustments?.[key] !== undefined));
107+
108+
const tooltipContent = useMemo(() => {
109+
const features = [];
110+
if (supportsMasks) features.push('Masks');
111+
if (supportsGeometry) features.push('Crop & Transform');
112+
113+
if (features.length === 0) return undefined;
114+
return `Supports ${features.join(' + ')}`;
115+
}, [supportsMasks, supportsGeometry]);
106116

107117
return (
108118
<div className="flex items-center gap-2 p-2 rounded-lg bg-surface cursor-grabbing">
109-
<div className="w-20 h-14 bg-bg-tertiary rounded-md flex items-center justify-center shrink-0 relative overflow-hidden">
119+
<div
120+
className="w-20 h-14 bg-bg-tertiary rounded-md flex items-center justify-center shrink-0 relative overflow-hidden"
121+
data-tooltip={tooltipContent}
122+
>
110123
{isGeneratingPreviews && !previewUrl ? (
111124
<Loader2 size={20} className="animate-spin text-text-secondary" />
112125
) : previewUrl ? (
113-
<img src={previewUrl} alt={`${preset.name} preview`} className="w-full h-full object-cover rounded-md" />
126+
<img
127+
src={previewUrl}
128+
alt={`${preset.name} preview`}
129+
className="w-full h-full object-cover rounded-md pointer-events-none"
130+
/>
114131
) : (
115132
<Loader2 size={20} className="animate-spin text-text-secondary" />
116133
)}
117134

118135
{(supportsMasks || supportsGeometry) && (
119-
<div className="absolute top-1 right-1 bg-primary rounded-full px-1.5 py-0.5 flex items-center gap-1.5 backdrop-blur-xs shadow-xs z-10">
120-
{supportsMasks && <Layers size={11} className="text-white" data-tooltip="Supports Masks" />}
121-
{supportsGeometry && <Scaling size={11} className="text-white" data-tooltip="Supports Crop & Transform" />}
136+
<div className="absolute top-1 right-1 bg-primary rounded-full px-1.5 py-0.5 flex items-center gap-1.5 backdrop-blur-xs shadow-xs z-10 pointer-events-none">
137+
{supportsMasks && <Layers size={11} className="text-white" />}
138+
{supportsGeometry && <Scaling size={11} className="text-white" />}
122139
</div>
123140
)}
124141
</div>
@@ -572,8 +589,9 @@ export default function PresetsPanel({
572589
name: string,
573590
includeMasks: boolean,
574591
includeCropTransform: boolean,
592+
isAdditive: boolean,
575593
) => {
576-
const newPreset = addPreset(name, null, includeMasks, includeCropTransform);
594+
const newPreset = addPreset(name, null, includeMasks, includeCropTransform, isAdditive);
577595
setIsAddModalOpen(false);
578596
if (newPreset) {
579597
await generateSinglePreview(newPreset);
@@ -777,14 +795,27 @@ export default function PresetsPanel({
777795
options = [
778796
{
779797
icon: RefreshCw,
780-
label: 'Overwrite Preset',
781-
782-
onClick: async () => {
783-
const updated = updatePreset(data?.id ?? null);
784-
if (updated) {
785-
await generateSinglePreview(updated);
786-
}
787-
},
798+
label: 'Update from Current',
799+
submenu: [
800+
{
801+
label: 'Merge',
802+
onClick: async () => {
803+
const updated = updatePreset(data?.id ?? null, 'merge');
804+
if (updated) {
805+
await generateSinglePreview(updated);
806+
}
807+
},
808+
},
809+
{
810+
label: 'Replace',
811+
onClick: async () => {
812+
const updated = updatePreset(data?.id ?? null, 'replace');
813+
if (updated) {
814+
await generateSinglePreview(updated);
815+
}
816+
},
817+
},
818+
],
788819
},
789820
{ type: OPTION_SEPARATOR },
790821
{

src/hooks/usePresets.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export function usePresets(currentAdjustments: Adjustments) {
6060
folderId: string | null = null,
6161
includeMasks: boolean = false,
6262
includeCropTransform: boolean = false,
63+
isAdditive: boolean = false,
6364
) => {
6465
const GEOMETRY_KEYS = ADJUSTMENT_GROUPS.geometry.flatMap((group) => group.keys);
6566
const MASK_KEYS = ADJUSTMENT_GROUPS.masks.flatMap((group) => group.keys);
@@ -74,7 +75,11 @@ export function usePresets(currentAdjustments: Adjustments) {
7475
const currentValue = currentAdjustments[key as keyof Adjustments];
7576
const defaultValue = INITIAL_ADJUSTMENTS[key as keyof Adjustments];
7677

77-
if (JSON.stringify(currentValue) !== JSON.stringify(defaultValue)) {
78+
if (isAdditive) {
79+
if (JSON.stringify(currentValue) !== JSON.stringify(defaultValue)) {
80+
presetAdjustments[key] = currentValue;
81+
}
82+
} else {
7883
presetAdjustments[key] = currentValue;
7984
}
8085
}
@@ -174,7 +179,7 @@ export function usePresets(currentAdjustments: Adjustments) {
174179
savePresetsToBackend(updatedPresets);
175180
};
176181

177-
const updatePreset = (id: string | null) => {
182+
const updatePreset = (id: string | null, updateMode: 'merge' | 'replace') => {
178183
let existingPreset: Preset | null = null;
179184

180185
for (const item of presets) {
@@ -213,9 +218,13 @@ export function usePresets(currentAdjustments: Adjustments) {
213218

214219
if (Object.prototype.hasOwnProperty.call(currentAdjustments, key)) {
215220
const currentValue = currentAdjustments[key as keyof Adjustments];
216-
const defaultValue = INITIAL_ADJUSTMENTS[key as keyof Adjustments];
217221

218-
if (JSON.stringify(currentValue) !== JSON.stringify(defaultValue)) {
222+
if (updateMode === 'merge') {
223+
const defaultValue = INITIAL_ADJUSTMENTS[key as keyof Adjustments];
224+
if (JSON.stringify(currentValue) !== JSON.stringify(defaultValue)) {
225+
presetAdjustments[key] = currentValue;
226+
}
227+
} else {
219228
presetAdjustments[key] = currentValue;
220229
}
221230
}

0 commit comments

Comments
 (0)