@yogiswara/honcho-editor-ui 2.5.9 → 2.6.0
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/HBulkPreset.js +12 -2
- package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +3 -0
- package/dist/hooks/demo/HonchoEditorBulkDemo.js +228 -0
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +3 -0
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +354 -0
- package/dist/hooks/demo/index.d.ts +2 -0
- package/dist/hooks/demo/index.js +2 -0
- package/dist/hooks/editor/type.d.ts +71 -0
- package/dist/hooks/editor/useHonchoEditorBulk.d.ts +12 -12
- package/dist/hooks/editor/useHonchoEditorBulk.js +155 -42
- package/dist/hooks/editor/useHonchoEditorSingle.d.ts +43 -0
- package/dist/hooks/editor/useHonchoEditorSingle.js +158 -0
- package/dist/hooks/useAdjustmentHistory.d.ts +9 -5
- package/dist/hooks/useAdjustmentHistory.js +187 -31
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +18 -1
- package/dist/hooks/useAdjustmentHistoryBatch.js +627 -201
- package/dist/hooks/useGallerySwipe.d.ts +1 -1
- package/dist/hooks/usePaging.d.ts +89 -0
- package/dist/hooks/usePaging.js +211 -0
- package/dist/hooks/usePreset.d.ts +1 -1
- package/dist/hooks/usePreset.js +35 -35
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -1
- package/dist/lib/context/EditorContext.d.ts +10 -0
- package/dist/lib/context/EditorContext.js +4 -2
- package/dist/lib/hooks/useEditorHeadless.d.ts +18 -2
- package/dist/lib/hooks/useEditorHeadless.js +142 -63
- package/dist/utils/adjustment.d.ts +2 -1
- package/dist/utils/adjustment.js +16 -0
- package/dist/utils/imageLoader.d.ts +11 -0
- package/dist/utils/imageLoader.js +53 -0
- package/package.json +1 -1
- package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.d.ts +0 -17
- package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.js +0 -14
- package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.d.ts +0 -8
- package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.js +0 -30
- package/dist/components/editor/HImageEditorPage.d.ts +0 -1
- package/dist/components/editor/HImageEditorPage.js +0 -187
- package/dist/hooks/__tests__/useGallerySwipe.test.d.ts +0 -0
- package/dist/hooks/__tests__/useGallerySwipe.test.js +0 -619
- package/dist/hooks/editor/useHonchoEditor.d.ts +0 -203
- package/dist/hooks/editor/useHonchoEditor.js +0 -716
- package/dist/hooks/useAdjustmentHistory.demo.d.ts +0 -8
- package/dist/hooks/useAdjustmentHistory.demo.js +0 -106
- package/dist/hooks/useAdjustmentHistory.example.d.ts +0 -38
- package/dist/hooks/useAdjustmentHistory.example.js +0 -182
- package/dist/hooks/useAdjustmentHistory.syncDemo.d.ts +0 -8
- package/dist/hooks/useAdjustmentHistory.syncDemo.js +0 -180
- package/dist/hooks/useGallerySwipe.example.d.ts +0 -24
- package/dist/hooks/useGallerySwipe.example.js +0 -184
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
|
+
import { mapAdjustmentStateToColorAdjustment, mapColorAdjustmentToAdjustmentState } from '../utils/adjustment';
|
|
2
3
|
/**
|
|
3
4
|
* Create default adjustment state
|
|
4
5
|
*/
|
|
@@ -37,6 +38,23 @@ const createEmptyBatchState = () => ({
|
|
|
37
38
|
allImages: {},
|
|
38
39
|
initialStates: {}
|
|
39
40
|
});
|
|
41
|
+
/**
|
|
42
|
+
* Generate unique ID for history entries using UUID format
|
|
43
|
+
*/
|
|
44
|
+
const generateEntryId = () => {
|
|
45
|
+
// Simple UUID v4 implementation
|
|
46
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
47
|
+
const r = Math.random() * 16 | 0;
|
|
48
|
+
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
49
|
+
return v.toString(16);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Generate unique task ID for backend operations (same as entry ID)
|
|
54
|
+
*/
|
|
55
|
+
const generateTaskId = () => {
|
|
56
|
+
return generateEntryId();
|
|
57
|
+
};
|
|
40
58
|
/**
|
|
41
59
|
* Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
|
|
42
60
|
*
|
|
@@ -85,192 +103,429 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
85
103
|
const internalOptions = useMemo(() => ({
|
|
86
104
|
maxSize: options.maxSize ?? 'unlimited',
|
|
87
105
|
devWarnings: options.devWarnings ?? false,
|
|
88
|
-
defaultAdjustmentState: options.defaultAdjustmentState ?? {}
|
|
106
|
+
defaultAdjustmentState: options.defaultAdjustmentState ?? {},
|
|
107
|
+
controller: options.controller,
|
|
108
|
+
firebaseUid: options.firebaseUid,
|
|
109
|
+
eventId: options.eventId
|
|
89
110
|
}), [
|
|
90
111
|
options.maxSize,
|
|
91
112
|
options.devWarnings,
|
|
92
|
-
options.defaultAdjustmentState
|
|
113
|
+
options.defaultAdjustmentState,
|
|
114
|
+
options.controller,
|
|
115
|
+
options.firebaseUid,
|
|
116
|
+
options.eventId
|
|
93
117
|
]);
|
|
94
|
-
// Core state management -
|
|
118
|
+
// Core state management - using per-image history instead of batch history
|
|
95
119
|
const [allImageIds, setAllImageIds] = useState([]);
|
|
96
120
|
const [selectedIds, setSelectedIds] = useState([]);
|
|
97
|
-
const [
|
|
98
|
-
const [currentIndex, setCurrentIndex] = useState(0);
|
|
121
|
+
const [imageHistories, setImageHistories] = useState([]);
|
|
99
122
|
const [currentBatch, setCurrentBatch] = useState(createEmptyBatchState());
|
|
100
123
|
// Configuration refs
|
|
101
124
|
const maxSizeRef = useRef(internalOptions.maxSize);
|
|
102
125
|
const devWarningsRef = useRef(internalOptions.devWarnings);
|
|
103
|
-
//
|
|
126
|
+
// Helper function to rebuild currentBatch from imageHistories
|
|
127
|
+
const rebuildCurrentBatch = useCallback(() => {
|
|
128
|
+
const newBatch = createEmptyBatchState();
|
|
129
|
+
imageHistories.forEach(imageHistory => {
|
|
130
|
+
// Find current adjustment using currentHistoryEntryId
|
|
131
|
+
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
132
|
+
if (currentEntry) {
|
|
133
|
+
newBatch.allImages[imageHistory.imageId] = currentEntry.adjustment;
|
|
134
|
+
if (selectedIds.includes(imageHistory.imageId)) {
|
|
135
|
+
newBatch.currentSelection[imageHistory.imageId] = currentEntry.adjustment;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return newBatch;
|
|
140
|
+
}, [imageHistories, selectedIds]);
|
|
141
|
+
// Sync currentBatch with imageHistories
|
|
104
142
|
useEffect(() => {
|
|
105
|
-
setCurrentBatch(
|
|
106
|
-
}, [
|
|
143
|
+
setCurrentBatch(rebuildCurrentBatch());
|
|
144
|
+
}, [rebuildCurrentBatch]);
|
|
107
145
|
// Memory usage calculation
|
|
108
146
|
const getMemoryUsage = useCallback(() => {
|
|
109
147
|
try {
|
|
110
|
-
const
|
|
111
|
-
return
|
|
148
|
+
const historiesString = JSON.stringify(imageHistories);
|
|
149
|
+
return historiesString.length * 2; // Rough estimate: 2 bytes per character
|
|
112
150
|
}
|
|
113
151
|
catch (error) {
|
|
114
152
|
console.warn('Failed to estimate memory usage:', error);
|
|
115
|
-
return
|
|
153
|
+
return imageHistories.length * 100 * 1000; // Fallback estimate
|
|
116
154
|
}
|
|
117
|
-
}, [
|
|
118
|
-
// Trim
|
|
119
|
-
const
|
|
155
|
+
}, [imageHistories]);
|
|
156
|
+
// Trim individual image histories to specified size
|
|
157
|
+
const trimImageHistoriesToSize = useCallback((size) => {
|
|
120
158
|
if (size <= 0)
|
|
121
159
|
return;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
setCurrentIndex(prevIndex => {
|
|
129
|
-
const adjustedIndex = prevIndex - startIndex;
|
|
130
|
-
return Math.max(0, Math.min(adjustedIndex, trimmedHistory.length - 1));
|
|
131
|
-
});
|
|
132
|
-
return trimmedHistory;
|
|
133
|
-
});
|
|
160
|
+
setImageHistories(prevHistories => prevHistories.map(imageHistory => ({
|
|
161
|
+
...imageHistory,
|
|
162
|
+
history: imageHistory.history.length <= size
|
|
163
|
+
? imageHistory.history
|
|
164
|
+
: imageHistory.history.slice(-size) // Keep last 'size' entries
|
|
165
|
+
})));
|
|
134
166
|
}, []);
|
|
135
167
|
// Apply max size limit
|
|
136
168
|
const enforceMaxSize = useCallback(() => {
|
|
137
169
|
if (maxSizeRef.current === 'unlimited')
|
|
138
170
|
return;
|
|
139
171
|
const maxSize = maxSizeRef.current;
|
|
140
|
-
if (
|
|
141
|
-
|
|
172
|
+
if (typeof maxSize === 'number' && imageHistories.length > 0) {
|
|
173
|
+
const totalHistorySize = imageHistories.reduce((sum, h) => sum + h.history.length, 0);
|
|
174
|
+
if (totalHistorySize > maxSize * imageHistories.length) {
|
|
175
|
+
trimImageHistoriesToSize(maxSize);
|
|
176
|
+
}
|
|
142
177
|
}
|
|
143
|
-
}, [
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
}, [imageHistories, trimImageHistoriesToSize]);
|
|
179
|
+
// Apply adjustment deltas to selected images - with entry-based history and backend sync
|
|
180
|
+
const adjustSelected = useCallback(async (delta) => {
|
|
181
|
+
if (selectedIds.length === 0) {
|
|
182
|
+
if (devWarningsRef.current) {
|
|
183
|
+
console.warn('[useAdjustmentHistoryBatch] adjustSelected called with no selection');
|
|
184
|
+
}
|
|
148
185
|
return;
|
|
149
186
|
}
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
// Store backend operations to perform after state update
|
|
188
|
+
const backendOperations = [];
|
|
189
|
+
setImageHistories(prevHistories => {
|
|
190
|
+
return prevHistories.map(imageHistory => {
|
|
191
|
+
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
192
|
+
return imageHistory; // No change for unselected images
|
|
193
|
+
}
|
|
194
|
+
// Get current adjustment from current entry
|
|
195
|
+
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
196
|
+
const currentAdjustment = currentEntry?.adjustment || createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
197
|
+
// Apply deltas with clamping
|
|
198
|
+
const newAdjustment = { ...currentAdjustment };
|
|
199
|
+
Object.keys(delta).forEach(key => {
|
|
200
|
+
const deltaValue = delta[key];
|
|
201
|
+
if (typeof deltaValue === 'number') {
|
|
202
|
+
const currentValue = newAdjustment[key];
|
|
203
|
+
newAdjustment[key] = Math.max(-100, Math.min(100, currentValue + deltaValue));
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// Check if user is in the middle of history (not at latest state)
|
|
207
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
208
|
+
const isInMiddleOfHistory = currentEntryIndex < imageHistory.history.length - 1;
|
|
209
|
+
let replaceFromTaskId;
|
|
210
|
+
if (isInMiddleOfHistory) {
|
|
211
|
+
// If user is in middle of history, get the task ID of current position
|
|
212
|
+
replaceFromTaskId = currentEntry?.id;
|
|
213
|
+
}
|
|
214
|
+
// Generate new task ID for backend (same as entry ID)
|
|
215
|
+
const taskId = generateTaskId();
|
|
216
|
+
// Create new entry with task ID
|
|
217
|
+
const newEntryId = taskId; // Use the same ID for both entry and task
|
|
218
|
+
const newEntry = {
|
|
219
|
+
id: newEntryId,
|
|
220
|
+
adjustment: newAdjustment
|
|
221
|
+
};
|
|
222
|
+
// Prepare backend operation
|
|
223
|
+
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
224
|
+
backendOperations.push({
|
|
225
|
+
imageId: imageHistory.imageId,
|
|
226
|
+
taskId,
|
|
227
|
+
adjustment: newAdjustment,
|
|
228
|
+
replaceFromTaskId
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// Build new history
|
|
232
|
+
let newHistory;
|
|
233
|
+
if (isInMiddleOfHistory) {
|
|
234
|
+
// If in middle of history, truncate from current position and add new entry
|
|
235
|
+
newHistory = [...imageHistory.history.slice(0, currentEntryIndex + 1), newEntry];
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// If at end of history, just add new entry
|
|
239
|
+
newHistory = [...imageHistory.history, newEntry];
|
|
240
|
+
}
|
|
241
|
+
// Trim if needed
|
|
242
|
+
const maxSize = maxSizeRef.current;
|
|
243
|
+
const trimmedHistory = typeof maxSize === 'number' && newHistory.length > maxSize
|
|
244
|
+
? newHistory.slice(-maxSize)
|
|
245
|
+
: newHistory;
|
|
246
|
+
return {
|
|
247
|
+
...imageHistory,
|
|
248
|
+
history: trimmedHistory,
|
|
249
|
+
currentHistoryEntryId: newEntryId // Update current pointer
|
|
250
|
+
};
|
|
251
|
+
});
|
|
158
252
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
253
|
+
// Perform backend operations asynchronously
|
|
254
|
+
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
255
|
+
try {
|
|
256
|
+
const promises = backendOperations.map(async (operation) => {
|
|
257
|
+
await internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, {
|
|
258
|
+
gallery_id: operation.imageId,
|
|
259
|
+
task_id: operation.taskId,
|
|
260
|
+
color_adjustment: mapAdjustmentStateToColorAdjustment(operation.adjustment),
|
|
261
|
+
replace_from: operation.replaceFromTaskId
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
await Promise.all(promises);
|
|
265
|
+
if (devWarningsRef.current) {
|
|
266
|
+
console.log(`[useAdjustmentHistoryBatch] Synced ${backendOperations.length} adjustments to backend`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.error('[useAdjustmentHistoryBatch] Failed to sync adjustments to backend:', error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}, [selectedIds, internalOptions]);
|
|
274
|
+
// Apply preset values directly to selected images - preserves history for each image individually
|
|
275
|
+
const adjustSelectedWithPreset = useCallback(async (presetAdjustments) => {
|
|
276
|
+
if (selectedIds.length === 0) {
|
|
277
|
+
if (devWarningsRef.current) {
|
|
278
|
+
console.warn('[useAdjustmentHistoryBatch] adjustSelectedWithPreset called with no selection');
|
|
279
|
+
}
|
|
163
280
|
return;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
]
|
|
281
|
+
}
|
|
282
|
+
// Store backend operations to perform after state update
|
|
283
|
+
const backendOperations = [];
|
|
284
|
+
setImageHistories(prevHistories => {
|
|
285
|
+
return prevHistories.map(imageHistory => {
|
|
286
|
+
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
287
|
+
return imageHistory; // No change for unselected images
|
|
288
|
+
}
|
|
289
|
+
// Get current adjustment from current entry
|
|
290
|
+
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
291
|
+
const currentAdjustment = currentEntry?.adjustment || createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
292
|
+
// Apply preset values with clamping (same as adjustSelected logic)
|
|
293
|
+
const newAdjustment = { ...presetAdjustments };
|
|
294
|
+
Object.keys(newAdjustment).forEach(key => {
|
|
295
|
+
const presetValue = newAdjustment[key];
|
|
296
|
+
newAdjustment[key] = Math.max(-100, Math.min(100, presetValue));
|
|
297
|
+
});
|
|
298
|
+
// Check if user is in the middle of history (not at latest state)
|
|
299
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
300
|
+
const isInMiddleOfHistory = currentEntryIndex < imageHistory.history.length - 1;
|
|
301
|
+
let replaceFromTaskId;
|
|
302
|
+
if (isInMiddleOfHistory) {
|
|
303
|
+
// If user is in middle of history, get the task ID of current position
|
|
304
|
+
replaceFromTaskId = currentEntry?.id;
|
|
305
|
+
}
|
|
306
|
+
// Generate new task ID for backend (same as entry ID)
|
|
307
|
+
const taskId = generateTaskId();
|
|
308
|
+
// Create new entry with task ID
|
|
309
|
+
const newEntryId = taskId; // Use the same ID for both entry and task
|
|
310
|
+
const newEntry = {
|
|
311
|
+
id: newEntryId,
|
|
312
|
+
adjustment: newAdjustment
|
|
179
313
|
};
|
|
180
|
-
//
|
|
181
|
-
|
|
314
|
+
// Prepare backend operation
|
|
315
|
+
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
316
|
+
backendOperations.push({
|
|
317
|
+
imageId: imageHistory.imageId,
|
|
318
|
+
taskId,
|
|
319
|
+
adjustment: newAdjustment,
|
|
320
|
+
replaceFromTaskId
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// Build new history
|
|
324
|
+
let newHistory;
|
|
325
|
+
if (isInMiddleOfHistory) {
|
|
326
|
+
// If in middle of history, truncate from current position and add new entry
|
|
327
|
+
newHistory = [...imageHistory.history.slice(0, currentEntryIndex + 1), newEntry];
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// If at end of history, just add new entry
|
|
331
|
+
newHistory = [...imageHistory.history, newEntry];
|
|
332
|
+
}
|
|
333
|
+
// Trim if needed
|
|
334
|
+
const maxSize = maxSizeRef.current;
|
|
335
|
+
const trimmedHistory = typeof maxSize === 'number' && newHistory.length > maxSize
|
|
336
|
+
? newHistory.slice(-maxSize)
|
|
337
|
+
: newHistory;
|
|
338
|
+
return {
|
|
339
|
+
...imageHistory,
|
|
340
|
+
history: trimmedHistory,
|
|
341
|
+
currentHistoryEntryId: newEntryId // Update current pointer
|
|
342
|
+
};
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
// Perform backend operations asynchronously
|
|
346
|
+
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
347
|
+
try {
|
|
348
|
+
const promises = backendOperations.map(async (operation) => {
|
|
349
|
+
await internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, {
|
|
350
|
+
gallery_id: operation.imageId,
|
|
351
|
+
task_id: operation.taskId,
|
|
352
|
+
color_adjustment: mapAdjustmentStateToColorAdjustment(operation.adjustment),
|
|
353
|
+
replace_from: operation.replaceFromTaskId
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
await Promise.all(promises);
|
|
357
|
+
if (devWarningsRef.current) {
|
|
358
|
+
console.log(`[useAdjustmentHistoryBatch] Synced ${backendOperations.length} preset adjustments to backend`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.error('[useAdjustmentHistoryBatch] Failed to sync preset adjustments to backend:', error);
|
|
182
363
|
}
|
|
183
364
|
}
|
|
184
|
-
|
|
185
|
-
}, [selectedIds, currentBatch, pushBatchState]);
|
|
365
|
+
}, [selectedIds, internalOptions]);
|
|
186
366
|
// Set specific adjustment states for specified images (removed since not needed)
|
|
187
|
-
// Undo last changes to selected images
|
|
188
|
-
const undo = useCallback(() => {
|
|
189
|
-
if (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
currentSelection: { ...currentBatch.currentSelection },
|
|
193
|
-
allImages: { ...currentBatch.allImages },
|
|
194
|
-
initialStates: { ...currentBatch.initialStates }
|
|
195
|
-
};
|
|
196
|
-
// Only restore adjustments for currently selected images
|
|
197
|
-
for (const imageId of selectedIds) {
|
|
198
|
-
if (previousBatch.allImages[imageId] && newBatch.currentSelection[imageId]) {
|
|
199
|
-
// Restore from previous allImages state (not currentSelection)
|
|
200
|
-
newBatch.currentSelection[imageId] = { ...previousBatch.allImages[imageId] };
|
|
201
|
-
newBatch.allImages[imageId] = { ...previousBatch.allImages[imageId] };
|
|
202
|
-
}
|
|
367
|
+
// Undo last changes to selected images - entry-based history version with backend sync
|
|
368
|
+
const undo = useCallback(async () => {
|
|
369
|
+
if (selectedIds.length === 0) {
|
|
370
|
+
if (devWarningsRef.current) {
|
|
371
|
+
console.warn('[useAdjustmentHistoryBatch] Cannot undo - no images selected');
|
|
203
372
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
let anyChanges = false;
|
|
376
|
+
const backendOperations = [];
|
|
377
|
+
setImageHistories(prevHistories => {
|
|
378
|
+
return prevHistories.map(imageHistory => {
|
|
379
|
+
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
380
|
+
return imageHistory; // No change for unselected images
|
|
381
|
+
}
|
|
382
|
+
// Find current entry index
|
|
383
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
384
|
+
if (currentEntryIndex <= 0) {
|
|
385
|
+
return imageHistory; // Cannot undo if at first entry or entry not found
|
|
386
|
+
}
|
|
387
|
+
// Move to previous entry
|
|
388
|
+
const previousEntry = imageHistory.history[currentEntryIndex - 1];
|
|
389
|
+
anyChanges = true;
|
|
390
|
+
// Prepare backend sync operation
|
|
391
|
+
if (previousEntry.id && internalOptions.controller && internalOptions.firebaseUid) {
|
|
392
|
+
backendOperations.push({
|
|
393
|
+
imageId: imageHistory.imageId,
|
|
394
|
+
taskId: previousEntry.id
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
...imageHistory,
|
|
399
|
+
currentHistoryEntryId: previousEntry.id
|
|
400
|
+
};
|
|
212
401
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
402
|
+
});
|
|
403
|
+
// Sync with backend
|
|
404
|
+
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
405
|
+
try {
|
|
406
|
+
const promises = backendOperations.map(async (operation) => {
|
|
407
|
+
await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, operation.imageId, operation.taskId);
|
|
217
408
|
});
|
|
409
|
+
await Promise.all(promises);
|
|
410
|
+
if (devWarningsRef.current) {
|
|
411
|
+
console.log(`[useAdjustmentHistoryBatch] Synced ${backendOperations.length} undo operations to backend`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
console.error('[useAdjustmentHistoryBatch] Failed to sync undo to backend:', error);
|
|
218
416
|
}
|
|
219
417
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
};
|
|
230
|
-
// Only restore adjustments for currently selected images
|
|
231
|
-
for (const imageId of selectedIds) {
|
|
232
|
-
if (nextBatch.allImages[imageId] && newBatch.currentSelection[imageId]) {
|
|
233
|
-
// Restore from next allImages state (not currentSelection)
|
|
234
|
-
newBatch.currentSelection[imageId] = { ...nextBatch.allImages[imageId] };
|
|
235
|
-
newBatch.allImages[imageId] = { ...nextBatch.allImages[imageId] };
|
|
236
|
-
}
|
|
418
|
+
if (!anyChanges && devWarningsRef.current) {
|
|
419
|
+
console.warn('[useAdjustmentHistoryBatch] Undo skipped - no changes to undo for selected images');
|
|
420
|
+
}
|
|
421
|
+
}, [selectedIds, internalOptions]);
|
|
422
|
+
// Redo next changes to selected images - entry-based history version with backend sync
|
|
423
|
+
const redo = useCallback(async () => {
|
|
424
|
+
if (selectedIds.length === 0) {
|
|
425
|
+
if (devWarningsRef.current) {
|
|
426
|
+
console.warn('[useAdjustmentHistoryBatch] Cannot redo - no images selected');
|
|
237
427
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
let anyChanges = false;
|
|
431
|
+
const backendOperations = [];
|
|
432
|
+
setImageHistories(prevHistories => {
|
|
433
|
+
return prevHistories.map(imageHistory => {
|
|
434
|
+
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
435
|
+
return imageHistory; // No change for unselected images
|
|
436
|
+
}
|
|
437
|
+
// Find current entry index
|
|
438
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
439
|
+
if (currentEntryIndex >= imageHistory.history.length - 1 || currentEntryIndex === -1) {
|
|
440
|
+
return imageHistory; // Cannot redo if at last entry or entry not found
|
|
441
|
+
}
|
|
442
|
+
// Move to next entry
|
|
443
|
+
const nextEntry = imageHistory.history[currentEntryIndex + 1];
|
|
444
|
+
anyChanges = true;
|
|
445
|
+
// Prepare backend sync operation
|
|
446
|
+
if (nextEntry.id && internalOptions.controller && internalOptions.firebaseUid) {
|
|
447
|
+
backendOperations.push({
|
|
448
|
+
imageId: imageHistory.imageId,
|
|
449
|
+
taskId: nextEntry.id
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
...imageHistory,
|
|
454
|
+
currentHistoryEntryId: nextEntry.id
|
|
455
|
+
};
|
|
246
456
|
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
457
|
+
});
|
|
458
|
+
// Sync with backend
|
|
459
|
+
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
460
|
+
try {
|
|
461
|
+
const promises = backendOperations.map(async (operation) => {
|
|
462
|
+
await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, operation.imageId, operation.taskId);
|
|
251
463
|
});
|
|
464
|
+
await Promise.all(promises);
|
|
465
|
+
if (devWarningsRef.current) {
|
|
466
|
+
console.log(`[useAdjustmentHistoryBatch] Synced ${backendOperations.length} redo operations to backend`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
console.error('[useAdjustmentHistoryBatch] Failed to sync redo to backend:', error);
|
|
252
471
|
}
|
|
253
472
|
}
|
|
254
|
-
|
|
255
|
-
|
|
473
|
+
if (!anyChanges && devWarningsRef.current) {
|
|
474
|
+
console.warn('[useAdjustmentHistoryBatch] Redo skipped - no changes to redo for selected images');
|
|
475
|
+
}
|
|
476
|
+
}, [selectedIds, internalOptions]);
|
|
477
|
+
// Check if any selected image can be undone
|
|
478
|
+
const canUndoSelected = useCallback(() => {
|
|
479
|
+
return selectedIds.some(imageId => {
|
|
480
|
+
const imageHistory = imageHistories.find(h => h.imageId === imageId);
|
|
481
|
+
if (!imageHistory)
|
|
482
|
+
return false;
|
|
483
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
484
|
+
return currentEntryIndex > 0;
|
|
485
|
+
});
|
|
486
|
+
}, [selectedIds, imageHistories]);
|
|
487
|
+
// Check if any selected image can be redone
|
|
488
|
+
const canRedoSelected = useCallback(() => {
|
|
489
|
+
return selectedIds.some(imageId => {
|
|
490
|
+
const imageHistory = imageHistories.find(h => h.imageId === imageId);
|
|
491
|
+
if (!imageHistory)
|
|
492
|
+
return false;
|
|
493
|
+
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
494
|
+
return currentEntryIndex >= 0 && currentEntryIndex < imageHistory.history.length - 1;
|
|
495
|
+
});
|
|
496
|
+
}, [selectedIds, imageHistories]);
|
|
497
|
+
// Reset selected images to default state - entry-based history version
|
|
256
498
|
const reset = useCallback((imageIds) => {
|
|
257
499
|
const idsToReset = imageIds || selectedIds;
|
|
258
500
|
if (idsToReset.length === 0)
|
|
259
501
|
return;
|
|
260
|
-
const newBatch = {
|
|
261
|
-
currentSelection: { ...currentBatch.currentSelection },
|
|
262
|
-
allImages: { ...currentBatch.allImages },
|
|
263
|
-
initialStates: { ...currentBatch.initialStates }
|
|
264
|
-
};
|
|
265
502
|
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
503
|
+
setImageHistories(prevHistories => {
|
|
504
|
+
return prevHistories.map(imageHistory => {
|
|
505
|
+
if (!idsToReset.includes(imageHistory.imageId)) {
|
|
506
|
+
return imageHistory; // No change for images not being reset
|
|
507
|
+
}
|
|
508
|
+
// Create new entry with default state
|
|
509
|
+
const newEntryId = generateEntryId();
|
|
510
|
+
const newEntry = {
|
|
511
|
+
id: newEntryId,
|
|
512
|
+
adjustment: defaultState
|
|
513
|
+
};
|
|
514
|
+
// Add to this image's history
|
|
515
|
+
const newHistory = [...imageHistory.history, newEntry];
|
|
516
|
+
// Trim if needed
|
|
517
|
+
const maxSize = maxSizeRef.current;
|
|
518
|
+
const trimmedHistory = typeof maxSize === 'number' && newHistory.length > maxSize
|
|
519
|
+
? newHistory.slice(-maxSize)
|
|
520
|
+
: newHistory;
|
|
521
|
+
return {
|
|
522
|
+
...imageHistory,
|
|
523
|
+
history: trimmedHistory,
|
|
524
|
+
currentHistoryEntryId: newEntryId
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}, [selectedIds, internalOptions.defaultAdjustmentState]);
|
|
274
529
|
// Selection management with initial adjustments - single state update
|
|
275
530
|
const setSelection = useCallback((configs) => {
|
|
276
531
|
const imageIds = configs.map(config => config.imageId);
|
|
@@ -323,54 +578,217 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
323
578
|
});
|
|
324
579
|
}
|
|
325
580
|
}, [allImageIds, currentBatch, internalOptions.defaultAdjustmentState, internalOptions.devWarnings]);
|
|
326
|
-
// Sync adjustments for specific images
|
|
327
|
-
const syncAdjustment = useCallback((configs) => {
|
|
581
|
+
// Sync adjustments for specific images - loads full history from backend
|
|
582
|
+
const syncAdjustment = useCallback(async (configs) => {
|
|
328
583
|
if (configs.length === 0)
|
|
329
584
|
return;
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
585
|
+
// If controller is available, load full history from backend
|
|
586
|
+
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
587
|
+
try {
|
|
588
|
+
const historyPromises = configs.map(async (config) => {
|
|
589
|
+
try {
|
|
590
|
+
const historyResponse = await internalOptions.controller.getEditorHistory(internalOptions.firebaseUid, config.imageId);
|
|
591
|
+
return {
|
|
592
|
+
imageId: config.imageId,
|
|
593
|
+
backendHistory: historyResponse.history || [],
|
|
594
|
+
fallbackAdjustment: config.adjustment
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
console.warn(`[useAdjustmentHistoryBatch] Failed to load history for image ${config.imageId}:`, error);
|
|
599
|
+
return {
|
|
600
|
+
imageId: config.imageId,
|
|
601
|
+
backendHistory: [],
|
|
602
|
+
fallbackAdjustment: config.adjustment
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
const historyResults = await Promise.all(historyPromises);
|
|
607
|
+
setImageHistories(prevHistories => {
|
|
608
|
+
const updatedHistories = [...prevHistories];
|
|
609
|
+
for (const result of historyResults) {
|
|
610
|
+
const { imageId, backendHistory, fallbackAdjustment } = result;
|
|
611
|
+
const existingIndex = updatedHistories.findIndex(h => h.imageId === imageId);
|
|
612
|
+
if (backendHistory.length > 0) {
|
|
613
|
+
// Convert backend history to local history entries
|
|
614
|
+
const historyEntries = backendHistory.map((entry, index) => ({
|
|
615
|
+
id: entry.task_id, // Use backend task_id as our entry id
|
|
616
|
+
adjustment: mapColorAdjustmentToAdjustmentState ?
|
|
617
|
+
mapColorAdjustmentToAdjustmentState(entry.editor_config.color_adjustment) :
|
|
618
|
+
createDefaultAdjustmentState(internalOptions.defaultAdjustmentState)
|
|
619
|
+
}));
|
|
620
|
+
const newImageHistory = {
|
|
621
|
+
imageId,
|
|
622
|
+
currentHistoryEntryId: historyEntries[historyEntries.length - 1].id, // Point to latest entry
|
|
623
|
+
history: historyEntries
|
|
624
|
+
};
|
|
625
|
+
if (existingIndex >= 0) {
|
|
626
|
+
updatedHistories[existingIndex] = newImageHistory;
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
updatedHistories.push(newImageHistory);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
// No backend history, use fallback adjustment or default
|
|
634
|
+
const adjustment = fallbackAdjustment ? {
|
|
635
|
+
...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
|
|
636
|
+
...fallbackAdjustment
|
|
637
|
+
} : createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
638
|
+
const entryId = generateEntryId();
|
|
639
|
+
const entry = {
|
|
640
|
+
id: entryId,
|
|
641
|
+
adjustment
|
|
642
|
+
};
|
|
643
|
+
const newImageHistory = {
|
|
644
|
+
imageId,
|
|
645
|
+
currentHistoryEntryId: entryId,
|
|
646
|
+
history: [entry]
|
|
647
|
+
};
|
|
648
|
+
if (existingIndex >= 0) {
|
|
649
|
+
updatedHistories[existingIndex] = newImageHistory;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
updatedHistories.push(newImageHistory);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return updatedHistories;
|
|
657
|
+
});
|
|
658
|
+
if (internalOptions.devWarnings) {
|
|
659
|
+
const syncedImageIds = configs.map(c => c.imageId);
|
|
660
|
+
const totalHistoryEntries = historyResults.reduce((sum, result) => sum + result.backendHistory.length, 0);
|
|
661
|
+
console.log('[useAdjustmentHistoryBatch] Synced adjustments with backend history', {
|
|
662
|
+
syncedImages: syncedImageIds,
|
|
663
|
+
totalHistoryEntries,
|
|
664
|
+
historyLoaded: true
|
|
665
|
+
});
|
|
350
666
|
}
|
|
351
667
|
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
console.error('[useAdjustmentHistoryBatch] Failed to sync with backend, falling back to local only:', error);
|
|
670
|
+
// Fall back to local-only sync
|
|
671
|
+
syncAdjustmentLocal(configs);
|
|
672
|
+
}
|
|
352
673
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
setCurrentIndex(0);
|
|
357
|
-
setCurrentBatch(newBatch);
|
|
358
|
-
if (internalOptions.devWarnings) {
|
|
359
|
-
const syncedImageIds = configs.map(c => c.imageId);
|
|
360
|
-
console.log('useAdjustmentHistoryBatch: Synced adjustments (history cleared)', {
|
|
361
|
-
syncedImages: syncedImageIds,
|
|
362
|
-
historyCleared: true
|
|
363
|
-
});
|
|
674
|
+
else {
|
|
675
|
+
// No controller available, use local-only sync
|
|
676
|
+
syncAdjustmentLocal(configs);
|
|
364
677
|
}
|
|
365
|
-
|
|
678
|
+
// Update allImageIds to include any new images
|
|
679
|
+
const newImageIds = configs.map(c => c.imageId);
|
|
680
|
+
setAllImageIds(prev => {
|
|
681
|
+
const combined = Array.from(new Set([...prev, ...newImageIds]));
|
|
682
|
+
return combined;
|
|
683
|
+
});
|
|
684
|
+
}, [internalOptions]);
|
|
685
|
+
// Local-only sync for fallback
|
|
686
|
+
const syncAdjustmentLocal = useCallback((configs) => {
|
|
687
|
+
setImageHistories(prevHistories => {
|
|
688
|
+
const updatedHistories = [...prevHistories];
|
|
689
|
+
for (const config of configs) {
|
|
690
|
+
const { imageId, adjustment } = config;
|
|
691
|
+
const existingIndex = updatedHistories.findIndex(h => h.imageId === imageId);
|
|
692
|
+
if (adjustment) {
|
|
693
|
+
const fullAdjustment = {
|
|
694
|
+
...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
|
|
695
|
+
...adjustment
|
|
696
|
+
};
|
|
697
|
+
const entryId = generateEntryId();
|
|
698
|
+
const entry = {
|
|
699
|
+
id: entryId,
|
|
700
|
+
adjustment: fullAdjustment
|
|
701
|
+
};
|
|
702
|
+
if (existingIndex >= 0) {
|
|
703
|
+
// Update existing image history, replace with new adjustment as initial state
|
|
704
|
+
updatedHistories[existingIndex] = {
|
|
705
|
+
imageId,
|
|
706
|
+
currentHistoryEntryId: entryId,
|
|
707
|
+
history: [entry] // Reset history with synced state
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// Add new image history
|
|
712
|
+
updatedHistories.push({
|
|
713
|
+
imageId,
|
|
714
|
+
currentHistoryEntryId: entryId,
|
|
715
|
+
history: [entry]
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else if (existingIndex < 0) {
|
|
720
|
+
// Add new image with default state
|
|
721
|
+
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
722
|
+
const entryId = generateEntryId();
|
|
723
|
+
const entry = {
|
|
724
|
+
id: entryId,
|
|
725
|
+
adjustment: defaultState
|
|
726
|
+
};
|
|
727
|
+
updatedHistories.push({
|
|
728
|
+
imageId,
|
|
729
|
+
currentHistoryEntryId: entryId,
|
|
730
|
+
history: [entry]
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return updatedHistories;
|
|
735
|
+
});
|
|
736
|
+
}, [internalOptions]);
|
|
366
737
|
const toggleSelection = useCallback((imageId) => {
|
|
367
|
-
setSelectedIds(prev =>
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
738
|
+
setSelectedIds(prev => {
|
|
739
|
+
const isCurrentlySelected = prev.includes(imageId);
|
|
740
|
+
const newSelectedIds = isCurrentlySelected
|
|
741
|
+
? prev.filter(id => id !== imageId)
|
|
742
|
+
: [...prev, imageId];
|
|
743
|
+
// Update currentSelection in batch state
|
|
744
|
+
const newBatch = {
|
|
745
|
+
currentSelection: { ...currentBatch.currentSelection },
|
|
746
|
+
allImages: { ...currentBatch.allImages },
|
|
747
|
+
initialStates: { ...currentBatch.initialStates }
|
|
748
|
+
};
|
|
749
|
+
if (isCurrentlySelected) {
|
|
750
|
+
// Remove from currentSelection
|
|
751
|
+
delete newBatch.currentSelection[imageId];
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
// Add to currentSelection - use existing state from allImages or create default
|
|
755
|
+
if (currentBatch.allImages[imageId]) {
|
|
756
|
+
newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
// New image - create default state
|
|
760
|
+
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
761
|
+
newBatch.currentSelection[imageId] = { ...defaultState };
|
|
762
|
+
newBatch.allImages[imageId] = { ...defaultState };
|
|
763
|
+
newBatch.initialStates[imageId] = { ...defaultState };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
setCurrentBatch(newBatch);
|
|
767
|
+
return newSelectedIds;
|
|
768
|
+
});
|
|
769
|
+
}, [currentBatch, internalOptions.defaultAdjustmentState]);
|
|
371
770
|
const selectAll = useCallback(() => {
|
|
372
771
|
setSelectedIds([...allImageIds]);
|
|
373
|
-
|
|
772
|
+
// Update currentSelection to include all images
|
|
773
|
+
const newBatch = {
|
|
774
|
+
currentSelection: {},
|
|
775
|
+
allImages: { ...currentBatch.allImages },
|
|
776
|
+
initialStates: { ...currentBatch.initialStates }
|
|
777
|
+
};
|
|
778
|
+
for (const imageId of allImageIds) {
|
|
779
|
+
if (currentBatch.allImages[imageId]) {
|
|
780
|
+
newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
// New image - create default state
|
|
784
|
+
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
785
|
+
newBatch.currentSelection[imageId] = { ...defaultState };
|
|
786
|
+
newBatch.allImages[imageId] = { ...defaultState };
|
|
787
|
+
newBatch.initialStates[imageId] = { ...defaultState };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
setCurrentBatch(newBatch);
|
|
791
|
+
}, [allImageIds, currentBatch, internalOptions.defaultAdjustmentState]);
|
|
374
792
|
const clearSelection = useCallback(() => {
|
|
375
793
|
setSelectedIds([]);
|
|
376
794
|
// Clear currentSelection but keep allImages and initialStates
|
|
@@ -381,18 +799,16 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
381
799
|
};
|
|
382
800
|
setCurrentBatch(newBatch);
|
|
383
801
|
}, [currentBatch]);
|
|
384
|
-
//
|
|
802
|
+
// Jump to specific index - not applicable in per-image history
|
|
385
803
|
const jumpToIndex = useCallback((index) => {
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
setCurrentBatch(history[index]);
|
|
804
|
+
if (devWarningsRef.current) {
|
|
805
|
+
console.warn('[useAdjustmentHistoryBatch] jumpToIndex not supported in per-image history mode');
|
|
389
806
|
}
|
|
390
|
-
}, [
|
|
807
|
+
}, []);
|
|
808
|
+
// Clear all history and start fresh
|
|
391
809
|
const clearHistory = useCallback(() => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
setCurrentIndex(0);
|
|
395
|
-
setCurrentBatch(freshBatch);
|
|
810
|
+
setImageHistories([]);
|
|
811
|
+
setCurrentBatch(createEmptyBatchState());
|
|
396
812
|
setAllImageIds([]);
|
|
397
813
|
setSelectedIds([]);
|
|
398
814
|
}, []);
|
|
@@ -403,32 +819,41 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
403
819
|
initialStates: { ...currentBatch.initialStates }
|
|
404
820
|
};
|
|
405
821
|
}, [currentBatch]);
|
|
822
|
+
// Sync entire batch state - adapted for entry-based history
|
|
406
823
|
const syncBatch = useCallback((newBatch, targetIndex) => {
|
|
407
824
|
// Validate input
|
|
408
825
|
if (!newBatch || typeof newBatch !== 'object' || !newBatch.currentSelection || !newBatch.allImages || !newBatch.initialStates) {
|
|
409
826
|
console.warn('syncBatch: newBatch must be a valid BatchAdjustmentState object with currentSelection, allImages, and initialStates');
|
|
410
827
|
return;
|
|
411
828
|
}
|
|
412
|
-
//
|
|
829
|
+
// Convert batch state to entry-based histories
|
|
830
|
+
const newImageHistories = [];
|
|
831
|
+
Object.entries(newBatch.allImages).forEach(([imageId, adjustment]) => {
|
|
832
|
+
const entryId = generateEntryId();
|
|
833
|
+
const entry = {
|
|
834
|
+
id: entryId,
|
|
835
|
+
adjustment
|
|
836
|
+
};
|
|
837
|
+
newImageHistories.push({
|
|
838
|
+
imageId,
|
|
839
|
+
currentHistoryEntryId: entryId,
|
|
840
|
+
history: [entry] // Start with current state as single history entry
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
// Update state
|
|
844
|
+
setImageHistories(newImageHistories);
|
|
413
845
|
setCurrentBatch({
|
|
414
846
|
currentSelection: { ...newBatch.currentSelection },
|
|
415
847
|
allImages: { ...newBatch.allImages },
|
|
416
848
|
initialStates: { ...newBatch.initialStates }
|
|
417
849
|
});
|
|
418
|
-
//
|
|
419
|
-
setHistory([{
|
|
420
|
-
currentSelection: { ...newBatch.currentSelection },
|
|
421
|
-
allImages: { ...newBatch.allImages },
|
|
422
|
-
initialStates: { ...newBatch.initialStates }
|
|
423
|
-
}]);
|
|
424
|
-
setCurrentIndex(0);
|
|
425
|
-
// Update image tracking
|
|
850
|
+
// Update tracking
|
|
426
851
|
const allImageIds = Object.keys(newBatch.allImages);
|
|
427
852
|
const selectedImageIds = Object.keys(newBatch.currentSelection);
|
|
428
853
|
setAllImageIds(allImageIds);
|
|
429
854
|
setSelectedIds(selectedImageIds);
|
|
430
855
|
if (devWarningsRef.current) {
|
|
431
|
-
console.log('
|
|
856
|
+
console.log('[useAdjustmentHistoryBatch] Synchronized batch state to entry-based history', {
|
|
432
857
|
totalImages: allImageIds.length,
|
|
433
858
|
selectedImages: selectedImageIds.length
|
|
434
859
|
});
|
|
@@ -441,19 +866,20 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
441
866
|
enforceMaxSize();
|
|
442
867
|
}
|
|
443
868
|
}, [enforceMaxSize]);
|
|
444
|
-
// History info object
|
|
869
|
+
// History info object - updated for per-image history
|
|
445
870
|
const historyInfo = useMemo(() => ({
|
|
446
|
-
canUndo:
|
|
447
|
-
canRedo:
|
|
448
|
-
currentIndex,
|
|
449
|
-
totalStates: history.length,
|
|
871
|
+
canUndo: canUndoSelected(),
|
|
872
|
+
canRedo: canRedoSelected(),
|
|
873
|
+
currentIndex: 0, // Not applicable in per-image history
|
|
874
|
+
totalStates: imageHistories.reduce((sum, h) => sum + h.history.length, 0),
|
|
450
875
|
selectedCount: selectedIds.length,
|
|
451
876
|
totalImages: allImageIds.length,
|
|
452
877
|
historySize: getMemoryUsage()
|
|
453
|
-
}), [
|
|
878
|
+
}), [canUndoSelected, canRedoSelected, imageHistories, selectedIds.length, allImageIds.length, getMemoryUsage]);
|
|
454
879
|
// Actions object - stabilized with useMemo
|
|
455
880
|
const actions = useMemo(() => ({
|
|
456
881
|
adjustSelected,
|
|
882
|
+
adjustSelectedWithPreset,
|
|
457
883
|
undo,
|
|
458
884
|
redo,
|
|
459
885
|
reset,
|
|
@@ -467,7 +893,7 @@ export function useAdjustmentHistoryBatch(options = {}) {
|
|
|
467
893
|
getCurrentBatch,
|
|
468
894
|
syncBatch
|
|
469
895
|
}), [
|
|
470
|
-
adjustSelected, undo, redo, reset,
|
|
896
|
+
adjustSelected, adjustSelectedWithPreset, undo, redo, reset,
|
|
471
897
|
setSelection, syncAdjustment, toggleSelection, selectAll, clearSelection,
|
|
472
898
|
jumpToIndex, clearHistory, getCurrentBatch, syncBatch
|
|
473
899
|
]);
|