@yogiswara/honcho-editor-ui 2.7.13 â 2.7.14
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/package.json +1 -1
- package/dist/color.d.ts +0 -9
- package/dist/color.js +0 -9
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.d.ts +0 -8
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.js +0 -28
- package/dist/components/editor/GalleryAlbum/ImageItem.d.ts +0 -10
- package/dist/components/editor/GalleryAlbum/ImageItem.js +0 -81
- package/dist/components/editor/HAccordionAspectRatio.d.ts +0 -14
- package/dist/components/editor/HAccordionAspectRatio.js +0 -102
- package/dist/components/editor/HAccordionColor.d.ts +0 -16
- package/dist/components/editor/HAccordionColor.js +0 -282
- package/dist/components/editor/HAccordionColorAdjustment.d.ts +0 -35
- package/dist/components/editor/HAccordionColorAdjustment.js +0 -31
- package/dist/components/editor/HAccordionDetails.d.ts +0 -12
- package/dist/components/editor/HAccordionDetails.js +0 -157
- package/dist/components/editor/HAccordionLight.d.ts +0 -20
- package/dist/components/editor/HAccordionLight.js +0 -414
- package/dist/components/editor/HAccordionPreset.d.ts +0 -23
- package/dist/components/editor/HAccordionPreset.js +0 -50
- package/dist/components/editor/HAlertBox.d.ts +0 -8
- package/dist/components/editor/HAlertBox.js +0 -55
- package/dist/components/editor/HAspectRatioMobile.d.ts +0 -0
- package/dist/components/editor/HAspectRatioMobile.js +0 -1
- package/dist/components/editor/HBulkAccordionColorAdjustment.d.ts +0 -55
- package/dist/components/editor/HBulkAccordionColorAdjustment.js +0 -31
- package/dist/components/editor/HBulkAccordionColorAdjustmentColors.d.ts +0 -20
- package/dist/components/editor/HBulkAccordionColorAdjustmentColors.js +0 -121
- package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.d.ts +0 -12
- package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.js +0 -65
- package/dist/components/editor/HBulkAccordionColorAdjustmentLight.d.ts +0 -28
- package/dist/components/editor/HBulkAccordionColorAdjustmentLight.js +0 -177
- package/dist/components/editor/HBulkColorAdjustmentMobile.d.ts +0 -53
- package/dist/components/editor/HBulkColorAdjustmentMobile.js +0 -16
- package/dist/components/editor/HBulkColorMobile.d.ts +0 -20
- package/dist/components/editor/HBulkColorMobile.js +0 -121
- package/dist/components/editor/HBulkDetailsMobile.d.ts +0 -12
- package/dist/components/editor/HBulkDetailsMobile.js +0 -65
- package/dist/components/editor/HBulkLightMobile.d.ts +0 -28
- package/dist/components/editor/HBulkLightMobile.js +0 -192
- package/dist/components/editor/HBulkPreset.d.ts +0 -24
- package/dist/components/editor/HBulkPreset.js +0 -43
- package/dist/components/editor/HBulkPresetMobile.d.ts +0 -15
- package/dist/components/editor/HBulkPresetMobile.js +0 -26
- package/dist/components/editor/HDialogBox.d.ts +0 -18
- package/dist/components/editor/HDialogBox.js +0 -51
- package/dist/components/editor/HDialogCopy.d.ts +0 -40
- package/dist/components/editor/HDialogCopy.js +0 -80
- package/dist/components/editor/HFooter.d.ts +0 -12
- package/dist/components/editor/HFooter.js +0 -24
- package/dist/components/editor/HHeaderEditor.d.ts +0 -17
- package/dist/components/editor/HHeaderEditor.js +0 -36
- package/dist/components/editor/HImageEditorBulkDekstop.d.ts +0 -15
- package/dist/components/editor/HImageEditorBulkDekstop.js +0 -29
- package/dist/components/editor/HImageEditorBulkMobile.d.ts +0 -72
- package/dist/components/editor/HImageEditorBulkMobile.js +0 -81
- package/dist/components/editor/HImageEditorDekstop.d.ts +0 -15
- package/dist/components/editor/HImageEditorDekstop.js +0 -29
- package/dist/components/editor/HImageEditorMobile.d.ts +0 -51
- package/dist/components/editor/HImageEditorMobile.js +0 -92
- package/dist/components/editor/HImageEditorMobileLayout.d.ts +0 -14
- package/dist/components/editor/HImageEditorMobileLayout.js +0 -58
- package/dist/components/editor/HModalEditorDekstop.d.ts +0 -13
- package/dist/components/editor/HModalEditorDekstop.js +0 -22
- package/dist/components/editor/HModalMobile.d.ts +0 -13
- package/dist/components/editor/HModalMobile.js +0 -7
- package/dist/components/editor/HPresetDelete.d.ts +0 -7
- package/dist/components/editor/HPresetDelete.js +0 -7
- package/dist/components/editor/HPresetOptionMenu.d.ts +0 -9
- package/dist/components/editor/HPresetOptionMenu.js +0 -20
- package/dist/components/editor/HSliderColorMobile.d.ts +0 -16
- package/dist/components/editor/HSliderColorMobile.js +0 -270
- package/dist/components/editor/HSliderDetailsMobile.d.ts +0 -12
- package/dist/components/editor/HSliderDetailsMobile.js +0 -154
- package/dist/components/editor/HSliderLightMobile.d.ts +0 -20
- package/dist/components/editor/HSliderLightMobile.js +0 -420
- package/dist/components/editor/HTabAspectRatioMobile.d.ts +0 -0
- package/dist/components/editor/HTabAspectRatioMobile.js +0 -1
- package/dist/components/editor/HTabColorAdjustmentMobile.d.ts +0 -33
- package/dist/components/editor/HTabColorAdjustmentMobile.js +0 -16
- package/dist/components/editor/HTabPresetMobile.d.ts +0 -14
- package/dist/components/editor/HTabPresetMobile.js +0 -10
- package/dist/components/editor/HTextField.d.ts +0 -14
- package/dist/components/editor/HTextField.js +0 -51
- package/dist/components/editor/HWatermarkView.d.ts +0 -6
- package/dist/components/editor/HWatermarkView.js +0 -16
- package/dist/components/editor/svg/Tick.d.ts +0 -2
- package/dist/components/editor/svg/Tick.js +0 -6
- package/dist/components/modal/HModalDialog.d.ts +0 -12
- package/dist/components/modal/HModalDialog.js +0 -18
- package/dist/components/modal/HModalRename.d.ts +0 -14
- package/dist/components/modal/HModalRename.js +0 -35
- package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +0 -3
- package/dist/hooks/demo/HonchoEditorBulkDemo.js +0 -410
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +0 -3
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +0 -354
- package/dist/hooks/demo/index.d.ts +0 -2
- package/dist/hooks/demo/index.js +0 -2
- package/dist/hooks/editor/type.d.ts +0 -174
- package/dist/hooks/editor/type.js +0 -1
- package/dist/hooks/editor/useHonchoEditorBulk.d.ts +0 -96
- package/dist/hooks/editor/useHonchoEditorBulk.js +0 -427
- package/dist/hooks/editor/useHonchoEditorSingle.d.ts +0 -44
- package/dist/hooks/editor/useHonchoEditorSingle.js +0 -162
- package/dist/hooks/useAdjustmentHistory.d.ts +0 -97
- package/dist/hooks/useAdjustmentHistory.js +0 -493
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +0 -177
- package/dist/hooks/useAdjustmentHistoryBatch.js +0 -1189
- package/dist/hooks/useGallerySwipe.d.ts +0 -36
- package/dist/hooks/useGallerySwipe.js +0 -344
- package/dist/hooks/usePaging.d.ts +0 -89
- package/dist/hooks/usePaging.js +0 -211
- package/dist/hooks/usePreset.d.ts +0 -82
- package/dist/hooks/usePreset.js +0 -344
- package/dist/index.d.ts +0 -41
- package/dist/index.js +0 -44
- package/dist/lib/context/EditorContext.d.ts +0 -28
- package/dist/lib/context/EditorContext.js +0 -60
- package/dist/lib/context/EditorProcessingService.d.ts +0 -36
- package/dist/lib/context/EditorProcessingService.js +0 -249
- package/dist/lib/editor/honcho-editor.d.ts +0 -324
- package/dist/lib/editor/honcho-editor.js +0 -825
- package/dist/lib/hooks/useEditor.d.ts +0 -22
- package/dist/lib/hooks/useEditor.js +0 -35
- package/dist/lib/hooks/useEditorHeadless.d.ts +0 -34
- package/dist/lib/hooks/useEditorHeadless.js +0 -207
- package/dist/lib/hooks/useImageProcessor.d.ts +0 -18
- package/dist/lib/hooks/useImageProcessor.js +0 -113
- package/dist/setupTests.d.ts +0 -1
- package/dist/setupTests.js +0 -1
- package/dist/themes/colors.d.ts +0 -12
- package/dist/themes/colors.js +0 -12
- package/dist/themes/honchoTheme.d.ts +0 -25
- package/dist/themes/honchoTheme.js +0 -94
- package/dist/utils/adjustment.d.ts +0 -6
- package/dist/utils/adjustment.js +0 -48
- package/dist/utils/imageLoader.d.ts +0 -11
- package/dist/utils/imageLoader.js +0 -48
- package/dist/utils/isMobile.d.ts +0 -1
- package/dist/utils/isMobile.js +0 -5
|
@@ -1,1189 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
|
-
import { mapAdjustmentStateToColorAdjustment, mapColorAdjustmentToAdjustmentState } from '../utils/adjustment';
|
|
3
|
-
/**
|
|
4
|
-
* Create default adjustment state
|
|
5
|
-
*/
|
|
6
|
-
const createDefaultAdjustmentState = (overrides) => ({
|
|
7
|
-
tempScore: 0,
|
|
8
|
-
tintScore: 0,
|
|
9
|
-
vibranceScore: 0,
|
|
10
|
-
saturationScore: 0,
|
|
11
|
-
exposureScore: 0,
|
|
12
|
-
highlightsScore: 0,
|
|
13
|
-
shadowsScore: 0,
|
|
14
|
-
whitesScore: 0,
|
|
15
|
-
blacksScore: 0,
|
|
16
|
-
contrastScore: 0,
|
|
17
|
-
clarityScore: 0,
|
|
18
|
-
sharpnessScore: 0,
|
|
19
|
-
...overrides
|
|
20
|
-
});
|
|
21
|
-
/**
|
|
22
|
-
* Compare two BatchAdjustmentState objects for equality
|
|
23
|
-
*/
|
|
24
|
-
const compareBatchStates = (a, b) => {
|
|
25
|
-
try {
|
|
26
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.warn('Failed to compare batch states with JSON.stringify:', error);
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Create empty batch state
|
|
35
|
-
*/
|
|
36
|
-
const createEmptyBatchState = () => ({
|
|
37
|
-
currentSelection: {},
|
|
38
|
-
allImages: {},
|
|
39
|
-
initialStates: {}
|
|
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
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
|
|
60
|
-
*
|
|
61
|
-
* **Pure State Management Design:**
|
|
62
|
-
* - Starts empty, no image loading functionality
|
|
63
|
-
* - Focus on state management and history tracking only
|
|
64
|
-
* - Selection-based operations with persistent state
|
|
65
|
-
* - Manual selection via `actions.setSelection()`
|
|
66
|
-
*
|
|
67
|
-
* **Key Features:**
|
|
68
|
-
* - **Current Selection**: Images actively being adjusted
|
|
69
|
-
* - **All Images**: Persistent state for every image touched
|
|
70
|
-
* - **Selective Operations**: Undo/redo only affects selected images
|
|
71
|
-
* - **Automatic State Persistence**: Images remain in allImages even when deselected
|
|
72
|
-
*
|
|
73
|
-
* **Typical Usage Flow:**
|
|
74
|
-
* ```typescript
|
|
75
|
-
* const { actions, currentBatch, selectedIds } = useAdjustmentHistoryBatch();
|
|
76
|
-
*
|
|
77
|
-
* // Select images (new images get default state automatically)
|
|
78
|
-
* actions.setSelection(['img1', 'img2', 'img3']);
|
|
79
|
-
*
|
|
80
|
-
* // Apply adjustments to selected images
|
|
81
|
-
* actions.adjustSelected({ exposureScore: 10 });
|
|
82
|
-
*
|
|
83
|
-
* // Change selection (img1, img2 state persists in allImages)
|
|
84
|
-
* actions.setSelection(['img3']);
|
|
85
|
-
*
|
|
86
|
-
* // Adjust only img3
|
|
87
|
-
* actions.adjustSelected({ contrastScore: 5 });
|
|
88
|
-
*
|
|
89
|
-
* // Undo affects only currently selected (img3)
|
|
90
|
-
* actions.undo();
|
|
91
|
-
* ```
|
|
92
|
-
*
|
|
93
|
-
* **State Structure:**
|
|
94
|
-
* - `currentBatch.currentSelection`: Currently selected images and their states
|
|
95
|
-
* - `currentBatch.allImages`: All images that have been selected/adjusted (persistent)
|
|
96
|
-
* - `selectedIds`: Array of currently selected image IDs
|
|
97
|
-
*
|
|
98
|
-
* @param options - Configuration options for history behavior
|
|
99
|
-
* @returns Object with current batch, selection, history info, actions, and config
|
|
100
|
-
*/
|
|
101
|
-
export function useAdjustmentHistoryBatch(options = {}) {
|
|
102
|
-
// Internal stabilization
|
|
103
|
-
const internalOptions = useMemo(() => ({
|
|
104
|
-
maxSize: options.maxSize ?? 'unlimited',
|
|
105
|
-
devWarnings: options.devWarnings ?? false,
|
|
106
|
-
defaultAdjustmentState: options.defaultAdjustmentState ?? {},
|
|
107
|
-
controller: options.controller,
|
|
108
|
-
firebaseUid: options.firebaseUid,
|
|
109
|
-
eventId: options.eventId
|
|
110
|
-
}), [
|
|
111
|
-
options.maxSize,
|
|
112
|
-
options.devWarnings,
|
|
113
|
-
options.defaultAdjustmentState,
|
|
114
|
-
options.controller,
|
|
115
|
-
options.firebaseUid,
|
|
116
|
-
options.eventId
|
|
117
|
-
]);
|
|
118
|
-
// Core state management - using per-image history instead of batch history
|
|
119
|
-
const [allImageIds, setAllImageIds] = useState([]);
|
|
120
|
-
const [selectedIds, setSelectedIds] = useState([]);
|
|
121
|
-
const [imageHistories, setImageHistories] = useState([]);
|
|
122
|
-
const [currentBatch, setCurrentBatch] = useState(createEmptyBatchState());
|
|
123
|
-
const [lastUpdateTimestamp, setLastUpdateTimestamp] = useState(Date.now());
|
|
124
|
-
// Configuration refs
|
|
125
|
-
const maxSizeRef = useRef(internalOptions.maxSize);
|
|
126
|
-
const devWarningsRef = useRef(internalOptions.devWarnings);
|
|
127
|
-
// Helper function to rebuild currentBatch from imageHistories
|
|
128
|
-
const rebuildCurrentBatch = useCallback(() => {
|
|
129
|
-
console.log('[useAdjustmentHistoryBatch] đ§ rebuildCurrentBatch called with imageHistories:', imageHistories.map(h => ({
|
|
130
|
-
imageId: h.imageId,
|
|
131
|
-
currentHistoryEntryId: h.currentHistoryEntryId,
|
|
132
|
-
historyLength: h.history.length,
|
|
133
|
-
historyIds: h.history.map(entry => entry.id)
|
|
134
|
-
})));
|
|
135
|
-
const newBatch = createEmptyBatchState();
|
|
136
|
-
imageHistories.forEach(imageHistory => {
|
|
137
|
-
// Find current adjustment using currentHistoryEntryId
|
|
138
|
-
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
139
|
-
console.log(`[useAdjustmentHistoryBatch] đ rebuildCurrentBatch for image ${imageHistory.imageId}:`, {
|
|
140
|
-
currentHistoryEntryId: imageHistory.currentHistoryEntryId,
|
|
141
|
-
foundCurrentEntry: !!currentEntry,
|
|
142
|
-
historyLength: imageHistory.history.length,
|
|
143
|
-
historyIds: imageHistory.history.map(entry => entry.id)
|
|
144
|
-
});
|
|
145
|
-
if (currentEntry) {
|
|
146
|
-
newBatch.allImages[imageHistory.imageId] = currentEntry.adjustment;
|
|
147
|
-
if (selectedIds.includes(imageHistory.imageId)) {
|
|
148
|
-
newBatch.currentSelection[imageHistory.imageId] = currentEntry.adjustment;
|
|
149
|
-
}
|
|
150
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully rebuilt batch for image ${imageHistory.imageId}`);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
console.error(`[useAdjustmentHistoryBatch] â CRITICAL: Current entry not found for image ${imageHistory.imageId}!`, {
|
|
154
|
-
searchingFor: imageHistory.currentHistoryEntryId,
|
|
155
|
-
availableIds: imageHistory.history.map(entry => entry.id),
|
|
156
|
-
historyLength: imageHistory.history.length
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
console.log('[useAdjustmentHistoryBatch] đ§ rebuildCurrentBatch result:', {
|
|
161
|
-
allImagesCount: Object.keys(newBatch.allImages).length,
|
|
162
|
-
currentSelectionCount: Object.keys(newBatch.currentSelection).length,
|
|
163
|
-
allImagesIds: Object.keys(newBatch.allImages),
|
|
164
|
-
currentSelectionIds: Object.keys(newBatch.currentSelection)
|
|
165
|
-
});
|
|
166
|
-
return newBatch;
|
|
167
|
-
}, [imageHistories, selectedIds]);
|
|
168
|
-
// Sync currentBatch with imageHistories
|
|
169
|
-
useEffect(() => {
|
|
170
|
-
setCurrentBatch(rebuildCurrentBatch());
|
|
171
|
-
}, [rebuildCurrentBatch]);
|
|
172
|
-
// Memory usage calculation
|
|
173
|
-
const getMemoryUsage = useCallback(() => {
|
|
174
|
-
try {
|
|
175
|
-
const historiesString = JSON.stringify(imageHistories);
|
|
176
|
-
return historiesString.length * 2; // Rough estimate: 2 bytes per character
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
console.warn('Failed to estimate memory usage:', error);
|
|
180
|
-
return imageHistories.length * 100 * 1000; // Fallback estimate
|
|
181
|
-
}
|
|
182
|
-
}, [imageHistories]);
|
|
183
|
-
// Trim individual image histories to specified size
|
|
184
|
-
const trimImageHistoriesToSize = useCallback((size) => {
|
|
185
|
-
if (size <= 0)
|
|
186
|
-
return;
|
|
187
|
-
setImageHistories(prevHistories => prevHistories.map(imageHistory => ({
|
|
188
|
-
...imageHistory,
|
|
189
|
-
history: imageHistory.history.length <= size
|
|
190
|
-
? imageHistory.history
|
|
191
|
-
: imageHistory.history.slice(-size) // Keep last 'size' entries
|
|
192
|
-
})));
|
|
193
|
-
}, []);
|
|
194
|
-
// Apply max size limit
|
|
195
|
-
const enforceMaxSize = useCallback(() => {
|
|
196
|
-
if (maxSizeRef.current === 'unlimited')
|
|
197
|
-
return;
|
|
198
|
-
const maxSize = maxSizeRef.current;
|
|
199
|
-
if (typeof maxSize === 'number' && imageHistories.length > 0) {
|
|
200
|
-
const totalHistorySize = imageHistories.reduce((sum, h) => sum + h.history.length, 0);
|
|
201
|
-
if (totalHistorySize > maxSize * imageHistories.length) {
|
|
202
|
-
trimImageHistoriesToSize(maxSize);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}, [imageHistories, trimImageHistoriesToSize]);
|
|
206
|
-
// Apply adjustment deltas to selected images - with entry-based history and backend sync
|
|
207
|
-
const adjustSelected = useCallback(async (delta) => {
|
|
208
|
-
if (selectedIds.length === 0) {
|
|
209
|
-
if (devWarningsRef.current) {
|
|
210
|
-
console.warn('[useAdjustmentHistoryBatch] adjustSelected called with no selection');
|
|
211
|
-
}
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
// Prepare backend operations BEFORE state update (outside setImageHistories)
|
|
215
|
-
const backendOperations = [];
|
|
216
|
-
// Process each image to prepare operations
|
|
217
|
-
const operationsToApply = [];
|
|
218
|
-
imageHistories.forEach(imageHistory => {
|
|
219
|
-
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
220
|
-
return; // Skip images not being adjusted
|
|
221
|
-
}
|
|
222
|
-
// Get current adjustment from current entry
|
|
223
|
-
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
224
|
-
const currentAdjustment = currentEntry?.adjustment || createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
225
|
-
// Apply deltas with clamping
|
|
226
|
-
const newAdjustment = { ...currentAdjustment };
|
|
227
|
-
Object.keys(delta).forEach(key => {
|
|
228
|
-
const deltaValue = delta[key];
|
|
229
|
-
if (typeof deltaValue === 'number') {
|
|
230
|
-
const currentValue = newAdjustment[key];
|
|
231
|
-
newAdjustment[key] = Math.max(-100, Math.min(100, currentValue + deltaValue));
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
// Check if user is in the middle of history (not at latest state)
|
|
235
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
236
|
-
const isInMiddleOfHistory = currentEntryIndex < imageHistory.history.length - 1;
|
|
237
|
-
let replaceFromTaskId;
|
|
238
|
-
console.log(`[useAdjustmentHistoryBatch] đ¯ Delta replace logic for image ${imageHistory.imageId}:`, {
|
|
239
|
-
currentEntryIndex,
|
|
240
|
-
historyLength: imageHistory.history.length,
|
|
241
|
-
isInMiddleOfHistory,
|
|
242
|
-
currentEntryId: currentEntry?.id
|
|
243
|
-
});
|
|
244
|
-
if (isInMiddleOfHistory) {
|
|
245
|
-
// If user is in middle of history, get the task ID of current position
|
|
246
|
-
replaceFromTaskId = currentEntry?.id;
|
|
247
|
-
console.log(`[useAdjustmentHistoryBatch] đ DELTA will REPLACE current position. replaceFromTaskId=${replaceFromTaskId}`);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
console.log(`[useAdjustmentHistoryBatch] â DELTA will ADD new entry (at end of history)`);
|
|
251
|
-
}
|
|
252
|
-
// Generate new task ID for backend (same as entry ID)
|
|
253
|
-
const taskId = generateTaskId();
|
|
254
|
-
// Create new entry with task ID
|
|
255
|
-
const newEntryId = taskId; // Use the same ID for both entry and task
|
|
256
|
-
const newEntry = {
|
|
257
|
-
id: newEntryId,
|
|
258
|
-
adjustment: newAdjustment
|
|
259
|
-
};
|
|
260
|
-
// Build new history
|
|
261
|
-
let newHistory;
|
|
262
|
-
if (isInMiddleOfHistory) {
|
|
263
|
-
// If in middle of history, truncate from current position and add new entry
|
|
264
|
-
newHistory = [...imageHistory.history.slice(0, currentEntryIndex + 1), newEntry];
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
// If at end of history, just add new entry
|
|
268
|
-
newHistory = [...imageHistory.history, newEntry];
|
|
269
|
-
}
|
|
270
|
-
// Trim if needed
|
|
271
|
-
const maxSize = maxSizeRef.current;
|
|
272
|
-
const trimmedHistory = typeof maxSize === 'number' && newHistory.length > maxSize
|
|
273
|
-
? newHistory.slice(-maxSize)
|
|
274
|
-
: newHistory;
|
|
275
|
-
// Store operation for state update
|
|
276
|
-
operationsToApply.push({
|
|
277
|
-
imageId: imageHistory.imageId,
|
|
278
|
-
newEntryId,
|
|
279
|
-
newHistory: trimmedHistory
|
|
280
|
-
});
|
|
281
|
-
// Prepare backend operation (OUTSIDE state setter)
|
|
282
|
-
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
283
|
-
backendOperations.push({
|
|
284
|
-
imageId: imageHistory.imageId,
|
|
285
|
-
taskId,
|
|
286
|
-
adjustment: newAdjustment,
|
|
287
|
-
replaceFromTaskId
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
// Now update state with prepared operations (no backend preparation inside)
|
|
292
|
-
setImageHistories(prevHistories => {
|
|
293
|
-
return prevHistories.map(imageHistory => {
|
|
294
|
-
// Find operation for this image
|
|
295
|
-
const operation = operationsToApply.find(op => op.imageId === imageHistory.imageId);
|
|
296
|
-
if (!operation) {
|
|
297
|
-
return imageHistory; // No change for images not being adjusted
|
|
298
|
-
}
|
|
299
|
-
console.log(`[useAdjustmentHistoryBatch] đ Applying state update for delta on image ${imageHistory.imageId}`);
|
|
300
|
-
return {
|
|
301
|
-
...imageHistory,
|
|
302
|
-
history: operation.newHistory,
|
|
303
|
-
currentHistoryEntryId: operation.newEntryId // Update current pointer
|
|
304
|
-
};
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
// Perform backend operations asynchronously (already prepared)
|
|
308
|
-
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
309
|
-
try {
|
|
310
|
-
console.log(`[useAdjustmentHistoryBatch] đ¤ Syncing ${backendOperations.length} adjustments to backend (createEditorConfig for each image)`);
|
|
311
|
-
const promises = backendOperations.map(async (operation) => {
|
|
312
|
-
console.log(`[useAdjustmentHistoryBatch] đ Calling createEditorConfig for image ${operation.imageId} with taskId ${operation.taskId}`);
|
|
313
|
-
await internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, {
|
|
314
|
-
gallery_id: operation.imageId,
|
|
315
|
-
task_id: operation.taskId,
|
|
316
|
-
color_adjustment: mapAdjustmentStateToColorAdjustment(operation.adjustment),
|
|
317
|
-
replace_from: operation.replaceFromTaskId
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
await Promise.all(promises);
|
|
321
|
-
if (devWarningsRef.current) {
|
|
322
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully synced ${backendOperations.length} adjustments to backend`);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
catch (error) {
|
|
326
|
-
console.error('[useAdjustmentHistoryBatch] â Failed to sync adjustments to backend:', error);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}, [selectedIds, internalOptions, imageHistories]);
|
|
330
|
-
// Shared function for applying adjustments (used by both reset and preset)
|
|
331
|
-
const applyAdjustmentToSelected = useCallback(async (adjustment, operationType, targetImageIds) => {
|
|
332
|
-
const idsToProcess = targetImageIds || selectedIds;
|
|
333
|
-
if (idsToProcess.length === 0) {
|
|
334
|
-
if (devWarningsRef.current) {
|
|
335
|
-
console.warn(`[useAdjustmentHistoryBatch] â ${operationType} called with no images to process`);
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
console.log(`[useAdjustmentHistoryBatch] đ ${operationType.toUpperCase()} called for images:`, idsToProcess);
|
|
340
|
-
// Prepare backend operations BEFORE state update (outside setImageHistories)
|
|
341
|
-
const backendOperations = [];
|
|
342
|
-
// Process each image to prepare operations
|
|
343
|
-
const operationsToApply = [];
|
|
344
|
-
imageHistories.forEach(imageHistory => {
|
|
345
|
-
if (!idsToProcess.includes(imageHistory.imageId)) {
|
|
346
|
-
return; // Skip images not being processed
|
|
347
|
-
}
|
|
348
|
-
console.log(`[useAdjustmentHistoryBatch] đ Processing ${operationType} for image ${imageHistory.imageId}`);
|
|
349
|
-
// Get current entry for replace_from logic
|
|
350
|
-
const currentEntry = imageHistory.history.find(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
351
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
352
|
-
const isInMiddleOfHistory = currentEntryIndex < imageHistory.history.length - 1;
|
|
353
|
-
let replaceFromTaskId;
|
|
354
|
-
console.log(`[useAdjustmentHistoryBatch] đ¯ ${operationType.toUpperCase()} replace logic for image ${imageHistory.imageId}:`, {
|
|
355
|
-
currentEntryIndex,
|
|
356
|
-
historyLength: imageHistory.history.length,
|
|
357
|
-
isInMiddleOfHistory,
|
|
358
|
-
currentEntryId: currentEntry?.id
|
|
359
|
-
});
|
|
360
|
-
if (isInMiddleOfHistory && currentEntry?.id) {
|
|
361
|
-
// Only use replace_from when truly in middle of history (not at latest)
|
|
362
|
-
replaceFromTaskId = currentEntry.id;
|
|
363
|
-
console.log(`[useAdjustmentHistoryBatch] đ ${operationType.toUpperCase()} will REPLACE current position. replaceFromTaskId=${replaceFromTaskId}`);
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
console.log(`[useAdjustmentHistoryBatch] â ${operationType.toUpperCase()} will ADD new entry (at end of history)`);
|
|
367
|
-
}
|
|
368
|
-
// Create new entry with adjustment
|
|
369
|
-
const newEntryId = generateEntryId();
|
|
370
|
-
const newEntry = {
|
|
371
|
-
id: newEntryId,
|
|
372
|
-
adjustment
|
|
373
|
-
};
|
|
374
|
-
// Build new history
|
|
375
|
-
let newHistory;
|
|
376
|
-
if (isInMiddleOfHistory) {
|
|
377
|
-
// If in middle of history, truncate from current position and add new entry
|
|
378
|
-
newHistory = [...imageHistory.history.slice(0, currentEntryIndex + 1), newEntry];
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
// If at end of history, just add new entry
|
|
382
|
-
newHistory = [...imageHistory.history, newEntry];
|
|
383
|
-
}
|
|
384
|
-
// Trim if needed
|
|
385
|
-
const maxSize = maxSizeRef.current;
|
|
386
|
-
const trimmedHistory = typeof maxSize === 'number' && newHistory.length > maxSize
|
|
387
|
-
? newHistory.slice(-maxSize)
|
|
388
|
-
: newHistory;
|
|
389
|
-
// Store operation for state update
|
|
390
|
-
operationsToApply.push({
|
|
391
|
-
imageId: imageHistory.imageId,
|
|
392
|
-
newEntryId,
|
|
393
|
-
newHistory: trimmedHistory
|
|
394
|
-
});
|
|
395
|
-
// Prepare backend operation (OUTSIDE state setter)
|
|
396
|
-
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
397
|
-
console.log(`[useAdjustmentHistoryBatch] â
Adding ${operationType.toUpperCase()} backend operation for image ${imageHistory.imageId} with taskId ${newEntryId}`, {
|
|
398
|
-
replaceFromTaskId,
|
|
399
|
-
willReplace: !!replaceFromTaskId
|
|
400
|
-
});
|
|
401
|
-
backendOperations.push({
|
|
402
|
-
imageId: imageHistory.imageId,
|
|
403
|
-
taskId: newEntryId,
|
|
404
|
-
adjustment,
|
|
405
|
-
replaceFromTaskId
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
// Now update state with prepared operations (no backend preparation inside)
|
|
410
|
-
setImageHistories(prevHistories => {
|
|
411
|
-
return prevHistories.map(imageHistory => {
|
|
412
|
-
// Find operation for this image
|
|
413
|
-
const operation = operationsToApply.find(op => op.imageId === imageHistory.imageId);
|
|
414
|
-
if (!operation) {
|
|
415
|
-
return imageHistory; // No change for images not being processed
|
|
416
|
-
}
|
|
417
|
-
console.log(`[useAdjustmentHistoryBatch] đ Applying state update for ${operationType} on image ${imageHistory.imageId}`);
|
|
418
|
-
return {
|
|
419
|
-
...imageHistory,
|
|
420
|
-
history: operation.newHistory,
|
|
421
|
-
currentHistoryEntryId: operation.newEntryId // Update current pointer
|
|
422
|
-
};
|
|
423
|
-
});
|
|
424
|
-
});
|
|
425
|
-
// Perform backend operations asynchronously (already prepared)
|
|
426
|
-
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
427
|
-
try {
|
|
428
|
-
console.log(`[useAdjustmentHistoryBatch] đ¤ Syncing ${backendOperations.length} ${operationType} operations to backend (createEditorConfig for each image)`);
|
|
429
|
-
const promises = backendOperations.map(async (operation) => {
|
|
430
|
-
console.log(`[useAdjustmentHistoryBatch] đ Calling createEditorConfig for ${operationType} on image ${operation.imageId} with taskId ${operation.taskId}`, {
|
|
431
|
-
replaceFrom: operation.replaceFromTaskId,
|
|
432
|
-
hasReplaceFrom: !!operation.replaceFromTaskId
|
|
433
|
-
});
|
|
434
|
-
await internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, {
|
|
435
|
-
gallery_id: operation.imageId,
|
|
436
|
-
task_id: operation.taskId,
|
|
437
|
-
color_adjustment: mapAdjustmentStateToColorAdjustment(operation.adjustment),
|
|
438
|
-
replace_from: operation.replaceFromTaskId
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
await Promise.all(promises);
|
|
442
|
-
if (devWarningsRef.current) {
|
|
443
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully synced ${backendOperations.length} ${operationType} operations to backend`);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
catch (error) {
|
|
447
|
-
console.error(`[useAdjustmentHistoryBatch] â Failed to sync ${operationType} to backend:`, error);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
console.log(`[useAdjustmentHistoryBatch] đ ${operationType.toUpperCase()} backend operations prepared: ${backendOperations.length}`);
|
|
451
|
-
}, [selectedIds, internalOptions, imageHistories]);
|
|
452
|
-
// Apply preset values directly to selected images - now uses shared logic
|
|
453
|
-
const adjustSelectedWithPreset = useCallback(async (presetAdjustments) => {
|
|
454
|
-
console.log('[useAdjustmentHistoryBatch] đ¨ adjustSelectedWithPreset called with:', {
|
|
455
|
-
selectedIds,
|
|
456
|
-
selectedCount: selectedIds.length,
|
|
457
|
-
presetAdjustments,
|
|
458
|
-
hasController: !!internalOptions.controller,
|
|
459
|
-
hasFirebaseUid: !!internalOptions.firebaseUid,
|
|
460
|
-
imageHistoriesCount: imageHistories.length,
|
|
461
|
-
imageHistoriesIds: imageHistories.map(h => h.imageId)
|
|
462
|
-
});
|
|
463
|
-
console.log('[useAdjustmentHistoryBatch] đ PRESET: Current imageHistories state at start:', imageHistories.map(h => ({
|
|
464
|
-
imageId: h.imageId,
|
|
465
|
-
currentHistoryEntryId: h.currentHistoryEntryId,
|
|
466
|
-
historyLength: h.history.length,
|
|
467
|
-
historyIds: h.history.map(entry => entry.id)
|
|
468
|
-
})));
|
|
469
|
-
if (selectedIds.length === 0) {
|
|
470
|
-
if (devWarningsRef.current) {
|
|
471
|
-
console.warn('[useAdjustmentHistoryBatch] â adjustSelectedWithPreset called with no selection');
|
|
472
|
-
}
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
// Apply preset values with clamping
|
|
476
|
-
const clampedPreset = { ...presetAdjustments };
|
|
477
|
-
Object.keys(clampedPreset).forEach(key => {
|
|
478
|
-
const presetValue = clampedPreset[key];
|
|
479
|
-
clampedPreset[key] = Math.max(-100, Math.min(100, presetValue));
|
|
480
|
-
});
|
|
481
|
-
console.log('[useAdjustmentHistoryBatch] đ¨ Using SHARED logic for preset (same as reset)');
|
|
482
|
-
// Use the same logic as reset, just with preset values instead of default values
|
|
483
|
-
await applyAdjustmentToSelected(clampedPreset, 'preset');
|
|
484
|
-
}, [selectedIds, internalOptions, imageHistories, applyAdjustmentToSelected]);
|
|
485
|
-
// Set specific adjustment states for specified images (removed since not needed)
|
|
486
|
-
// Undo last changes to selected images - entry-based history version with backend sync
|
|
487
|
-
const undo = useCallback(async () => {
|
|
488
|
-
if (selectedIds.length === 0) {
|
|
489
|
-
if (devWarningsRef.current) {
|
|
490
|
-
console.warn('[useAdjustmentHistoryBatch] Cannot undo - no images selected');
|
|
491
|
-
}
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
console.log('[useAdjustmentHistoryBatch] đ UNDO: Before undo, current imageHistories state:', imageHistories.map(h => ({
|
|
495
|
-
imageId: h.imageId,
|
|
496
|
-
currentHistoryEntryId: h.currentHistoryEntryId,
|
|
497
|
-
historyLength: h.history.length,
|
|
498
|
-
historyIds: h.history.map(entry => entry.id)
|
|
499
|
-
})));
|
|
500
|
-
// Prepare backend operations BEFORE state update (outside setImageHistories)
|
|
501
|
-
let anyChanges = false;
|
|
502
|
-
const backendOperations = [];
|
|
503
|
-
// Process each image to prepare operations
|
|
504
|
-
const operationsToApply = [];
|
|
505
|
-
imageHistories.forEach(imageHistory => {
|
|
506
|
-
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
507
|
-
return; // Skip images not selected
|
|
508
|
-
}
|
|
509
|
-
console.log(`[useAdjustmentHistoryBatch] đ UNDO: Processing image ${imageHistory.imageId} with history:`, {
|
|
510
|
-
currentHistoryEntryId: imageHistory.currentHistoryEntryId,
|
|
511
|
-
historyLength: imageHistory.history.length,
|
|
512
|
-
historyIds: imageHistory.history.map(entry => entry.id)
|
|
513
|
-
});
|
|
514
|
-
// Find current entry index
|
|
515
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
516
|
-
if (currentEntryIndex <= 0) {
|
|
517
|
-
console.log(`[useAdjustmentHistoryBatch] đ UNDO: Cannot undo image ${imageHistory.imageId} - at first entry or not found`);
|
|
518
|
-
return; // Cannot undo if at first entry or entry not found
|
|
519
|
-
}
|
|
520
|
-
// Move to previous entry
|
|
521
|
-
const previousEntry = imageHistory.history[currentEntryIndex - 1];
|
|
522
|
-
anyChanges = true;
|
|
523
|
-
console.log(`[useAdjustmentHistoryBatch] đ UNDO: Moving image ${imageHistory.imageId} from index ${currentEntryIndex} to ${currentEntryIndex - 1}`, {
|
|
524
|
-
fromId: imageHistory.currentHistoryEntryId,
|
|
525
|
-
toId: previousEntry.id,
|
|
526
|
-
preservedHistoryLength: imageHistory.history.length
|
|
527
|
-
});
|
|
528
|
-
// Store operation for state update
|
|
529
|
-
operationsToApply.push({
|
|
530
|
-
imageId: imageHistory.imageId,
|
|
531
|
-
newCurrentHistoryEntryId: previousEntry.id
|
|
532
|
-
});
|
|
533
|
-
// Prepare backend sync operation (OUTSIDE state setter)
|
|
534
|
-
if (previousEntry.id && internalOptions.controller && internalOptions.firebaseUid) {
|
|
535
|
-
backendOperations.push({
|
|
536
|
-
imageId: imageHistory.imageId,
|
|
537
|
-
taskId: previousEntry.id
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
// Now update state with prepared operations (no backend preparation inside)
|
|
542
|
-
setImageHistories(prevHistories => {
|
|
543
|
-
const result = prevHistories.map(imageHistory => {
|
|
544
|
-
// Find operation for this image
|
|
545
|
-
const operation = operationsToApply.find(op => op.imageId === imageHistory.imageId);
|
|
546
|
-
if (!operation) {
|
|
547
|
-
return imageHistory; // No change for images not selected
|
|
548
|
-
}
|
|
549
|
-
console.log(`[useAdjustmentHistoryBatch] đ Applying state update for undo on image ${imageHistory.imageId}`);
|
|
550
|
-
const updatedHistory = {
|
|
551
|
-
...imageHistory,
|
|
552
|
-
currentHistoryEntryId: operation.newCurrentHistoryEntryId
|
|
553
|
-
};
|
|
554
|
-
console.log(`[useAdjustmentHistoryBatch] đ UNDO: Updated image ${imageHistory.imageId} history:`, {
|
|
555
|
-
currentHistoryEntryId: updatedHistory.currentHistoryEntryId,
|
|
556
|
-
historyLength: updatedHistory.history.length,
|
|
557
|
-
historyIds: updatedHistory.history.map(entry => entry.id)
|
|
558
|
-
});
|
|
559
|
-
return updatedHistory;
|
|
560
|
-
});
|
|
561
|
-
console.log('[useAdjustmentHistoryBatch] đ UNDO: After undo, final imageHistories state:', result.map(h => ({
|
|
562
|
-
imageId: h.imageId,
|
|
563
|
-
currentHistoryEntryId: h.currentHistoryEntryId,
|
|
564
|
-
historyLength: h.history.length,
|
|
565
|
-
historyIds: h.history.map(entry => entry.id)
|
|
566
|
-
})));
|
|
567
|
-
return result;
|
|
568
|
-
});
|
|
569
|
-
// Sync with backend (already prepared)
|
|
570
|
-
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
571
|
-
try {
|
|
572
|
-
console.log(`[useAdjustmentHistoryBatch] âĒ Syncing ${backendOperations.length} undo operations to backend (setHistoryIndex for each image)`);
|
|
573
|
-
const promises = backendOperations.map(async (operation) => {
|
|
574
|
-
console.log(`[useAdjustmentHistoryBatch] đ Calling setHistoryIndex for undo on image ${operation.imageId} to taskId ${operation.taskId}`);
|
|
575
|
-
await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, operation.imageId, operation.taskId);
|
|
576
|
-
});
|
|
577
|
-
await Promise.all(promises);
|
|
578
|
-
if (devWarningsRef.current) {
|
|
579
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully synced ${backendOperations.length} undo operations to backend`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
console.error('[useAdjustmentHistoryBatch] â Failed to sync undo to backend:', error);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (!anyChanges && devWarningsRef.current) {
|
|
587
|
-
console.warn('[useAdjustmentHistoryBatch] Undo skipped - no changes to undo for selected images');
|
|
588
|
-
}
|
|
589
|
-
}, [selectedIds, internalOptions, imageHistories]);
|
|
590
|
-
// Redo next changes to selected images - entry-based history version with backend sync
|
|
591
|
-
const redo = useCallback(async () => {
|
|
592
|
-
if (selectedIds.length === 0) {
|
|
593
|
-
if (devWarningsRef.current) {
|
|
594
|
-
console.warn('[useAdjustmentHistoryBatch] Cannot redo - no images selected');
|
|
595
|
-
}
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
// Step 1: Prepare redo operations from current state
|
|
599
|
-
let anyChanges = false;
|
|
600
|
-
const operationsToApply = [];
|
|
601
|
-
const backendOperations = [];
|
|
602
|
-
imageHistories.forEach(imageHistory => {
|
|
603
|
-
if (!selectedIds.includes(imageHistory.imageId)) {
|
|
604
|
-
return; // Skip unselected images
|
|
605
|
-
}
|
|
606
|
-
// Find current entry index
|
|
607
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
608
|
-
if (currentEntryIndex >= imageHistory.history.length - 1 || currentEntryIndex === -1) {
|
|
609
|
-
return; // Cannot redo if at last entry or entry not found
|
|
610
|
-
}
|
|
611
|
-
// Move to next entry
|
|
612
|
-
const nextEntry = imageHistory.history[currentEntryIndex + 1];
|
|
613
|
-
anyChanges = true;
|
|
614
|
-
operationsToApply.push({
|
|
615
|
-
imageId: imageHistory.imageId,
|
|
616
|
-
nextEntryId: nextEntry.id
|
|
617
|
-
});
|
|
618
|
-
// Prepare backend sync operation
|
|
619
|
-
if (nextEntry.id && internalOptions.controller && internalOptions.firebaseUid) {
|
|
620
|
-
backendOperations.push({
|
|
621
|
-
imageId: imageHistory.imageId,
|
|
622
|
-
taskId: nextEntry.id
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
// Step 2: Apply state updates cleanly
|
|
627
|
-
if (operationsToApply.length > 0) {
|
|
628
|
-
setImageHistories(prevHistories => {
|
|
629
|
-
return prevHistories.map(imageHistory => {
|
|
630
|
-
const operation = operationsToApply.find(op => op.imageId === imageHistory.imageId);
|
|
631
|
-
if (!operation) {
|
|
632
|
-
return imageHistory; // No change for this image
|
|
633
|
-
}
|
|
634
|
-
return {
|
|
635
|
-
...imageHistory,
|
|
636
|
-
currentHistoryEntryId: operation.nextEntryId
|
|
637
|
-
};
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
// Step 3: Sync with backend
|
|
642
|
-
if (backendOperations.length > 0 && internalOptions.controller && internalOptions.firebaseUid) {
|
|
643
|
-
try {
|
|
644
|
-
console.log(`[useAdjustmentHistoryBatch] ⊠Syncing ${backendOperations.length} redo operations to backend (setHistoryIndex for each image)`);
|
|
645
|
-
const promises = backendOperations.map(async (operation) => {
|
|
646
|
-
console.log(`[useAdjustmentHistoryBatch] đ Calling setHistoryIndex for redo on image ${operation.imageId} to taskId ${operation.taskId}`);
|
|
647
|
-
await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, operation.imageId, operation.taskId);
|
|
648
|
-
});
|
|
649
|
-
await Promise.all(promises);
|
|
650
|
-
if (devWarningsRef.current) {
|
|
651
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully synced ${backendOperations.length} redo operations to backend`);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
catch (error) {
|
|
655
|
-
console.error('[useAdjustmentHistoryBatch] â Failed to sync redo to backend:', error);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
if (!anyChanges && devWarningsRef.current) {
|
|
659
|
-
console.warn('[useAdjustmentHistoryBatch] Redo skipped - no changes to redo for selected images');
|
|
660
|
-
}
|
|
661
|
-
}, [selectedIds, internalOptions, imageHistories]);
|
|
662
|
-
// Check if any selected image can be undone
|
|
663
|
-
const canUndoSelected = useCallback(() => {
|
|
664
|
-
return selectedIds.some(imageId => {
|
|
665
|
-
const imageHistory = imageHistories.find(h => h.imageId === imageId);
|
|
666
|
-
if (!imageHistory)
|
|
667
|
-
return false;
|
|
668
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
669
|
-
return currentEntryIndex > 0;
|
|
670
|
-
});
|
|
671
|
-
}, [selectedIds, imageHistories]);
|
|
672
|
-
// Check if any selected image can be redone
|
|
673
|
-
const canRedoSelected = useCallback(() => {
|
|
674
|
-
return selectedIds.some(imageId => {
|
|
675
|
-
const imageHistory = imageHistories.find(h => h.imageId === imageId);
|
|
676
|
-
if (!imageHistory)
|
|
677
|
-
return false;
|
|
678
|
-
const currentEntryIndex = imageHistory.history.findIndex(entry => entry.id === imageHistory.currentHistoryEntryId);
|
|
679
|
-
return currentEntryIndex >= 0 && currentEntryIndex < imageHistory.history.length - 1;
|
|
680
|
-
});
|
|
681
|
-
}, [selectedIds, imageHistories]);
|
|
682
|
-
// Reset selected images to default state - now uses shared logic
|
|
683
|
-
const reset = useCallback(async (imageIds) => {
|
|
684
|
-
const idsToReset = imageIds || selectedIds;
|
|
685
|
-
console.log('[useAdjustmentHistoryBatch] đ reset called for images:', idsToReset);
|
|
686
|
-
// Create default state
|
|
687
|
-
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
688
|
-
console.log('[useAdjustmentHistoryBatch] đ Using SHARED logic for reset (same as preset)');
|
|
689
|
-
// Use the same logic as preset, just with default values instead of preset values
|
|
690
|
-
await applyAdjustmentToSelected(defaultState, 'reset', idsToReset);
|
|
691
|
-
}, [selectedIds, internalOptions.defaultAdjustmentState, applyAdjustmentToSelected]);
|
|
692
|
-
// Selection management with initial adjustments - single state update
|
|
693
|
-
const setSelection = useCallback((configs) => {
|
|
694
|
-
const imageIds = configs.map(config => config.imageId);
|
|
695
|
-
// Update selectedIds state
|
|
696
|
-
setSelectedIds(imageIds);
|
|
697
|
-
// Build new batch state with initial adjustments
|
|
698
|
-
const newBatch = {
|
|
699
|
-
currentSelection: {},
|
|
700
|
-
allImages: { ...currentBatch.allImages },
|
|
701
|
-
initialStates: { ...currentBatch.initialStates }
|
|
702
|
-
};
|
|
703
|
-
// Process each image config
|
|
704
|
-
for (const config of configs) {
|
|
705
|
-
const { imageId, adjustment } = config;
|
|
706
|
-
// If image exists in allImages, use its state
|
|
707
|
-
if (currentBatch.allImages[imageId]) {
|
|
708
|
-
newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
|
|
709
|
-
}
|
|
710
|
-
else {
|
|
711
|
-
// New image - determine initial state
|
|
712
|
-
let initialState;
|
|
713
|
-
if (adjustment) {
|
|
714
|
-
// Use provided adjustment as initial state
|
|
715
|
-
initialState = {
|
|
716
|
-
...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
|
|
717
|
-
...adjustment
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
else {
|
|
721
|
-
// Use default state
|
|
722
|
-
initialState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
723
|
-
}
|
|
724
|
-
// Set initial state for new image
|
|
725
|
-
newBatch.currentSelection[imageId] = { ...initialState };
|
|
726
|
-
newBatch.allImages[imageId] = { ...initialState };
|
|
727
|
-
newBatch.initialStates[imageId] = { ...initialState };
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
// Update allImageIds to include any new images
|
|
731
|
-
const newAllImageIds = Array.from(new Set([...allImageIds, ...imageIds]));
|
|
732
|
-
setAllImageIds(newAllImageIds);
|
|
733
|
-
// Single state update to prevent multiple re-renders
|
|
734
|
-
setCurrentBatch(newBatch);
|
|
735
|
-
if (internalOptions.devWarnings) {
|
|
736
|
-
console.log('useAdjustmentHistoryBatch: Selection updated with initial adjustments', {
|
|
737
|
-
selected: imageIds,
|
|
738
|
-
totalImages: newAllImageIds.length,
|
|
739
|
-
newImages: imageIds.filter(id => !allImageIds.includes(id)),
|
|
740
|
-
withInitialAdjustments: configs.filter(c => c.adjustment).length
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}, [allImageIds, currentBatch, internalOptions.defaultAdjustmentState, internalOptions.devWarnings]);
|
|
744
|
-
// Sync adjustments for specific images - loads full history from backend
|
|
745
|
-
const syncAdjustment = useCallback(async (configs) => {
|
|
746
|
-
if (configs.length === 0)
|
|
747
|
-
return;
|
|
748
|
-
// If controller is available, load full history from backend
|
|
749
|
-
if (internalOptions.controller && internalOptions.firebaseUid) {
|
|
750
|
-
try {
|
|
751
|
-
const historyPromises = configs.map(async (config) => {
|
|
752
|
-
try {
|
|
753
|
-
const historyResponse = await internalOptions.controller.getEditorHistory(internalOptions.firebaseUid, config.imageId);
|
|
754
|
-
return {
|
|
755
|
-
imageId: config.imageId,
|
|
756
|
-
backendHistory: historyResponse.history || [],
|
|
757
|
-
fallbackAdjustment: config.adjustment
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
catch (error) {
|
|
761
|
-
console.warn(`[useAdjustmentHistoryBatch] Failed to load history for image ${config.imageId}:`, error);
|
|
762
|
-
return {
|
|
763
|
-
imageId: config.imageId,
|
|
764
|
-
backendHistory: [],
|
|
765
|
-
fallbackAdjustment: config.adjustment
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
const historyResults = await Promise.all(historyPromises);
|
|
770
|
-
setImageHistories(prevHistories => {
|
|
771
|
-
const updatedHistories = [...prevHistories];
|
|
772
|
-
for (const result of historyResults) {
|
|
773
|
-
const { imageId, backendHistory, fallbackAdjustment } = result;
|
|
774
|
-
const existingIndex = updatedHistories.findIndex(h => h.imageId === imageId);
|
|
775
|
-
if (backendHistory.length > 0) {
|
|
776
|
-
// Convert backend history to local history entries
|
|
777
|
-
const historyEntries = backendHistory.map((entry, index) => ({
|
|
778
|
-
id: entry.task_id, // Use backend task_id as our entry id
|
|
779
|
-
adjustment: mapColorAdjustmentToAdjustmentState ?
|
|
780
|
-
mapColorAdjustmentToAdjustmentState(entry.editor_config.color_adjustment) :
|
|
781
|
-
createDefaultAdjustmentState(internalOptions.defaultAdjustmentState)
|
|
782
|
-
}));
|
|
783
|
-
const newImageHistory = {
|
|
784
|
-
imageId,
|
|
785
|
-
currentHistoryEntryId: historyEntries[historyEntries.length - 1].id, // Point to latest entry
|
|
786
|
-
history: historyEntries
|
|
787
|
-
};
|
|
788
|
-
if (existingIndex >= 0) {
|
|
789
|
-
updatedHistories[existingIndex] = newImageHistory;
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
updatedHistories.push(newImageHistory);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
// No backend history, use fallback adjustment or default
|
|
797
|
-
const adjustment = fallbackAdjustment ? {
|
|
798
|
-
...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
|
|
799
|
-
...fallbackAdjustment
|
|
800
|
-
} : createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
801
|
-
const entryId = generateEntryId();
|
|
802
|
-
const entry = {
|
|
803
|
-
id: entryId,
|
|
804
|
-
adjustment
|
|
805
|
-
};
|
|
806
|
-
const newImageHistory = {
|
|
807
|
-
imageId,
|
|
808
|
-
currentHistoryEntryId: entryId,
|
|
809
|
-
history: [entry]
|
|
810
|
-
};
|
|
811
|
-
if (existingIndex >= 0) {
|
|
812
|
-
updatedHistories[existingIndex] = newImageHistory;
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
updatedHistories.push(newImageHistory);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
return updatedHistories;
|
|
820
|
-
});
|
|
821
|
-
if (internalOptions.devWarnings) {
|
|
822
|
-
const syncedImageIds = configs.map(c => c.imageId);
|
|
823
|
-
const totalHistoryEntries = historyResults.reduce((sum, result) => sum + result.backendHistory.length, 0);
|
|
824
|
-
console.log('[useAdjustmentHistoryBatch] Synced adjustments with backend history', {
|
|
825
|
-
syncedImages: syncedImageIds,
|
|
826
|
-
totalHistoryEntries,
|
|
827
|
-
historyLoaded: true
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
catch (error) {
|
|
832
|
-
console.error('[useAdjustmentHistoryBatch] Failed to sync with backend, falling back to local only:', error);
|
|
833
|
-
// Fall back to local-only sync
|
|
834
|
-
syncAdjustmentLocal(configs);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
// No controller available, use local-only sync
|
|
839
|
-
syncAdjustmentLocal(configs);
|
|
840
|
-
}
|
|
841
|
-
// Update allImageIds to include any new images
|
|
842
|
-
const newImageIds = configs.map(c => c.imageId);
|
|
843
|
-
setAllImageIds(prev => {
|
|
844
|
-
const combined = Array.from(new Set([...prev, ...newImageIds]));
|
|
845
|
-
return combined;
|
|
846
|
-
});
|
|
847
|
-
}, [internalOptions]);
|
|
848
|
-
// Local-only sync for fallback
|
|
849
|
-
const syncAdjustmentLocal = useCallback((configs) => {
|
|
850
|
-
setImageHistories(prevHistories => {
|
|
851
|
-
const updatedHistories = [...prevHistories];
|
|
852
|
-
for (const config of configs) {
|
|
853
|
-
const { imageId, adjustment } = config;
|
|
854
|
-
const existingIndex = updatedHistories.findIndex(h => h.imageId === imageId);
|
|
855
|
-
if (adjustment) {
|
|
856
|
-
const fullAdjustment = {
|
|
857
|
-
...createDefaultAdjustmentState(internalOptions.defaultAdjustmentState),
|
|
858
|
-
...adjustment
|
|
859
|
-
};
|
|
860
|
-
const entryId = generateEntryId();
|
|
861
|
-
const entry = {
|
|
862
|
-
id: entryId,
|
|
863
|
-
adjustment: fullAdjustment
|
|
864
|
-
};
|
|
865
|
-
if (existingIndex >= 0) {
|
|
866
|
-
// Update existing image history, replace with new adjustment as initial state
|
|
867
|
-
updatedHistories[existingIndex] = {
|
|
868
|
-
imageId,
|
|
869
|
-
currentHistoryEntryId: entryId,
|
|
870
|
-
history: [entry] // Reset history with synced state
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
else {
|
|
874
|
-
// Add new image history
|
|
875
|
-
updatedHistories.push({
|
|
876
|
-
imageId,
|
|
877
|
-
currentHistoryEntryId: entryId,
|
|
878
|
-
history: [entry]
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
else if (existingIndex < 0) {
|
|
883
|
-
// Add new image with default state
|
|
884
|
-
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
885
|
-
const entryId = generateEntryId();
|
|
886
|
-
const entry = {
|
|
887
|
-
id: entryId,
|
|
888
|
-
adjustment: defaultState
|
|
889
|
-
};
|
|
890
|
-
updatedHistories.push({
|
|
891
|
-
imageId,
|
|
892
|
-
currentHistoryEntryId: entryId,
|
|
893
|
-
history: [entry]
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
return updatedHistories;
|
|
898
|
-
});
|
|
899
|
-
}, [internalOptions]);
|
|
900
|
-
const toggleSelection = useCallback((imageId) => {
|
|
901
|
-
setSelectedIds(prev => {
|
|
902
|
-
const isCurrentlySelected = prev.includes(imageId);
|
|
903
|
-
const newSelectedIds = isCurrentlySelected
|
|
904
|
-
? prev.filter(id => id !== imageId)
|
|
905
|
-
: [...prev, imageId];
|
|
906
|
-
// Update currentSelection in batch state
|
|
907
|
-
const newBatch = {
|
|
908
|
-
currentSelection: { ...currentBatch.currentSelection },
|
|
909
|
-
allImages: { ...currentBatch.allImages },
|
|
910
|
-
initialStates: { ...currentBatch.initialStates }
|
|
911
|
-
};
|
|
912
|
-
if (isCurrentlySelected) {
|
|
913
|
-
// Remove from currentSelection
|
|
914
|
-
delete newBatch.currentSelection[imageId];
|
|
915
|
-
}
|
|
916
|
-
else {
|
|
917
|
-
// Add to currentSelection - use existing state from allImages or create default
|
|
918
|
-
if (currentBatch.allImages[imageId]) {
|
|
919
|
-
newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
|
|
920
|
-
}
|
|
921
|
-
else {
|
|
922
|
-
// New image - create default state
|
|
923
|
-
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
924
|
-
newBatch.currentSelection[imageId] = { ...defaultState };
|
|
925
|
-
newBatch.allImages[imageId] = { ...defaultState };
|
|
926
|
-
newBatch.initialStates[imageId] = { ...defaultState };
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
setCurrentBatch(newBatch);
|
|
930
|
-
return newSelectedIds;
|
|
931
|
-
});
|
|
932
|
-
}, [currentBatch, internalOptions.defaultAdjustmentState]);
|
|
933
|
-
const selectAll = useCallback(() => {
|
|
934
|
-
setSelectedIds([...allImageIds]);
|
|
935
|
-
// Update currentSelection to include all images
|
|
936
|
-
const newBatch = {
|
|
937
|
-
currentSelection: {},
|
|
938
|
-
allImages: { ...currentBatch.allImages },
|
|
939
|
-
initialStates: { ...currentBatch.initialStates }
|
|
940
|
-
};
|
|
941
|
-
for (const imageId of allImageIds) {
|
|
942
|
-
if (currentBatch.allImages[imageId]) {
|
|
943
|
-
newBatch.currentSelection[imageId] = { ...currentBatch.allImages[imageId] };
|
|
944
|
-
}
|
|
945
|
-
else {
|
|
946
|
-
// New image - create default state
|
|
947
|
-
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
948
|
-
newBatch.currentSelection[imageId] = { ...defaultState };
|
|
949
|
-
newBatch.allImages[imageId] = { ...defaultState };
|
|
950
|
-
newBatch.initialStates[imageId] = { ...defaultState };
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
setCurrentBatch(newBatch);
|
|
954
|
-
}, [allImageIds, currentBatch, internalOptions.defaultAdjustmentState]);
|
|
955
|
-
const clearSelection = useCallback(() => {
|
|
956
|
-
setSelectedIds([]);
|
|
957
|
-
// Clear currentSelection but keep allImages and initialStates
|
|
958
|
-
const newBatch = {
|
|
959
|
-
currentSelection: {},
|
|
960
|
-
allImages: { ...currentBatch.allImages },
|
|
961
|
-
initialStates: { ...currentBatch.initialStates }
|
|
962
|
-
};
|
|
963
|
-
setCurrentBatch(newBatch);
|
|
964
|
-
}, [currentBatch]);
|
|
965
|
-
// Jump to specific index - not applicable in per-image history
|
|
966
|
-
const jumpToIndex = useCallback((index) => {
|
|
967
|
-
if (devWarningsRef.current) {
|
|
968
|
-
console.warn('[useAdjustmentHistoryBatch] jumpToIndex not supported in per-image history mode');
|
|
969
|
-
}
|
|
970
|
-
}, []);
|
|
971
|
-
// Clear all history and start fresh
|
|
972
|
-
const clearHistory = useCallback(() => {
|
|
973
|
-
setImageHistories([]);
|
|
974
|
-
setCurrentBatch(createEmptyBatchState());
|
|
975
|
-
setAllImageIds([]);
|
|
976
|
-
setSelectedIds([]);
|
|
977
|
-
}, []);
|
|
978
|
-
const getCurrentBatch = useCallback(() => {
|
|
979
|
-
return {
|
|
980
|
-
currentSelection: { ...currentBatch.currentSelection },
|
|
981
|
-
allImages: { ...currentBatch.allImages },
|
|
982
|
-
initialStates: { ...currentBatch.initialStates }
|
|
983
|
-
};
|
|
984
|
-
}, [currentBatch]);
|
|
985
|
-
// Sync entire batch state - adapted for entry-based history
|
|
986
|
-
const syncBatch = useCallback((newBatch, targetIndex) => {
|
|
987
|
-
// Validate input
|
|
988
|
-
if (!newBatch || typeof newBatch !== 'object' || !newBatch.currentSelection || !newBatch.allImages || !newBatch.initialStates) {
|
|
989
|
-
console.warn('syncBatch: newBatch must be a valid BatchAdjustmentState object with currentSelection, allImages, and initialStates');
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
// Convert batch state to entry-based histories
|
|
993
|
-
const newImageHistories = [];
|
|
994
|
-
Object.entries(newBatch.allImages).forEach(([imageId, adjustment]) => {
|
|
995
|
-
const entryId = generateEntryId();
|
|
996
|
-
const entry = {
|
|
997
|
-
id: entryId,
|
|
998
|
-
adjustment
|
|
999
|
-
};
|
|
1000
|
-
newImageHistories.push({
|
|
1001
|
-
imageId,
|
|
1002
|
-
currentHistoryEntryId: entryId,
|
|
1003
|
-
history: [entry] // Start with current state as single history entry
|
|
1004
|
-
});
|
|
1005
|
-
});
|
|
1006
|
-
// Update state
|
|
1007
|
-
setImageHistories(newImageHistories);
|
|
1008
|
-
setCurrentBatch({
|
|
1009
|
-
currentSelection: { ...newBatch.currentSelection },
|
|
1010
|
-
allImages: { ...newBatch.allImages },
|
|
1011
|
-
initialStates: { ...newBatch.initialStates }
|
|
1012
|
-
});
|
|
1013
|
-
// Update tracking
|
|
1014
|
-
const allImageIds = Object.keys(newBatch.allImages);
|
|
1015
|
-
const selectedImageIds = Object.keys(newBatch.currentSelection);
|
|
1016
|
-
setAllImageIds(allImageIds);
|
|
1017
|
-
setSelectedIds(selectedImageIds);
|
|
1018
|
-
if (devWarningsRef.current) {
|
|
1019
|
-
console.log('[useAdjustmentHistoryBatch] Synchronized batch state to entry-based history', {
|
|
1020
|
-
totalImages: allImageIds.length,
|
|
1021
|
-
selectedImages: selectedImageIds.length
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
}, []);
|
|
1025
|
-
// Sync gallery updates - check for updated galleries and refresh their history
|
|
1026
|
-
const syncGalleryUpdates = useCallback(async () => {
|
|
1027
|
-
if (!internalOptions.controller || !internalOptions.firebaseUid || !internalOptions.eventId) {
|
|
1028
|
-
if (devWarningsRef.current) {
|
|
1029
|
-
console.warn('[useAdjustmentHistoryBatch] syncGalleryUpdates: Missing required options (controller, firebaseUid, or eventId)');
|
|
1030
|
-
}
|
|
1031
|
-
return;
|
|
1032
|
-
}
|
|
1033
|
-
try {
|
|
1034
|
-
// Step 1: Check for gallery updates since last timestamp
|
|
1035
|
-
console.log(`[useAdjustmentHistoryBatch] đ Checking for gallery updates since timestamp: ${lastUpdateTimestamp}`);
|
|
1036
|
-
const updateResponse = await internalOptions.controller.getGalleryUpdateTimestamp(internalOptions.firebaseUid, internalOptions.eventId, lastUpdateTimestamp);
|
|
1037
|
-
if (!updateResponse.gallery || updateResponse.gallery.length === 0) {
|
|
1038
|
-
if (devWarningsRef.current) {
|
|
1039
|
-
console.log('[useAdjustmentHistoryBatch] â
No gallery updates found');
|
|
1040
|
-
}
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
console.log(`[useAdjustmentHistoryBatch] đĨ Found ${updateResponse.gallery.length} updated galleries:`, updateResponse.gallery);
|
|
1044
|
-
// Step 2: Fetch history for each updated gallery
|
|
1045
|
-
const historyPromises = updateResponse.gallery.map(async (galleryId) => {
|
|
1046
|
-
try {
|
|
1047
|
-
console.log(`[useAdjustmentHistoryBatch] đ Fetching history for gallery: ${galleryId}`);
|
|
1048
|
-
const historyResponse = await internalOptions.controller.getEditorHistory(internalOptions.firebaseUid, galleryId);
|
|
1049
|
-
return {
|
|
1050
|
-
imageId: galleryId,
|
|
1051
|
-
currentTaskId: historyResponse.current_task_id,
|
|
1052
|
-
backendHistory: historyResponse.history || [],
|
|
1053
|
-
success: true
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
catch (error) {
|
|
1057
|
-
console.error(`[useAdjustmentHistoryBatch] â Failed to fetch history for gallery ${galleryId}:`, error);
|
|
1058
|
-
return {
|
|
1059
|
-
imageId: galleryId,
|
|
1060
|
-
currentTaskId: '',
|
|
1061
|
-
backendHistory: [],
|
|
1062
|
-
success: false
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
const historyResults = await Promise.all(historyPromises);
|
|
1067
|
-
const successfulUpdates = historyResults.filter(result => result.success && result.backendHistory.length > 0);
|
|
1068
|
-
if (successfulUpdates.length === 0) {
|
|
1069
|
-
if (devWarningsRef.current) {
|
|
1070
|
-
console.log('[useAdjustmentHistoryBatch] âšī¸ No valid history updates to apply');
|
|
1071
|
-
}
|
|
1072
|
-
// Update timestamp even if no valid updates
|
|
1073
|
-
setLastUpdateTimestamp(Date.now());
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
|
-
// Step 3: Prepare history updates from backend data
|
|
1077
|
-
const historyUpdates = [];
|
|
1078
|
-
successfulUpdates.forEach(result => {
|
|
1079
|
-
const { imageId, currentTaskId, backendHistory } = result;
|
|
1080
|
-
if (backendHistory.length > 0) {
|
|
1081
|
-
// Convert backend history to local history entries
|
|
1082
|
-
const historyEntries = backendHistory.map((entry) => ({
|
|
1083
|
-
id: entry.task_id, // Use backend task_id as our entry id
|
|
1084
|
-
adjustment: mapColorAdjustmentToAdjustmentState ?
|
|
1085
|
-
mapColorAdjustmentToAdjustmentState(entry.editor_config.color_adjustment) :
|
|
1086
|
-
createDefaultAdjustmentState(internalOptions.defaultAdjustmentState)
|
|
1087
|
-
}));
|
|
1088
|
-
historyUpdates.push({
|
|
1089
|
-
imageId,
|
|
1090
|
-
newHistory: historyEntries,
|
|
1091
|
-
newCurrentEntryId: currentTaskId // Use current_task_id as current history entry
|
|
1092
|
-
});
|
|
1093
|
-
console.log(`[useAdjustmentHistoryBatch] đ Prepared history update for gallery ${imageId}:`, {
|
|
1094
|
-
historyEntries: historyEntries.length,
|
|
1095
|
-
currentEntryId: currentTaskId
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
// Step 4: Apply history updates cleanly to state
|
|
1100
|
-
if (historyUpdates.length > 0) {
|
|
1101
|
-
setImageHistories(prevHistories => {
|
|
1102
|
-
return prevHistories.map(imageHistory => {
|
|
1103
|
-
const update = historyUpdates.find(u => u.imageId === imageHistory.imageId);
|
|
1104
|
-
if (!update) {
|
|
1105
|
-
return imageHistory; // No update for this image
|
|
1106
|
-
}
|
|
1107
|
-
// Replace entire history with updated backend data
|
|
1108
|
-
return {
|
|
1109
|
-
...imageHistory,
|
|
1110
|
-
history: update.newHistory,
|
|
1111
|
-
currentHistoryEntryId: update.newCurrentEntryId
|
|
1112
|
-
};
|
|
1113
|
-
});
|
|
1114
|
-
});
|
|
1115
|
-
console.log(`[useAdjustmentHistoryBatch] â
Successfully updated history for ${historyUpdates.length} galleries`);
|
|
1116
|
-
}
|
|
1117
|
-
// Step 5: Update timestamp to mark successful sync
|
|
1118
|
-
setLastUpdateTimestamp(Date.now());
|
|
1119
|
-
if (devWarningsRef.current) {
|
|
1120
|
-
console.log('[useAdjustmentHistoryBatch] đ Gallery sync completed', {
|
|
1121
|
-
totalChecked: updateResponse.gallery.length,
|
|
1122
|
-
successfulUpdates: successfulUpdates.length,
|
|
1123
|
-
appliedUpdates: historyUpdates.length,
|
|
1124
|
-
newTimestamp: Date.now()
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
catch (error) {
|
|
1129
|
-
console.error('[useAdjustmentHistoryBatch] â Failed to sync gallery updates:', error);
|
|
1130
|
-
// Don't update timestamp on error, so we can retry
|
|
1131
|
-
}
|
|
1132
|
-
}, [internalOptions, lastUpdateTimestamp, mapColorAdjustmentToAdjustmentState]);
|
|
1133
|
-
// Configuration actions
|
|
1134
|
-
const setMaxSize = useCallback((size) => {
|
|
1135
|
-
maxSizeRef.current = size;
|
|
1136
|
-
if (size !== 'unlimited') {
|
|
1137
|
-
enforceMaxSize();
|
|
1138
|
-
}
|
|
1139
|
-
}, [enforceMaxSize]);
|
|
1140
|
-
// History info object - updated for per-image history
|
|
1141
|
-
const historyInfo = useMemo(() => ({
|
|
1142
|
-
canUndo: canUndoSelected(),
|
|
1143
|
-
canRedo: canRedoSelected(),
|
|
1144
|
-
currentIndex: 0, // Not applicable in per-image history
|
|
1145
|
-
totalStates: imageHistories.reduce((sum, h) => sum + h.history.length, 0),
|
|
1146
|
-
selectedCount: selectedIds.length,
|
|
1147
|
-
totalImages: allImageIds.length,
|
|
1148
|
-
historySize: getMemoryUsage()
|
|
1149
|
-
}), [canUndoSelected, canRedoSelected, imageHistories, selectedIds.length, allImageIds.length, getMemoryUsage]);
|
|
1150
|
-
// Actions object - stabilized with useMemo
|
|
1151
|
-
const actions = useMemo(() => ({
|
|
1152
|
-
adjustSelected,
|
|
1153
|
-
adjustSelectedWithPreset,
|
|
1154
|
-
undo,
|
|
1155
|
-
redo,
|
|
1156
|
-
reset,
|
|
1157
|
-
setSelection,
|
|
1158
|
-
syncAdjustment,
|
|
1159
|
-
syncGalleryUpdates,
|
|
1160
|
-
toggleSelection,
|
|
1161
|
-
selectAll,
|
|
1162
|
-
clearSelection,
|
|
1163
|
-
jumpToIndex,
|
|
1164
|
-
clearHistory,
|
|
1165
|
-
getCurrentBatch,
|
|
1166
|
-
syncBatch
|
|
1167
|
-
}), [
|
|
1168
|
-
adjustSelected, adjustSelectedWithPreset, undo, redo, reset,
|
|
1169
|
-
setSelection, syncAdjustment, syncGalleryUpdates, toggleSelection, selectAll, clearSelection,
|
|
1170
|
-
jumpToIndex, clearHistory, getCurrentBatch, syncBatch
|
|
1171
|
-
]);
|
|
1172
|
-
// Config object - stabilized with useMemo
|
|
1173
|
-
const config = useMemo(() => ({
|
|
1174
|
-
setMaxSize,
|
|
1175
|
-
getMemoryUsage
|
|
1176
|
-
}), [setMaxSize, getMemoryUsage]);
|
|
1177
|
-
// Apply max size enforcement when history changes
|
|
1178
|
-
useEffect(() => {
|
|
1179
|
-
enforceMaxSize();
|
|
1180
|
-
}, [enforceMaxSize]);
|
|
1181
|
-
return {
|
|
1182
|
-
currentBatch,
|
|
1183
|
-
selectedIds,
|
|
1184
|
-
allImageIds,
|
|
1185
|
-
historyInfo,
|
|
1186
|
-
actions,
|
|
1187
|
-
config
|
|
1188
|
-
};
|
|
1189
|
-
}
|