@yogiswara/honcho-editor-ui 2.7.13 → 2.7.15
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.
- package/dist/components/editor/HAccordionPreset.js +12 -10
- package/dist/components/editor/HHeaderEditor.js +4 -4
- package/dist/hooks/editor/useHonchoEditorSingle.js +22 -21
- package/dist/hooks/useAdjustmentHistory.d.ts +3 -3
- package/dist/hooks/useAdjustmentHistory.js +196 -118
- package/dist/lib/hooks/useEditorHeadless.js +1 -1
- package/package.json +1 -1
|
@@ -27,21 +27,23 @@ export default function HAccordionPreset(props) {
|
|
|
27
27
|
};
|
|
28
28
|
const isPanelExpanded = (panelName) => props.expandedPanels.includes(panelName);
|
|
29
29
|
return (_jsx(_Fragment, { children: _jsx(Box, { children: _jsxs(Accordion, { sx: accordionStyle, expanded: isPanelExpanded('preset'), onChange: props.onChange('preset'), disableGutters: true, children: [_jsx(AccordionSummary, { sx: { pr: 0 }, children: _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { sx: { ...typography.titleMedium, color: colors.surface }, children: "Preset" }), _jsx(CardMedia, { component: "img", image: isPanelExpanded('preset') ? "/v1/svg/expanded-editor.svg" : "/v1/svg/expand-editor.svg", sx: { width: "11.67px", height: "5.83px" } })] }) }), _jsx(AccordionDetails, { sx: { pr: "4px" }, children: _jsxs(Stack, { direction: "column", gap: "8px", sx: { pt: '0px', pb: '0px', mx: '0px', width: '100%' }, children: [props.presets.map((preset) => (_jsxs(Stack, { direction: "row", alignItems: "center", sx: { width: "100%" }, children: [_jsx(Box, { sx: { width: 100, flexShrink: 0 }, children: _jsx(Button, { sx: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
width: '100px',
|
|
31
|
+
textWrap: 'nowrap',
|
|
32
|
+
overflow: 'hidden',
|
|
33
|
+
textOverflow: 'ellipsis',
|
|
34
|
+
display: 'block',
|
|
35
|
+
textTransform: 'none',
|
|
36
36
|
color: colors.surface,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
// pr: "120px", // No longer needed since the button is full width
|
|
38
|
+
pl: "0px",
|
|
39
|
+
ml: "0px",
|
|
40
|
+
justifyContent: 'flex-start',
|
|
41
|
+
...typography.bodyMedium
|
|
40
42
|
}, onClick: () => props.onSelectPreset(preset.id), children: _jsx(Box, { component: "span", sx: {
|
|
41
43
|
overflow: "hidden",
|
|
42
44
|
textOverflow: "ellipsis",
|
|
43
45
|
whiteSpace: "nowrap",
|
|
44
|
-
minWidth: 0,
|
|
46
|
+
minWidth: 0,
|
|
45
47
|
}, children: preset.name }) }) }), _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, sx: { ml: "auto" }, children: [props.selectedPreset === preset.id && (_jsx(CardMedia, { component: "img", image: "v1/svg/check-ratio-editor.svg", sx: { width: "20px", height: "20px" } })), _jsx(IconButton, { "aria-label": preset.name, onClick: (event) => props.onPresetMenuClick(event, preset.id), sx: { p: 0, m: 0 }, children: _jsx(CardMedia, { component: "img", image: "/v1/svg/dots-editor.svg", alt: "Options" }) })] })] }, preset.id))), _jsx(Button, { variant: "text", sx: { color: colors.surface, border: "1px solid",
|
|
46
48
|
borderColor: colors.surface,
|
|
47
49
|
borderRadius: "40px",
|
|
@@ -11,10 +11,10 @@ export default function HHeaderEditor(props) {
|
|
|
11
11
|
return (_jsx(_Fragment, { children: _jsxs(Stack, { direction: "row", justifyContent: "space-between", width: "100%", sx: {
|
|
12
12
|
id: 'HHeaderEditor',
|
|
13
13
|
pr: !isMobile ? "24px" : "6px",
|
|
14
|
-
position: 'fixed',
|
|
15
|
-
top: 0,
|
|
16
|
-
left: 0,
|
|
17
|
-
right: 0,
|
|
14
|
+
position: isMobile ? 'fixed' : 'relative',
|
|
15
|
+
top: isMobile ? 0 : 'auto',
|
|
16
|
+
left: isMobile ? 0 : 'auto',
|
|
17
|
+
right: isMobile ? 0 : 'auto',
|
|
18
18
|
zIndex: 1300,
|
|
19
19
|
backgroundColor: 'transparent',
|
|
20
20
|
}, children: [_jsx(Stack, { direction: "row", justifyContent: "flex-start", sx: { pl: !isMobile ? "0px" : "14px" }, children: _jsx(IconButton, { "aria-label": "back", onClick: props.onBack, sx: {
|
|
@@ -37,7 +37,7 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
37
37
|
[field]: value
|
|
38
38
|
};
|
|
39
39
|
adjustmentHistory.actions.pushState(newState);
|
|
40
|
-
}, [adjustmentHistory.currentState, adjustmentHistory.actions.pushState]);
|
|
40
|
+
}, [adjustmentHistory.currentState, adjustmentHistory.actions.pushState, adjustmentHistory.historyInfo.isBatchMode]);
|
|
41
41
|
const setBatchMode = useCallback((enabled) => {
|
|
42
42
|
adjustmentHistory.config.setBatchMode(enabled);
|
|
43
43
|
}, [adjustmentHistory.config.setBatchMode]);
|
|
@@ -57,17 +57,17 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
57
57
|
// Reset means setting all adjustments to 0 and adding it as new history entry
|
|
58
58
|
// This allows users to undo the reset operation
|
|
59
59
|
// Reset acts like normal adjustment - each reset creates a new history entry
|
|
60
|
-
console.log('Resetting adjustments to 0 - adding to history
|
|
61
|
-
//
|
|
60
|
+
console.log('Resetting adjustments to 0 - adding to history');
|
|
61
|
+
// Always add reset to history (this automatically syncs to backend via batch mode)
|
|
62
|
+
await adjustmentHistory.config.setBatchMode(true);
|
|
62
63
|
adjustmentHistory.actions.pushState(initialAdjustments);
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
}, [adjustmentHistory.actions.pushState, adjustmentHistory.config.syncToBackend]);
|
|
64
|
+
await adjustmentHistory.config.setBatchMode(false, true); // forceHistory = true
|
|
65
|
+
}, [adjustmentHistory.actions.pushState, adjustmentHistory.config.setBatchMode]);
|
|
66
66
|
const loadPresets = useCallback(async () => {
|
|
67
67
|
await presetHook.actions.load();
|
|
68
68
|
}, [presetHook.actions.load]);
|
|
69
69
|
const applyPreset = useCallback(async (preset) => {
|
|
70
|
-
console.log('Applying preset:', preset.name
|
|
70
|
+
console.log('Applying preset:', preset.name);
|
|
71
71
|
const adjustmentState = {
|
|
72
72
|
tempScore: preset.temperature,
|
|
73
73
|
tintScore: preset.tint,
|
|
@@ -82,11 +82,12 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
82
82
|
clarityScore: preset.clarity,
|
|
83
83
|
sharpnessScore: preset.sharpness,
|
|
84
84
|
};
|
|
85
|
-
|
|
85
|
+
console.log('Applying preset:', preset.name, 'with adjustments:', adjustmentState);
|
|
86
|
+
// Always apply preset and add to history (this automatically syncs to backend via batch mode)
|
|
87
|
+
await adjustmentHistory.config.setBatchMode(true);
|
|
86
88
|
adjustmentHistory.actions.pushState(adjustmentState);
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
}, [adjustmentHistory.actions.pushState, adjustmentHistory.config.syncToBackend]);
|
|
89
|
+
await adjustmentHistory.config.setBatchMode(false, true); // forceHistory = true
|
|
90
|
+
}, [adjustmentHistory.actions.pushState, adjustmentHistory.config.setBatchMode]);
|
|
90
91
|
const createPreset = useCallback(async (name, adjustments) => {
|
|
91
92
|
return await presetHook.actions.create(name, adjustments);
|
|
92
93
|
}, [presetHook.actions.create]);
|
|
@@ -101,16 +102,16 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
101
102
|
return {
|
|
102
103
|
temperature: adjustments.tempScore,
|
|
103
104
|
tint: adjustments.tintScore,
|
|
104
|
-
vibrance: adjustments.vibranceScore
|
|
105
|
-
saturation: adjustments.saturationScore
|
|
106
|
-
exposure:
|
|
107
|
-
highlights: adjustments.highlightsScore
|
|
108
|
-
shadows: adjustments.shadowsScore
|
|
109
|
-
whites: adjustments.whitesScore
|
|
110
|
-
blacks: adjustments.blacksScore
|
|
111
|
-
contrast: adjustments.contrastScore
|
|
112
|
-
clarity: adjustments.clarityScore
|
|
113
|
-
sharpness: adjustments.sharpnessScore
|
|
105
|
+
vibrance: adjustments.vibranceScore,
|
|
106
|
+
saturation: adjustments.saturationScore,
|
|
107
|
+
exposure: adjustments.exposureScore,
|
|
108
|
+
highlights: adjustments.highlightsScore,
|
|
109
|
+
shadows: adjustments.shadowsScore,
|
|
110
|
+
whites: adjustments.whitesScore,
|
|
111
|
+
blacks: adjustments.blacksScore,
|
|
112
|
+
contrast: adjustments.contrastScore,
|
|
113
|
+
clarity: adjustments.clarityScore,
|
|
114
|
+
sharpness: adjustments.sharpnessScore,
|
|
114
115
|
};
|
|
115
116
|
}, [adjustmentHistory.currentState]);
|
|
116
117
|
const actions = {
|
|
@@ -49,6 +49,8 @@ export interface HistoryActions {
|
|
|
49
49
|
trimHistory: (keepLast: number) => void;
|
|
50
50
|
/** Replace entire history with new list of adjustment states */
|
|
51
51
|
syncHistory: (newHistory: AdjustmentState[], targetIndex?: number) => void;
|
|
52
|
+
/** Sync history from backend using getEditorHistory */
|
|
53
|
+
syncFromBackend: () => Promise<void>;
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Configuration actions for runtime adjustment
|
|
@@ -57,11 +59,9 @@ export interface HistoryConfig {
|
|
|
57
59
|
/** Set maximum history size */
|
|
58
60
|
setMaxSize: (size: number | 'unlimited') => void;
|
|
59
61
|
/** Enable or disable batch mode */
|
|
60
|
-
setBatchMode: (enabled: boolean) => Promise<void>;
|
|
62
|
+
setBatchMode: (enabled: boolean, forceHistory?: boolean) => Promise<void>;
|
|
61
63
|
/** Get current memory usage estimate */
|
|
62
64
|
getMemoryUsage: () => number;
|
|
63
|
-
/** Force sync current state to backend */
|
|
64
|
-
syncToBackend: () => Promise<void>;
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* Return type for the useAdjustmentHistory hook
|
|
@@ -18,6 +18,25 @@ const convertAdjustmentStateToColorAdjustment = (adjustmentState) => {
|
|
|
18
18
|
sharpness: adjustmentState.sharpnessScore,
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Convert ColorAdjustment from backend to AdjustmentState format
|
|
23
|
+
*/
|
|
24
|
+
const convertColorAdjustmentToAdjustmentState = (colorAdjustment) => {
|
|
25
|
+
return {
|
|
26
|
+
tempScore: colorAdjustment.temperature,
|
|
27
|
+
tintScore: colorAdjustment.tint,
|
|
28
|
+
saturationScore: colorAdjustment.saturation,
|
|
29
|
+
vibranceScore: colorAdjustment.vibrance,
|
|
30
|
+
exposureScore: colorAdjustment.exposure,
|
|
31
|
+
contrastScore: colorAdjustment.contrast,
|
|
32
|
+
highlightsScore: colorAdjustment.highlights,
|
|
33
|
+
shadowsScore: colorAdjustment.shadows,
|
|
34
|
+
whitesScore: colorAdjustment.whites,
|
|
35
|
+
blacksScore: colorAdjustment.blacks,
|
|
36
|
+
clarityScore: colorAdjustment.clarity,
|
|
37
|
+
sharpnessScore: colorAdjustment.sharpness,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
21
40
|
/**
|
|
22
41
|
* Compare two AdjustmentState objects for equality
|
|
23
42
|
* Uses JSON.stringify for deep comparison of all adjustment values
|
|
@@ -79,7 +98,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
79
98
|
currentImageId
|
|
80
99
|
]);
|
|
81
100
|
// Core state management
|
|
82
|
-
const [history, setHistory] = useState([{ state: initialState }]);
|
|
101
|
+
const [history, setHistory] = useState([{ state: initialState, taskId: `initial_${Date.now()}` }]);
|
|
83
102
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
84
103
|
const [currentState, setCurrentState] = useState(initialState);
|
|
85
104
|
// Batch mode state - ref to avoid triggering effects
|
|
@@ -97,11 +116,17 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
97
116
|
largeHistoryWarningShown: false
|
|
98
117
|
});
|
|
99
118
|
// Sync currentState with history when not in batch mode
|
|
119
|
+
// useEffect(() => {
|
|
120
|
+
// if (!batchModeRef.current) {
|
|
121
|
+
// console.log(`[useAdjustmentHistory] Syncing currentState with history:`, history[currentIndex]?.state);
|
|
122
|
+
// setCurrentState(history[currentIndex]?.state || initialState);
|
|
123
|
+
// }
|
|
124
|
+
// }, [history, currentIndex, initialState]);
|
|
100
125
|
useEffect(() => {
|
|
101
|
-
if (
|
|
102
|
-
|
|
126
|
+
if (internalOptions.currentImageId) {
|
|
127
|
+
syncFromBackend().catch(console.error);
|
|
103
128
|
}
|
|
104
|
-
}, [
|
|
129
|
+
}, [internalOptions.currentImageId, internalOptions.firebaseUid, internalOptions.controller]);
|
|
105
130
|
const getMemoryUsage = useCallback(() => {
|
|
106
131
|
try {
|
|
107
132
|
const historyString = JSON.stringify(history);
|
|
@@ -160,12 +185,15 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
160
185
|
if (batchModeRef.current) {
|
|
161
186
|
// In batch mode: Don't update history yet, just update UI state
|
|
162
187
|
// History will be updated when batch mode ends
|
|
188
|
+
console.log(`[useAdjustmentHistory] Pushing state in batch mode:`, newState);
|
|
163
189
|
return;
|
|
164
190
|
}
|
|
165
191
|
// Normal mode: Update history immediately
|
|
166
192
|
setHistory(prevHistory => {
|
|
167
193
|
const truncatedHistory = prevHistory.slice(0, currentIndex + 1);
|
|
168
|
-
const
|
|
194
|
+
// const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
195
|
+
// we put the taskId empty to check this state no need to save it
|
|
196
|
+
const newHistory = [...truncatedHistory, { state: newState, taskId: "" }];
|
|
169
197
|
setCurrentIndex(newHistory.length - 1);
|
|
170
198
|
return newHistory;
|
|
171
199
|
});
|
|
@@ -178,6 +206,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
178
206
|
const newState = historyEntry.state;
|
|
179
207
|
setCurrentIndex(newIndex);
|
|
180
208
|
setCurrentState(newState);
|
|
209
|
+
console.log(`[useAdjustmentHistory] Undoing to index ${newIndex}:`, newState);
|
|
181
210
|
// Call controller to set history index in backend if taskId exists
|
|
182
211
|
if (historyEntry.taskId && internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
|
|
183
212
|
try {
|
|
@@ -189,6 +218,9 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
189
218
|
console.error('❌ Failed to set history index for undo:', error);
|
|
190
219
|
}
|
|
191
220
|
}
|
|
221
|
+
else {
|
|
222
|
+
console.log("🔙 Undo: No taskId available for setting history index", historyEntry.taskId, internalOptions.firebaseUid, internalOptions.currentImageId);
|
|
223
|
+
}
|
|
192
224
|
// Exit batch mode when undoing
|
|
193
225
|
if (batchModeRef.current) {
|
|
194
226
|
batchModeRef.current = false;
|
|
@@ -205,6 +237,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
205
237
|
const newState = historyEntry.state;
|
|
206
238
|
setCurrentIndex(newIndex);
|
|
207
239
|
setCurrentState(newState);
|
|
240
|
+
console.log(`[useAdjustmentHistory] Redoing to index ${newIndex}:`, newState);
|
|
208
241
|
// Call controller to set history index in backend if taskId exists
|
|
209
242
|
if (historyEntry.taskId && internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
|
|
210
243
|
try {
|
|
@@ -216,11 +249,14 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
216
249
|
console.error('❌ Failed to set history index for redo:', error);
|
|
217
250
|
}
|
|
218
251
|
}
|
|
252
|
+
else {
|
|
253
|
+
console.warn('🔄 Redo: No taskId available for setting history index', historyEntry.taskId, internalOptions.firebaseUid, internalOptions.currentImageId);
|
|
254
|
+
}
|
|
219
255
|
}
|
|
220
256
|
}, [currentIndex, history, internalOptions]);
|
|
221
257
|
// Reset history with new initial state
|
|
222
258
|
const reset = useCallback((newInitialState) => {
|
|
223
|
-
setHistory([{ state: newInitialState }]);
|
|
259
|
+
setHistory([{ state: newInitialState, taskId: `reset_${Date.now()}` }]);
|
|
224
260
|
setCurrentIndex(0);
|
|
225
261
|
setCurrentState(newInitialState);
|
|
226
262
|
batchModeRef.current = internalOptions.enableBatching;
|
|
@@ -244,7 +280,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
244
280
|
}, [history]);
|
|
245
281
|
// Clear all history and start fresh
|
|
246
282
|
const clearHistory = useCallback(() => {
|
|
247
|
-
setHistory([{ state: currentState }]);
|
|
283
|
+
setHistory([{ state: currentState, taskId: `clear_${Date.now()}` }]);
|
|
248
284
|
setCurrentIndex(0);
|
|
249
285
|
batchModeRef.current = internalOptions.enableBatching;
|
|
250
286
|
batchStartIndexRef.current = null;
|
|
@@ -260,62 +296,69 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
260
296
|
}, [trimHistoryToSize]);
|
|
261
297
|
// Sync/replace entire history with new list
|
|
262
298
|
const syncHistory = useCallback((newHistory, targetIndex) => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
299
|
+
throw Error('syncHistory is not implemented yet');
|
|
300
|
+
// // Validate input
|
|
301
|
+
// if (!Array.isArray(newHistory) || newHistory.length === 0) {
|
|
302
|
+
// console.warn('syncHistory: newHistory must be a non-empty array');
|
|
303
|
+
// return;
|
|
304
|
+
// }
|
|
305
|
+
// // Validate all items are AdjustmentState objects
|
|
306
|
+
// const isValidHistory = newHistory.every(state =>
|
|
307
|
+
// state && typeof state === 'object' &&
|
|
308
|
+
// typeof state.tempScore === 'number' &&
|
|
309
|
+
// typeof state.tintScore === 'number' &&
|
|
310
|
+
// typeof state.vibranceScore === 'number' &&
|
|
311
|
+
// typeof state.saturationScore === 'number' &&
|
|
312
|
+
// typeof state.exposureScore === 'number' &&
|
|
313
|
+
// typeof state.highlightsScore === 'number' &&
|
|
314
|
+
// typeof state.shadowsScore === 'number' &&
|
|
315
|
+
// typeof state.whitesScore === 'number' &&
|
|
316
|
+
// typeof state.blacksScore === 'number' &&
|
|
317
|
+
// typeof state.contrastScore === 'number' &&
|
|
318
|
+
// typeof state.clarityScore === 'number' &&
|
|
319
|
+
// typeof state.sharpnessScore === 'number'
|
|
320
|
+
// );
|
|
321
|
+
// if (!isValidHistory) {
|
|
322
|
+
// console.warn('syncHistory: All items in newHistory must be valid AdjustmentState objects');
|
|
323
|
+
// return;
|
|
324
|
+
// }
|
|
325
|
+
// // Exit batch mode if active
|
|
326
|
+
// if (batchModeRef.current) {
|
|
327
|
+
// batchModeRef.current = false;
|
|
328
|
+
// batchStartIndexRef.current = null;
|
|
329
|
+
// batchStartStateRef.current = null;
|
|
330
|
+
// }
|
|
331
|
+
// // Determine target index
|
|
332
|
+
// let finalIndex = targetIndex ?? newHistory.length - 1; // Default to last item
|
|
333
|
+
// finalIndex = Math.max(0, Math.min(finalIndex, newHistory.length - 1)); // Clamp to valid range
|
|
334
|
+
// // Create a copy of the new history to avoid mutations and convert to HistoryEntry format
|
|
335
|
+
// const historyToSet = newHistory.map((state, index) => ({
|
|
336
|
+
// state: { ...state },
|
|
337
|
+
// taskId: `sync_${Date.now()}_${index}`
|
|
338
|
+
// }));
|
|
339
|
+
// // Apply max size limit if needed
|
|
340
|
+
// if (maxSizeRef.current !== 'unlimited' && historyToSet.length > maxSizeRef.current) {
|
|
341
|
+
// const trimAmount = historyToSet.length - maxSizeRef.current;
|
|
342
|
+
// const trimmedHistory = historyToSet.slice(trimAmount);
|
|
343
|
+
// // Adjust target index
|
|
344
|
+
// finalIndex = Math.max(0, finalIndex - trimAmount);
|
|
345
|
+
// setHistory(trimmedHistory);
|
|
346
|
+
// setCurrentIndex(finalIndex);
|
|
347
|
+
// console.log(`syncHistory: Trimmed ${trimAmount} entries to respect maxSize of ${maxSizeRef.current}`);
|
|
348
|
+
// setCurrentState(trimmedHistory[finalIndex].state);
|
|
349
|
+
// if (devWarningsRef.current) {
|
|
350
|
+
// console.warn(`syncHistory: Trimmed ${trimAmount} entries to respect maxSize of ${maxSizeRef.current}`);
|
|
351
|
+
// }
|
|
352
|
+
// } else {
|
|
353
|
+
// // Set history as-is
|
|
354
|
+
// setHistory(historyToSet);
|
|
355
|
+
// setCurrentIndex(finalIndex);
|
|
356
|
+
// console.log(`syncHistory: Synchronized ${historyToSet.length} states, current index: ${finalIndex}`);
|
|
357
|
+
// setCurrentState(historyToSet[finalIndex].state);
|
|
358
|
+
// }
|
|
359
|
+
// if (devWarningsRef.current) {
|
|
360
|
+
// console.log(`syncHistory: Synchronized ${historyToSet.length} states, current index: ${finalIndex}`);
|
|
361
|
+
// }
|
|
319
362
|
}, []);
|
|
320
363
|
// Configuration setters
|
|
321
364
|
const setMaxSize = useCallback((size) => {
|
|
@@ -324,7 +367,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
324
367
|
enforceMaxSize();
|
|
325
368
|
}
|
|
326
369
|
}, [enforceMaxSize]);
|
|
327
|
-
const setBatchMode = useCallback(async (enabled) => {
|
|
370
|
+
const setBatchMode = useCallback(async (enabled, forceHistory = false) => {
|
|
328
371
|
const wasInBatch = batchModeRef.current;
|
|
329
372
|
console.log(`🔧 setBatchMode called: enabled=${enabled}, wasInBatch=${wasInBatch}, currentIndex=${currentIndex}, historyLength=${history.length}`);
|
|
330
373
|
if (enabled && !wasInBatch) {
|
|
@@ -332,27 +375,40 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
332
375
|
batchModeRef.current = true;
|
|
333
376
|
batchStartIndexRef.current = currentIndex;
|
|
334
377
|
batchStartStateRef.current = currentState;
|
|
335
|
-
console.log("
|
|
378
|
+
console.log("🔧 setBatchMode(true): Starting batch mode", {
|
|
379
|
+
currentState,
|
|
380
|
+
currentIndex,
|
|
381
|
+
batchStartState: currentState
|
|
382
|
+
});
|
|
336
383
|
}
|
|
337
384
|
else if (!enabled && wasInBatch) {
|
|
338
385
|
// Guard against double execution
|
|
386
|
+
console.log("🔧 setBatchMode: Ending batch mode", batchModeProcessingRef.current);
|
|
339
387
|
if (batchModeProcessingRef.current) {
|
|
340
388
|
console.log("⚠️ setBatchMode(false) already processing, skipping duplicate call");
|
|
341
389
|
return;
|
|
342
390
|
}
|
|
391
|
+
console.log("🔧 setBatchMode: Ending batch mode", currentState, `batchStartIndex=${batchStartIndexRef.current}`);
|
|
343
392
|
// Ending batch mode - commit final state to history
|
|
344
393
|
batchModeProcessingRef.current = true;
|
|
345
394
|
batchModeRef.current = false;
|
|
346
395
|
// Only add to history if state actually changed from batch start
|
|
347
|
-
|
|
348
|
-
|
|
396
|
+
const statesEqual = batchStartStateRef.current ?
|
|
397
|
+
compareAdjustmentStates(currentState, batchStartStateRef.current) : true;
|
|
398
|
+
console.log("🔧 setBatchMode(false): Comparing states", {
|
|
399
|
+
currentState,
|
|
400
|
+
batchStartState: batchStartStateRef.current,
|
|
401
|
+
statesEqual,
|
|
402
|
+
forceHistory,
|
|
403
|
+
willAddToHistory: batchStartStateRef.current && (!statesEqual || forceHistory)
|
|
404
|
+
});
|
|
405
|
+
if (batchStartStateRef.current && (!statesEqual || forceHistory)) {
|
|
349
406
|
// Generate a unique task ID for this history entry
|
|
350
407
|
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
351
408
|
setHistory(prevHistory => {
|
|
352
409
|
// Check if we were in the middle of history BEFORE any truncation
|
|
353
410
|
// Handle case where batchStartIndexRef.current might be null (e.g., after undo)
|
|
354
411
|
const batchStartIndex = batchStartIndexRef.current ?? currentIndex;
|
|
355
|
-
const wasInMiddleOfHistory = batchStartIndex < prevHistory.length - 1;
|
|
356
412
|
const truncatedHistory = prevHistory.slice(0, batchStartIndex + 1);
|
|
357
413
|
const newHistoryEntry = {
|
|
358
414
|
state: currentState,
|
|
@@ -401,11 +457,78 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
401
457
|
}
|
|
402
458
|
}
|
|
403
459
|
}
|
|
460
|
+
else {
|
|
461
|
+
if (forceHistory) {
|
|
462
|
+
console.log("🔧 setBatchMode: Force history requested but no batch start state");
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
console.log("🔧 setBatchMode: No changes since batch start, not adding to history");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
404
468
|
batchStartIndexRef.current = null;
|
|
405
469
|
batchStartStateRef.current = null;
|
|
406
470
|
batchModeProcessingRef.current = false; // Reset processing flag
|
|
407
471
|
}
|
|
408
472
|
}, [currentIndex, currentState, internalOptions]);
|
|
473
|
+
// Sync history from backend using getEditorHistory
|
|
474
|
+
const syncFromBackend = useCallback(async () => {
|
|
475
|
+
if (!internalOptions.controller || !internalOptions.firebaseUid || !internalOptions.currentImageId) {
|
|
476
|
+
console.warn('⚠️ syncFromBackend: Controller, firebaseUid, or currentImageId not provided - cannot sync from backend');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
console.log('🔄 Syncing history from backend using getEditorHistory');
|
|
481
|
+
const historyResponse = await internalOptions.controller.getEditorHistory(internalOptions.firebaseUid, internalOptions.currentImageId);
|
|
482
|
+
console.log('📥 Received history response from backend:', historyResponse);
|
|
483
|
+
// Sort history by timestamp (oldest first) before processing
|
|
484
|
+
const sortedHistory = [...historyResponse.history].sort((a, b) => {
|
|
485
|
+
const timeA = new Date(a.log.created_at).getTime();
|
|
486
|
+
const timeB = new Date(b.log.created_at).getTime();
|
|
487
|
+
return timeA - timeB; // Ascending order (oldest first)
|
|
488
|
+
});
|
|
489
|
+
console.log('📋 Sorted history by timestamp:', sortedHistory.map(entry => ({
|
|
490
|
+
task_id: entry.task_id,
|
|
491
|
+
timestamp: entry.log.created_at
|
|
492
|
+
})));
|
|
493
|
+
// Convert backend history to AdjustmentState format with taskIds
|
|
494
|
+
const backendHistoryEntries = sortedHistory.map((entry) => ({
|
|
495
|
+
state: convertColorAdjustmentToAdjustmentState(entry.editor_config.color_adjustment),
|
|
496
|
+
taskId: entry.task_id
|
|
497
|
+
}));
|
|
498
|
+
// Find the index of the current task
|
|
499
|
+
let currentTaskIndex = -1;
|
|
500
|
+
if (historyResponse.current_task_id) {
|
|
501
|
+
currentTaskIndex = backendHistoryEntries.findIndex(entry => entry.taskId === historyResponse.current_task_id);
|
|
502
|
+
}
|
|
503
|
+
// If current_task_id is not found in history, default to the last entry
|
|
504
|
+
if (currentTaskIndex === -1 && backendHistoryEntries.length > 0) {
|
|
505
|
+
currentTaskIndex = backendHistoryEntries.length - 1;
|
|
506
|
+
console.warn(`⚠️ Current task ID "${historyResponse.current_task_id}" not found in history, defaulting to last entry`);
|
|
507
|
+
}
|
|
508
|
+
// Handle empty history case
|
|
509
|
+
if (backendHistoryEntries.length === 0) {
|
|
510
|
+
console.log('📝 Backend history is empty, keeping current local history');
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
// Exit batch mode if active
|
|
514
|
+
if (batchModeRef.current) {
|
|
515
|
+
batchModeRef.current = false;
|
|
516
|
+
batchStartIndexRef.current = null;
|
|
517
|
+
batchStartStateRef.current = null;
|
|
518
|
+
}
|
|
519
|
+
// Update history state with backend data
|
|
520
|
+
setHistory(backendHistoryEntries);
|
|
521
|
+
setCurrentIndex(Math.max(0, currentTaskIndex));
|
|
522
|
+
console.log(`📍 Setting current index to: ${Math.max(0, currentTaskIndex)} (task_id: ${historyResponse.current_task_id})`);
|
|
523
|
+
setCurrentState(backendHistoryEntries[Math.max(0, currentTaskIndex)].state);
|
|
524
|
+
console.log(`✅ Successfully synced ${backendHistoryEntries.length} history entries from backend`);
|
|
525
|
+
console.log(`📍 Set current index to: ${Math.max(0, currentTaskIndex)} (task_id: ${historyResponse.current_task_id})`);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
console.error('❌ Failed to sync history from backend:', error);
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
}, [internalOptions]);
|
|
409
532
|
// History info object
|
|
410
533
|
const historyInfo = useMemo(() => ({
|
|
411
534
|
canUndo: currentIndex > 0,
|
|
@@ -425,60 +548,15 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
425
548
|
clearHistory,
|
|
426
549
|
getHistory,
|
|
427
550
|
trimHistory,
|
|
428
|
-
syncHistory
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const syncToBackend = useCallback(async () => {
|
|
432
|
-
if (internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
|
|
433
|
-
try {
|
|
434
|
-
console.log('🔄 Force syncing current state to backend');
|
|
435
|
-
// Check if we're in middle of history (not at the latest)
|
|
436
|
-
const wasInMiddleOfHistory = currentIndex < history.length - 1;
|
|
437
|
-
let replaceFromTaskId;
|
|
438
|
-
if (wasInMiddleOfHistory) {
|
|
439
|
-
// Get the task_id from the current history entry to use as replace_from
|
|
440
|
-
const currentHistoryEntry = history[currentIndex];
|
|
441
|
-
replaceFromTaskId = currentHistoryEntry?.taskId;
|
|
442
|
-
console.log(`📍 In middle of history (index ${currentIndex}), using replace_from: ${replaceFromTaskId}`);
|
|
443
|
-
}
|
|
444
|
-
// Generate a unique task ID for this sync
|
|
445
|
-
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
446
|
-
const createEditorConfigPayload = {
|
|
447
|
-
gallery_id: internalOptions.currentImageId,
|
|
448
|
-
task_id: taskId,
|
|
449
|
-
color_adjustment: convertAdjustmentStateToColorAdjustment(currentState),
|
|
450
|
-
...(replaceFromTaskId && { replace_from: replaceFromTaskId })
|
|
451
|
-
};
|
|
452
|
-
await internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, createEditorConfigPayload);
|
|
453
|
-
console.log('✅ Successfully synced current state to backend');
|
|
454
|
-
// Update the current history entry with the new task ID
|
|
455
|
-
setHistory(prevHistory => {
|
|
456
|
-
const newHistory = [...prevHistory];
|
|
457
|
-
if (newHistory[currentIndex]) {
|
|
458
|
-
newHistory[currentIndex] = {
|
|
459
|
-
...newHistory[currentIndex],
|
|
460
|
-
taskId: taskId
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
return newHistory;
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
catch (error) {
|
|
467
|
-
console.error('❌ Failed to sync current state to backend:', error);
|
|
468
|
-
throw error;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
console.warn('⚠️ Controller, firebaseUid, or currentImageId not provided - skipping backend sync');
|
|
473
|
-
}
|
|
474
|
-
}, [currentState, currentIndex, history, internalOptions]);
|
|
551
|
+
syncHistory,
|
|
552
|
+
syncFromBackend
|
|
553
|
+
}), [pushState, undo, redo, reset, jumpToIndex, clearHistory, getHistory, trimHistory, syncHistory, syncFromBackend]);
|
|
475
554
|
// Config object - stabilized with useMemo
|
|
476
555
|
const config = useMemo(() => ({
|
|
477
556
|
setMaxSize,
|
|
478
557
|
setBatchMode,
|
|
479
|
-
getMemoryUsage
|
|
480
|
-
|
|
481
|
-
}), [setMaxSize, setBatchMode, getMemoryUsage, syncToBackend]);
|
|
558
|
+
getMemoryUsage
|
|
559
|
+
}), [setMaxSize, setBatchMode, getMemoryUsage]);
|
|
482
560
|
// Apply max size enforcement when history changes
|
|
483
561
|
useEffect(() => {
|
|
484
562
|
enforceMaxSize();
|
|
@@ -125,7 +125,7 @@ export function useEditorHeadless(options = {}) {
|
|
|
125
125
|
console.debug('HonchoEditor instance created successfully');
|
|
126
126
|
}
|
|
127
127
|
console.debug('Initializing HonchoEditor...');
|
|
128
|
-
await editorRef.current.initialize(
|
|
128
|
+
await editorRef.current.initialize(true);
|
|
129
129
|
console.debug('HonchoEditor initialized successfully');
|
|
130
130
|
setIsReady(true);
|
|
131
131
|
}
|