@yogiswara/honcho-editor-ui 2.2.7 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -10,8 +10,9 @@ export interface Controller {
|
|
|
10
10
|
syncConfig(firebaseUid: string): Promise<void>;
|
|
11
11
|
handleBack(firebaseUid: string, imageID: string): void;
|
|
12
12
|
getPresets(firebaseUid: string): Promise<Preset[]>;
|
|
13
|
-
createPreset(firebaseUid: string, name: string, settings: AdjustmentState): Promise<
|
|
13
|
+
createPreset(firebaseUid: string, name: string, settings: AdjustmentState): Promise<void>;
|
|
14
14
|
deletePreset(firebaseUid: string, presetId: string): Promise<void>;
|
|
15
|
+
updatePreset(firebaseUid: string, data: Preset): Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
export type AdjustmentState = {
|
|
17
18
|
tempScore: number;
|
|
@@ -30,6 +31,19 @@ export type AdjustmentState = {
|
|
|
30
31
|
export type Preset = {
|
|
31
32
|
id: string;
|
|
32
33
|
name: string;
|
|
34
|
+
is_default: boolean;
|
|
35
|
+
temperature: number;
|
|
36
|
+
tint: number;
|
|
37
|
+
saturation: number;
|
|
38
|
+
vibrance: number;
|
|
39
|
+
exposure: number;
|
|
40
|
+
contrast: number;
|
|
41
|
+
highlights: number;
|
|
42
|
+
shadows: number;
|
|
43
|
+
whites: number;
|
|
44
|
+
blacks: number;
|
|
45
|
+
clarity: number;
|
|
46
|
+
sharpness: number;
|
|
33
47
|
};
|
|
34
48
|
export type ImageItem = {
|
|
35
49
|
id: string;
|
|
@@ -170,14 +184,11 @@ export declare function useHonchoEditor(controller: Controller, initImageId: str
|
|
|
170
184
|
handleSelectDesktopPreset: (presetId: string) => void;
|
|
171
185
|
handlePresetMenuClick: (event: React.MouseEvent<HTMLElement>, presetId: string) => void;
|
|
172
186
|
handlePresetMenuClose: () => void;
|
|
173
|
-
handleCreatePreset: () => Promise<void>;
|
|
174
187
|
handleRemovePreset: () => void;
|
|
175
|
-
handleDeletePreset: () => Promise<void>;
|
|
176
188
|
handleOpenPresetModal: () => void;
|
|
177
189
|
handleClosePresetModal: () => void;
|
|
178
190
|
handleOpenPresetModalMobile: () => void;
|
|
179
191
|
handleClosePresetModalMobile: () => void;
|
|
180
|
-
handleCreatePresetMobile: () => Promise<void>;
|
|
181
192
|
setPresetName: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
182
193
|
handleNameChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
183
194
|
isRenameModalOpen: boolean;
|
|
@@ -22,6 +22,7 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
|
|
|
22
22
|
const [isEditorReady, setIsEditorReady] = useState(false);
|
|
23
23
|
const [isImageLoaded, setIsImageLoaded] = useState(false);
|
|
24
24
|
const [zoomLevel, setZoomLevel] = useState(1);
|
|
25
|
+
const [presets, setPresets] = useState([]);
|
|
25
26
|
// MARK: - Adjustment & History State
|
|
26
27
|
const [copiedAdjustments, setCopiedAdjustments] = useState(null);
|
|
27
28
|
const [copyColorChecks, setCopyColorChecks] = useState({ temperature: true, tint: true, vibrance: true, saturation: true });
|
|
@@ -46,7 +47,6 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
|
|
|
46
47
|
// Preset State
|
|
47
48
|
const [isPresetModalOpen, setPresetModalOpen] = useState(false);
|
|
48
49
|
const [isPresetModalOpenMobile, setPresetModalOpenMobile] = useState(false);
|
|
49
|
-
const [presets, setPresets] = useState([]);
|
|
50
50
|
const [presetName, setPresetName] = useState("Type Here");
|
|
51
51
|
const [isPresetCreated, setIsPresetCreated] = useState(false);
|
|
52
52
|
const [selectedMobilePreset, setSelectedMobilePreset] = useState('preset1');
|
|
@@ -232,66 +232,11 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
|
|
|
232
232
|
};
|
|
233
233
|
const handlePresetMenuClose = () => { setPresetMenuAnchorEl(null); setActivePresetMenuId(null); };
|
|
234
234
|
const handleRemovePreset = () => { console.log(`Remove: ${activePresetMenuId}`); handlePresetMenuClose(); };
|
|
235
|
-
const handleDeletePreset = useCallback(async () => {
|
|
236
|
-
if (!controller || !activePresetMenuId)
|
|
237
|
-
return;
|
|
238
|
-
try {
|
|
239
|
-
await controller.deletePreset(firebaseUid, activePresetMenuId);
|
|
240
|
-
// On success, remove the preset from local state
|
|
241
|
-
setPresets(prevPresets => prevPresets.filter(p => p.id !== activePresetMenuId));
|
|
242
|
-
}
|
|
243
|
-
catch (error) {
|
|
244
|
-
console.error("Failed to delete preset:", error);
|
|
245
|
-
}
|
|
246
|
-
handlePresetMenuClose(); // Close the options menu
|
|
247
|
-
}, [controller, activePresetMenuId]);
|
|
248
235
|
// Preset Modal Handlers
|
|
249
236
|
const handleOpenPresetModal = () => { setIsPresetCreated(false); setPresetModalOpen(true); };
|
|
250
237
|
const handleClosePresetModal = () => setPresetModalOpen(false);
|
|
251
|
-
const handleCreatePreset = useCallback(async () => {
|
|
252
|
-
if (!controller)
|
|
253
|
-
return;
|
|
254
|
-
// The current adjustment state from the history hook
|
|
255
|
-
const currentAdjustments = { ...currentAdjustmentsState };
|
|
256
|
-
try {
|
|
257
|
-
// Call the controller, which now calls the real API
|
|
258
|
-
const newPresetFromApi = await controller.createPreset(firebaseUid, presetName, currentAdjustments);
|
|
259
|
-
// If the API call was successful and returned a preset...
|
|
260
|
-
if (newPresetFromApi) {
|
|
261
|
-
// ...add the new preset from the API response to our local state
|
|
262
|
-
setPresets(prevPresets => [...prevPresets, newPresetFromApi]);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
console.error("Failed to create preset:", error);
|
|
267
|
-
// Optionally: show an error message to the user here
|
|
268
|
-
}
|
|
269
|
-
// Close the modal and show a confirmation
|
|
270
|
-
setIsPresetCreated(true);
|
|
271
|
-
handleClosePresetModal();
|
|
272
|
-
setTimeout(() => setIsPresetCreated(false), 1000); // Hide confirmation after 1s
|
|
273
|
-
}, [controller, presetName, currentAdjustmentsState, firebaseUid]);
|
|
274
238
|
const handleOpenPresetModalMobile = () => { setIsPresetCreated(false); setPresetModalOpenMobile(true); };
|
|
275
239
|
const handleClosePresetModalMobile = () => setPresetModalOpenMobile(false);
|
|
276
|
-
const handleCreatePresetMobile = useCallback(async () => {
|
|
277
|
-
if (!controller)
|
|
278
|
-
return;
|
|
279
|
-
console.log("Creating mobile preset:", presetName);
|
|
280
|
-
const currentAdjustments = { ...currentAdjustmentsState };
|
|
281
|
-
try {
|
|
282
|
-
// RE-USE THE SAME LOGIC AS THE DESKTOP VERSION
|
|
283
|
-
const newPresetFromApi = await controller.createPreset(firebaseUid, presetName, currentAdjustments);
|
|
284
|
-
if (newPresetFromApi) {
|
|
285
|
-
setPresets(prevPresets => [...prevPresets, newPresetFromApi]);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
catch (error) {
|
|
289
|
-
console.error("Failed to create mobile preset:", error);
|
|
290
|
-
}
|
|
291
|
-
setIsPresetCreated(true);
|
|
292
|
-
handleClosePresetModalMobile();
|
|
293
|
-
setTimeout(() => setIsPresetCreated(false), 1000);
|
|
294
|
-
}, [controller, presetName, currentAdjustmentsState, firebaseUid]);
|
|
295
240
|
const handleNameChange = (event) => setPresetName(event.target.value);
|
|
296
241
|
// Watermark Handlers
|
|
297
242
|
const handleOpenWatermarkView = () => setIsCreatingWatermark(true);
|
|
@@ -697,14 +642,11 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
|
|
|
697
642
|
handleSelectDesktopPreset,
|
|
698
643
|
handlePresetMenuClick,
|
|
699
644
|
handlePresetMenuClose,
|
|
700
|
-
handleCreatePreset,
|
|
701
645
|
handleRemovePreset,
|
|
702
|
-
handleDeletePreset,
|
|
703
646
|
handleOpenPresetModal,
|
|
704
647
|
handleClosePresetModal,
|
|
705
648
|
handleOpenPresetModalMobile,
|
|
706
649
|
handleClosePresetModalMobile,
|
|
707
|
-
handleCreatePresetMobile,
|
|
708
650
|
setPresetName,
|
|
709
651
|
handleNameChange,
|
|
710
652
|
isRenameModalOpen,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Controller, Preset, AdjustmentState } from './editor/useHonchoEditor';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the preset hook
|
|
4
|
+
*/
|
|
5
|
+
export interface PresetOptions {
|
|
6
|
+
/** Enable development warnings for debugging */
|
|
7
|
+
devWarnings?: boolean;
|
|
8
|
+
/** Auto-load presets on hook initialization */
|
|
9
|
+
autoLoad?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Information about the current preset state
|
|
13
|
+
*/
|
|
14
|
+
export interface PresetInfo {
|
|
15
|
+
/** Whether presets are currently being loaded */
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
/** Error message if any operation failed */
|
|
18
|
+
error: string | null;
|
|
19
|
+
/** Number of presets currently managed */
|
|
20
|
+
count: number;
|
|
21
|
+
/** Whether the hook has been initialized */
|
|
22
|
+
isInitialized: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Actions available for preset management
|
|
26
|
+
*/
|
|
27
|
+
export interface PresetActions {
|
|
28
|
+
/** Create a new preset */
|
|
29
|
+
create: (name: string, settings: AdjustmentState) => Promise<Preset | null>;
|
|
30
|
+
/** Rename an existing preset */
|
|
31
|
+
rename: (presetId: string, newName: string) => Promise<boolean>;
|
|
32
|
+
/** Delete a preset */
|
|
33
|
+
delete: (presetId: string) => Promise<boolean>;
|
|
34
|
+
/** Load/refresh presets from backend */
|
|
35
|
+
load: () => Promise<void>;
|
|
36
|
+
/** Find preset that matches the given adjustment state */
|
|
37
|
+
findByAdjustments: (adjustments: AdjustmentState) => Preset | null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Return type for the usePreset hook
|
|
41
|
+
*/
|
|
42
|
+
export interface UsePresetReturn {
|
|
43
|
+
/** Current list of presets */
|
|
44
|
+
presets: Preset[];
|
|
45
|
+
/** Information about preset state */
|
|
46
|
+
info: PresetInfo;
|
|
47
|
+
/** Available preset actions */
|
|
48
|
+
actions: PresetActions;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Hook for managing presets with backend communication through Controller.
|
|
52
|
+
*
|
|
53
|
+
* **Key Features:**
|
|
54
|
+
* - **Backend Communication**: All operations go through the provided Controller
|
|
55
|
+
* - **Local State Management**: Maintains local preset list for UI performance
|
|
56
|
+
* - **CRUD Operations**: Create, rename, delete, and list presets
|
|
57
|
+
* - **Error Handling**: Provides error states for failed operations
|
|
58
|
+
* - **Auto-loading**: Optional automatic preset loading on initialization
|
|
59
|
+
*
|
|
60
|
+
* **Typical Usage:**
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const { presets, actions, info } = usePreset(controller, firebaseUid, { autoLoad: true });
|
|
63
|
+
*
|
|
64
|
+
* // Create preset
|
|
65
|
+
* const newPreset = await actions.create('My Preset', adjustmentState);
|
|
66
|
+
*
|
|
67
|
+
* // Rename preset
|
|
68
|
+
* const success = await actions.rename(presetId, 'New Name');
|
|
69
|
+
*
|
|
70
|
+
* // Delete preset
|
|
71
|
+
* const deleted = await actions.delete(presetId);
|
|
72
|
+
*
|
|
73
|
+
* // Manual reload
|
|
74
|
+
* await actions.load();
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @param controller - Backend controller for API communication
|
|
78
|
+
* @param firebaseUid - User identifier for backend operations
|
|
79
|
+
* @param options - Configuration options
|
|
80
|
+
* @returns Object with presets, info, and actions
|
|
81
|
+
*/
|
|
82
|
+
export declare function usePreset(controller: Controller | null, firebaseUid: string, options?: PresetOptions): UsePresetReturn;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Hook for managing presets with backend communication through Controller.
|
|
4
|
+
*
|
|
5
|
+
* **Key Features:**
|
|
6
|
+
* - **Backend Communication**: All operations go through the provided Controller
|
|
7
|
+
* - **Local State Management**: Maintains local preset list for UI performance
|
|
8
|
+
* - **CRUD Operations**: Create, rename, delete, and list presets
|
|
9
|
+
* - **Error Handling**: Provides error states for failed operations
|
|
10
|
+
* - **Auto-loading**: Optional automatic preset loading on initialization
|
|
11
|
+
*
|
|
12
|
+
* **Typical Usage:**
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const { presets, actions, info } = usePreset(controller, firebaseUid, { autoLoad: true });
|
|
15
|
+
*
|
|
16
|
+
* // Create preset
|
|
17
|
+
* const newPreset = await actions.create('My Preset', adjustmentState);
|
|
18
|
+
*
|
|
19
|
+
* // Rename preset
|
|
20
|
+
* const success = await actions.rename(presetId, 'New Name');
|
|
21
|
+
*
|
|
22
|
+
* // Delete preset
|
|
23
|
+
* const deleted = await actions.delete(presetId);
|
|
24
|
+
*
|
|
25
|
+
* // Manual reload
|
|
26
|
+
* await actions.load();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @param controller - Backend controller for API communication
|
|
30
|
+
* @param firebaseUid - User identifier for backend operations
|
|
31
|
+
* @param options - Configuration options
|
|
32
|
+
* @returns Object with presets, info, and actions
|
|
33
|
+
*/
|
|
34
|
+
export function usePreset(controller, firebaseUid, options = {}) {
|
|
35
|
+
// Memoize options to prevent re-renders when object is recreated with same values
|
|
36
|
+
const memoizedOptions = useMemo(() => ({
|
|
37
|
+
devWarnings: options.devWarnings ?? false,
|
|
38
|
+
autoLoad: options.autoLoad ?? false
|
|
39
|
+
}), [options.devWarnings, options.autoLoad]);
|
|
40
|
+
// Core state
|
|
41
|
+
const [presets, setPresets] = useState([]);
|
|
42
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
43
|
+
const [error, setError] = useState(null);
|
|
44
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
45
|
+
// Track controller and firebaseUid changes with stable refs
|
|
46
|
+
const controllerRef = useRef(controller);
|
|
47
|
+
const firebaseUidRef = useRef(firebaseUid);
|
|
48
|
+
// Only update refs when values actually change
|
|
49
|
+
if (controllerRef.current !== controller) {
|
|
50
|
+
controllerRef.current = controller;
|
|
51
|
+
}
|
|
52
|
+
if (firebaseUidRef.current !== firebaseUid) {
|
|
53
|
+
firebaseUidRef.current = firebaseUid;
|
|
54
|
+
}
|
|
55
|
+
// Helper function to log debug messages - memoized to prevent re-renders
|
|
56
|
+
const debugLog = useCallback((message, data) => {
|
|
57
|
+
if (memoizedOptions.devWarnings) {
|
|
58
|
+
console.log(`[usePreset] ${message}`, data || '');
|
|
59
|
+
}
|
|
60
|
+
}, [memoizedOptions.devWarnings]);
|
|
61
|
+
// Helper function to handle errors
|
|
62
|
+
const handleError = useCallback((operation, error) => {
|
|
63
|
+
const errorMessage = `Failed to ${operation}: ${error?.message || error}`;
|
|
64
|
+
setError(errorMessage);
|
|
65
|
+
debugLog(`Error in ${operation}`, error);
|
|
66
|
+
return false;
|
|
67
|
+
}, [debugLog]);
|
|
68
|
+
// Load presets from backend
|
|
69
|
+
const load = useCallback(async () => {
|
|
70
|
+
if (!controllerRef.current || !firebaseUidRef.current) {
|
|
71
|
+
debugLog('Load skipped: missing controller or firebaseUid');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setIsLoading(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
try {
|
|
77
|
+
debugLog('Loading presets from backend...');
|
|
78
|
+
const loadedPresets = await controllerRef.current.getPresets(firebaseUidRef.current);
|
|
79
|
+
setPresets(loadedPresets);
|
|
80
|
+
setIsInitialized(true);
|
|
81
|
+
debugLog('Presets loaded successfully', { count: loadedPresets.length });
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
handleError('load presets', err);
|
|
85
|
+
setPresets([]); // Clear presets on error
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setIsLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}, [debugLog, handleError]);
|
|
91
|
+
// Fire-and-forget version of load for internal use
|
|
92
|
+
const loadInBackground = useCallback(() => {
|
|
93
|
+
if (!controllerRef.current || !firebaseUidRef.current) {
|
|
94
|
+
debugLog('Background load skipped: missing controller or firebaseUid');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
debugLog('Background loading presets...');
|
|
98
|
+
// Don't set loading state for background operations
|
|
99
|
+
controllerRef.current.getPresets(firebaseUidRef.current)
|
|
100
|
+
.then(loadedPresets => {
|
|
101
|
+
setPresets(loadedPresets);
|
|
102
|
+
if (!isInitialized) {
|
|
103
|
+
setIsInitialized(true);
|
|
104
|
+
}
|
|
105
|
+
debugLog('Background presets loaded successfully', { count: loadedPresets.length });
|
|
106
|
+
})
|
|
107
|
+
.catch(err => {
|
|
108
|
+
debugLog('Background load failed:', err);
|
|
109
|
+
// Don't set error state for background operations
|
|
110
|
+
});
|
|
111
|
+
}, [debugLog, isInitialized]);
|
|
112
|
+
// Create a new preset
|
|
113
|
+
const create = useCallback(async (name, settings) => {
|
|
114
|
+
if (!controllerRef.current || !firebaseUidRef.current) {
|
|
115
|
+
debugLog('Create skipped: missing controller or firebaseUid');
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!name.trim()) {
|
|
119
|
+
setError('Preset name cannot be empty');
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
// Check for duplicate names
|
|
123
|
+
if (presets.some(p => p.name.toLowerCase() === name.toLowerCase())) {
|
|
124
|
+
setError('A preset with this name already exists');
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
setIsLoading(true);
|
|
128
|
+
setError(null);
|
|
129
|
+
try {
|
|
130
|
+
debugLog('Creating preset...', { name, settings });
|
|
131
|
+
// Fire the create request but don't wait for preset data in response
|
|
132
|
+
await controllerRef.current.createPreset(firebaseUidRef.current, name, settings);
|
|
133
|
+
debugLog('Preset creation request completed');
|
|
134
|
+
// Fire and forget: Schedule a delayed refresh to get updated preset list
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
debugLog('Refreshing presets after create (fire and forget)');
|
|
137
|
+
loadInBackground();
|
|
138
|
+
}, 500); // 500ms delay to allow backend processing
|
|
139
|
+
// Return a minimal success indicator since we don't have the actual preset data
|
|
140
|
+
return { id: 'pending', name, is_default: false };
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
handleError('create preset', err);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
setIsLoading(false);
|
|
148
|
+
}
|
|
149
|
+
}, [presets, debugLog, handleError, loadInBackground]);
|
|
150
|
+
// Rename an existing preset
|
|
151
|
+
const rename = useCallback(async (presetId, newName) => {
|
|
152
|
+
if (!controllerRef.current || !firebaseUidRef.current) {
|
|
153
|
+
debugLog('Rename skipped: missing controller or firebaseUid');
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (!newName.trim()) {
|
|
157
|
+
setError('Preset name cannot be empty');
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const existingPreset = presets.find(p => p.id === presetId);
|
|
161
|
+
if (!existingPreset) {
|
|
162
|
+
setError('Preset not found');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Check for duplicate names (excluding the current preset)
|
|
166
|
+
if (presets.some(p => p.id !== presetId && p.name.toLowerCase() === newName.toLowerCase())) {
|
|
167
|
+
setError('A preset with this name already exists');
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
setIsLoading(true);
|
|
171
|
+
setError(null);
|
|
172
|
+
try {
|
|
173
|
+
debugLog('Renaming preset...', { presetId, oldName: existingPreset.name, newName });
|
|
174
|
+
const updatedPreset = { ...existingPreset, name: newName };
|
|
175
|
+
await controllerRef.current.updatePreset(firebaseUidRef.current, updatedPreset);
|
|
176
|
+
// Update local state
|
|
177
|
+
setPresets(prev => prev.map(p => p.id === presetId ? updatedPreset : p));
|
|
178
|
+
debugLog('Preset renamed successfully');
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
handleError('rename preset', err);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setIsLoading(false);
|
|
187
|
+
}
|
|
188
|
+
}, [presets, debugLog, handleError]);
|
|
189
|
+
// Delete a preset
|
|
190
|
+
const deletePreset = useCallback(async (presetId) => {
|
|
191
|
+
if (!controllerRef.current || !firebaseUidRef.current) {
|
|
192
|
+
debugLog('Delete skipped: missing controller or firebaseUid');
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const existingPreset = presets.find(p => p.id === presetId);
|
|
196
|
+
if (!existingPreset) {
|
|
197
|
+
setError('Preset not found');
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
setIsLoading(true);
|
|
201
|
+
setError(null);
|
|
202
|
+
try {
|
|
203
|
+
debugLog('Deleting preset...', { presetId, name: existingPreset.name });
|
|
204
|
+
await controllerRef.current.deletePreset(firebaseUidRef.current, presetId);
|
|
205
|
+
// Remove from local state
|
|
206
|
+
setPresets(prev => prev.filter(p => p.id !== presetId));
|
|
207
|
+
debugLog('Preset deleted successfully');
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
handleError('delete preset', err);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
setIsLoading(false);
|
|
216
|
+
}
|
|
217
|
+
}, [presets, debugLog, handleError]);
|
|
218
|
+
// Find preset that matches the given adjustment state
|
|
219
|
+
const findByAdjustments = useCallback((adjustments) => {
|
|
220
|
+
// Helper function to normalize adjustment values (treat null/undefined as 0)
|
|
221
|
+
const normalizeValue = (value) => {
|
|
222
|
+
return value ?? 0;
|
|
223
|
+
};
|
|
224
|
+
// Helper function to convert Preset to AdjustmentState for comparison
|
|
225
|
+
const presetToAdjustmentState = (preset) => {
|
|
226
|
+
return {
|
|
227
|
+
tempScore: normalizeValue(preset.temperature),
|
|
228
|
+
tintScore: normalizeValue(preset.tint),
|
|
229
|
+
vibranceScore: normalizeValue(preset.vibrance),
|
|
230
|
+
saturationScore: normalizeValue(preset.saturation),
|
|
231
|
+
exposureScore: normalizeValue(preset.exposure),
|
|
232
|
+
highlightsScore: normalizeValue(preset.highlights),
|
|
233
|
+
shadowsScore: normalizeValue(preset.shadows),
|
|
234
|
+
whitesScore: normalizeValue(preset.whites),
|
|
235
|
+
blacksScore: normalizeValue(preset.blacks),
|
|
236
|
+
contrastScore: normalizeValue(preset.contrast),
|
|
237
|
+
clarityScore: normalizeValue(preset.clarity),
|
|
238
|
+
sharpnessScore: normalizeValue(preset.sharpness)
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
// Helper function to compare two adjustment states
|
|
242
|
+
const adjustmentsMatch = (presetSettings, current) => {
|
|
243
|
+
const keys = [
|
|
244
|
+
'tempScore', 'tintScore', 'vibranceScore', 'saturationScore',
|
|
245
|
+
'exposureScore', 'highlightsScore', 'shadowsScore', 'whitesScore',
|
|
246
|
+
'blacksScore', 'contrastScore', 'clarityScore', 'sharpnessScore'
|
|
247
|
+
];
|
|
248
|
+
return keys.every(key => normalizeValue(presetSettings[key]) === normalizeValue(current[key]));
|
|
249
|
+
};
|
|
250
|
+
debugLog('Finding preset by adjustments...', adjustments);
|
|
251
|
+
// Find preset that matches the current adjustments
|
|
252
|
+
const matchingPreset = presets.find(preset => {
|
|
253
|
+
const presetAdjustments = presetToAdjustmentState(preset);
|
|
254
|
+
return adjustmentsMatch(presetAdjustments, adjustments);
|
|
255
|
+
});
|
|
256
|
+
if (matchingPreset) {
|
|
257
|
+
debugLog('Found matching preset:', matchingPreset);
|
|
258
|
+
return matchingPreset;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
debugLog('No matching preset found');
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}, [presets, debugLog]);
|
|
265
|
+
// Auto-load presets on initialization - stable dependencies
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (memoizedOptions.autoLoad && controller && firebaseUid && !isInitialized) {
|
|
268
|
+
debugLog('Auto-loading presets...');
|
|
269
|
+
load();
|
|
270
|
+
}
|
|
271
|
+
}, [memoizedOptions.autoLoad, controller, firebaseUid, isInitialized, load, debugLog]);
|
|
272
|
+
// Clear state when controller or firebaseUid changes
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
if (isInitialized) {
|
|
275
|
+
debugLog('Controller or firebaseUid changed, clearing state');
|
|
276
|
+
setPresets([]);
|
|
277
|
+
setError(null);
|
|
278
|
+
setIsInitialized(false);
|
|
279
|
+
}
|
|
280
|
+
}, [controller, firebaseUid, isInitialized, debugLog]);
|
|
281
|
+
// Preset info object - memoized to prevent re-renders
|
|
282
|
+
const info = useMemo(() => ({
|
|
283
|
+
isLoading,
|
|
284
|
+
error,
|
|
285
|
+
count: presets.length,
|
|
286
|
+
isInitialized
|
|
287
|
+
}), [isLoading, error, presets.length, isInitialized]);
|
|
288
|
+
// Actions object - memoized to prevent re-renders when functions don't change
|
|
289
|
+
const actions = useMemo(() => ({
|
|
290
|
+
create,
|
|
291
|
+
rename,
|
|
292
|
+
delete: deletePreset,
|
|
293
|
+
load,
|
|
294
|
+
findByAdjustments
|
|
295
|
+
}), [create, rename, deletePreset, load, findByAdjustments]);
|
|
296
|
+
// Return object - memoized to prevent re-renders when values don't change
|
|
297
|
+
return useMemo(() => ({
|
|
298
|
+
presets,
|
|
299
|
+
info,
|
|
300
|
+
actions
|
|
301
|
+
}), [presets, info, actions]);
|
|
302
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { useImageProcessor } from './lib/hooks/useImageProcessor';
|
|
|
28
28
|
export { useEditorHeadless } from './lib/hooks/useEditorHeadless';
|
|
29
29
|
export { useAdjustmentHistory, type UseAdjustmentHistoryReturn, type HistoryInfo, type HistoryActions, type HistoryConfig } from './hooks/useAdjustmentHistory';
|
|
30
30
|
export { useAdjustmentHistoryBatch, type UseAdjustmentHistoryBatchReturn, type BatchAdjustmentState, type ImageAdjustmentConfig, type BatchHistoryInfo, type BatchHistoryActions, type BatchHistoryConfig } from './hooks/useAdjustmentHistoryBatch';
|
|
31
|
+
export { usePreset, type UsePresetReturn, type PresetInfo, type PresetActions, type PresetOptions } from './hooks/usePreset';
|
|
31
32
|
export { default as useColors } from './themes/colors';
|
|
32
33
|
export { default as useHonchoTypography } from './themes/honchoTheme';
|
|
33
34
|
export { default as useIsMobile } from './utils/isMobile';
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,8 @@ export { useEditorHeadless } from './lib/hooks/useEditorHeadless';
|
|
|
26
26
|
// --- History Hooks ---
|
|
27
27
|
export { useAdjustmentHistory } from './hooks/useAdjustmentHistory';
|
|
28
28
|
export { useAdjustmentHistoryBatch } from './hooks/useAdjustmentHistoryBatch';
|
|
29
|
+
// --- Preset Hook ---
|
|
30
|
+
export { usePreset } from './hooks/usePreset';
|
|
29
31
|
// --- Theme & Utils ---
|
|
30
32
|
export { default as useColors } from './themes/colors';
|
|
31
33
|
export { default as useHonchoTypography } from './themes/honchoTheme';
|