@yogiswara/honcho-editor-ui 2.0.2 → 2.0.4
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/hooks/editor/useHonchoEditor.js +1 -1
- package/dist/hooks/editor/useHonchoEditorBulk.js +1 -0
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +114 -0
- package/dist/hooks/useAdjustmentHistoryBatch.js +444 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/package.json +1 -1
|
@@ -40,7 +40,7 @@ export function useHonchoEditor(controller, initImageId, firebaseUid) {
|
|
|
40
40
|
const [headerMenuAnchorEl, setHeaderMenuAnchorEl] = useState(null);
|
|
41
41
|
const [anchorMenuZoom, setAnchorMenuZoom] = useState(null);
|
|
42
42
|
// Panel Expansion State
|
|
43
|
-
const [colorAdjustmentExpandedPanels, setColorAdjustmentExpandedPanels] = useState(['whiteBalance']);
|
|
43
|
+
const [colorAdjustmentExpandedPanels, setColorAdjustmentExpandedPanels] = useState(['whiteBalance', 'light', 'details']);
|
|
44
44
|
const [presetExpandedPanels, setPresetExpandedPanels] = useState(['preset']);
|
|
45
45
|
// Watermark State
|
|
46
46
|
const [isCreatingWatermark, setIsCreatingWatermark] = useState(false);
|
|
@@ -78,6 +78,7 @@ export function useHonchoEditorBulk(controller, initImageId, firebaseUid) {
|
|
|
78
78
|
const updateAdjustments = useCallback((newValues) => {
|
|
79
79
|
const newState = { ...currentState, ...newValues };
|
|
80
80
|
historyActions.pushState(newState);
|
|
81
|
+
console.log('Updated adjustments:', newState);
|
|
81
82
|
}, [currentState, historyActions]);
|
|
82
83
|
const createRelativeAdjuster = (key, amount) => () => {
|
|
83
84
|
const currentValue = currentState[key];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { AdjustmentState } from './editor/useHonchoEditor';
|
|
2
|
+
/**
|
|
3
|
+
* Batch adjustment state - maps image IDs to their adjustment states
|
|
4
|
+
*/
|
|
5
|
+
export interface BatchAdjustmentState {
|
|
6
|
+
[imageId: string]: AdjustmentState;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for the batch adjustment history hook
|
|
10
|
+
*/
|
|
11
|
+
interface BatchHistoryOptions {
|
|
12
|
+
/** Maximum number of history entries to keep. Use 'unlimited' for no limit */
|
|
13
|
+
maxSize?: number | 'unlimited';
|
|
14
|
+
/** Enable development warnings for performance issues */
|
|
15
|
+
devWarnings?: boolean;
|
|
16
|
+
/** Default adjustment state for new images */
|
|
17
|
+
defaultAdjustmentState?: Partial<AdjustmentState>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Information about the current batch history state
|
|
21
|
+
*/
|
|
22
|
+
export interface BatchHistoryInfo {
|
|
23
|
+
/** Whether undo operation is available for selected images */
|
|
24
|
+
canUndo: boolean;
|
|
25
|
+
/** Whether redo operation is available for selected images */
|
|
26
|
+
canRedo: boolean;
|
|
27
|
+
/** Current position in history (0-based index) */
|
|
28
|
+
currentIndex: number;
|
|
29
|
+
/** Total number of states in history */
|
|
30
|
+
totalStates: number;
|
|
31
|
+
/** Number of currently selected images */
|
|
32
|
+
selectedCount: number;
|
|
33
|
+
/** Total number of images being managed */
|
|
34
|
+
totalImages: number;
|
|
35
|
+
/** Current size of history in memory */
|
|
36
|
+
historySize: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Actions available for batch history management
|
|
40
|
+
*/
|
|
41
|
+
export interface BatchHistoryActions {
|
|
42
|
+
/** Apply adjustment deltas to selected images */
|
|
43
|
+
adjustSelected: (delta: Partial<AdjustmentState>) => void;
|
|
44
|
+
/** Set specific adjustment states for specified images */
|
|
45
|
+
setAdjustments: (adjustments: Partial<BatchAdjustmentState>) => void;
|
|
46
|
+
/** Undo last changes to selected images */
|
|
47
|
+
undo: () => void;
|
|
48
|
+
/** Redo next changes to selected images */
|
|
49
|
+
redo: () => void;
|
|
50
|
+
/** Reset selected images to default state */
|
|
51
|
+
reset: (imageIds?: string[]) => void;
|
|
52
|
+
/** Set which images are selected */
|
|
53
|
+
setSelection: (imageIds: string[]) => void;
|
|
54
|
+
/** Add or remove image from selection */
|
|
55
|
+
toggleSelection: (imageId: string) => void;
|
|
56
|
+
/** Select all images */
|
|
57
|
+
selectAll: () => void;
|
|
58
|
+
/** Clear selection */
|
|
59
|
+
clearSelection: () => void;
|
|
60
|
+
/** Jump to specific index in history */
|
|
61
|
+
jumpToIndex: (index: number) => void;
|
|
62
|
+
/** Clear all history and start fresh */
|
|
63
|
+
clearHistory: () => void;
|
|
64
|
+
/** Get copy of current batch state */
|
|
65
|
+
getCurrentBatch: () => BatchAdjustmentState;
|
|
66
|
+
/** Sync entire batch state */
|
|
67
|
+
syncBatch: (newBatch: BatchAdjustmentState, targetIndex?: number) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Configuration actions for runtime adjustment
|
|
71
|
+
*/
|
|
72
|
+
export interface BatchHistoryConfig {
|
|
73
|
+
/** Set maximum history size */
|
|
74
|
+
setMaxSize: (size: number | 'unlimited') => void;
|
|
75
|
+
/** Get current memory usage estimate */
|
|
76
|
+
getMemoryUsage: () => number;
|
|
77
|
+
/** Add new images to the batch */
|
|
78
|
+
addImages: (imageIds: string[], selectNew?: boolean) => void;
|
|
79
|
+
/** Remove images from the batch */
|
|
80
|
+
removeImages: (imageIds: string[]) => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Return type for the useAdjustmentHistoryBatch hook
|
|
84
|
+
*/
|
|
85
|
+
export interface UseAdjustmentHistoryBatchReturn {
|
|
86
|
+
/** Current batch adjustment state */
|
|
87
|
+
currentBatch: BatchAdjustmentState;
|
|
88
|
+
/** Currently selected image IDs */
|
|
89
|
+
selectedIds: string[];
|
|
90
|
+
/** All image IDs being managed */
|
|
91
|
+
allImageIds: string[];
|
|
92
|
+
/** Information about history state */
|
|
93
|
+
historyInfo: BatchHistoryInfo;
|
|
94
|
+
/** Available history actions */
|
|
95
|
+
actions: BatchHistoryActions;
|
|
96
|
+
/** Configuration options */
|
|
97
|
+
config: BatchHistoryConfig;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
|
|
101
|
+
*
|
|
102
|
+
* Features:
|
|
103
|
+
* - Manages multiple images with individual adjustment states
|
|
104
|
+
* - Selective operations that only affect selected images
|
|
105
|
+
* - Proper history tracking for batch operations
|
|
106
|
+
* - Memory usage monitoring and optimization
|
|
107
|
+
* - Flexible selection management
|
|
108
|
+
*
|
|
109
|
+
* @param imageIds - Array of image IDs to manage
|
|
110
|
+
* @param options - Configuration options for history behavior
|
|
111
|
+
* @returns Object with current batch, selection, history info, actions, and config
|
|
112
|
+
*/
|
|
113
|
+
export declare function useAdjustmentHistoryBatch(imageIds: string[], options?: BatchHistoryOptions): UseAdjustmentHistoryBatchReturn;
|
|
114
|
+
export {};
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Create default adjustment state
|
|
4
|
+
*/
|
|
5
|
+
const createDefaultAdjustmentState = (overrides) => ({
|
|
6
|
+
tempScore: 0,
|
|
7
|
+
tintScore: 0,
|
|
8
|
+
vibranceScore: 0,
|
|
9
|
+
saturationScore: 0,
|
|
10
|
+
exposureScore: 0,
|
|
11
|
+
highlightsScore: 0,
|
|
12
|
+
shadowsScore: 0,
|
|
13
|
+
whitesScore: 0,
|
|
14
|
+
blacksScore: 0,
|
|
15
|
+
contrastScore: 0,
|
|
16
|
+
clarityScore: 0,
|
|
17
|
+
sharpnessScore: 0,
|
|
18
|
+
...overrides
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Compare two BatchAdjustmentState objects for equality
|
|
22
|
+
*/
|
|
23
|
+
const compareBatchStates = (a, b) => {
|
|
24
|
+
const aKeys = Object.keys(a);
|
|
25
|
+
const bKeys = Object.keys(b);
|
|
26
|
+
if (aKeys.length !== bKeys.length)
|
|
27
|
+
return false;
|
|
28
|
+
for (const key of aKeys) {
|
|
29
|
+
if (!b[key])
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
if (JSON.stringify(a[key]) !== JSON.stringify(b[key]))
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Fallback comparison
|
|
37
|
+
const stateA = a[key];
|
|
38
|
+
const stateB = b[key];
|
|
39
|
+
if (stateA.tempScore !== stateB.tempScore ||
|
|
40
|
+
stateA.tintScore !== stateB.tintScore ||
|
|
41
|
+
stateA.vibranceScore !== stateB.vibranceScore ||
|
|
42
|
+
stateA.saturationScore !== stateB.saturationScore ||
|
|
43
|
+
stateA.exposureScore !== stateB.exposureScore ||
|
|
44
|
+
stateA.highlightsScore !== stateB.highlightsScore ||
|
|
45
|
+
stateA.shadowsScore !== stateB.shadowsScore ||
|
|
46
|
+
stateA.whitesScore !== stateB.whitesScore ||
|
|
47
|
+
stateA.blacksScore !== stateB.blacksScore ||
|
|
48
|
+
stateA.contrastScore !== stateB.contrastScore ||
|
|
49
|
+
stateA.clarityScore !== stateB.clarityScore ||
|
|
50
|
+
stateA.sharpnessScore !== stateB.sharpnessScore) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Advanced hook for managing batch AdjustmentState history with selective undo/redo functionality.
|
|
59
|
+
*
|
|
60
|
+
* Features:
|
|
61
|
+
* - Manages multiple images with individual adjustment states
|
|
62
|
+
* - Selective operations that only affect selected images
|
|
63
|
+
* - Proper history tracking for batch operations
|
|
64
|
+
* - Memory usage monitoring and optimization
|
|
65
|
+
* - Flexible selection management
|
|
66
|
+
*
|
|
67
|
+
* @param imageIds - Array of image IDs to manage
|
|
68
|
+
* @param options - Configuration options for history behavior
|
|
69
|
+
* @returns Object with current batch, selection, history info, actions, and config
|
|
70
|
+
*/
|
|
71
|
+
export function useAdjustmentHistoryBatch(imageIds, options = {}) {
|
|
72
|
+
// Stabilize imageIds to prevent unnecessary re-renders
|
|
73
|
+
const stableImageIds = useMemo(() => {
|
|
74
|
+
// Return same reference if content is identical
|
|
75
|
+
return imageIds;
|
|
76
|
+
}, [imageIds.join(',')]); // Only re-memoize if actual content changes
|
|
77
|
+
// Internal stabilization
|
|
78
|
+
const internalOptions = useMemo(() => ({
|
|
79
|
+
maxSize: options.maxSize ?? 'unlimited',
|
|
80
|
+
devWarnings: options.devWarnings ?? false,
|
|
81
|
+
defaultAdjustmentState: options.defaultAdjustmentState ?? {}
|
|
82
|
+
}), [
|
|
83
|
+
options.maxSize,
|
|
84
|
+
options.devWarnings,
|
|
85
|
+
options.defaultAdjustmentState
|
|
86
|
+
]);
|
|
87
|
+
// Initialize batch state with all images
|
|
88
|
+
const createInitialBatch = useCallback((ids) => {
|
|
89
|
+
const batch = {};
|
|
90
|
+
for (const id of ids) {
|
|
91
|
+
batch[id] = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
92
|
+
}
|
|
93
|
+
return batch;
|
|
94
|
+
}, [internalOptions.defaultAdjustmentState]);
|
|
95
|
+
// Core state management - use stable imageIds for initialization
|
|
96
|
+
const [allImageIds, setAllImageIds] = useState(stableImageIds);
|
|
97
|
+
const [selectedIds, setSelectedIds] = useState(stableImageIds); // Default select all
|
|
98
|
+
const [history, setHistory] = useState(() => [createInitialBatch(stableImageIds)]);
|
|
99
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
100
|
+
const [currentBatch, setCurrentBatch] = useState(() => createInitialBatch(stableImageIds));
|
|
101
|
+
// Configuration refs
|
|
102
|
+
const maxSizeRef = useRef(internalOptions.maxSize);
|
|
103
|
+
const devWarningsRef = useRef(internalOptions.devWarnings);
|
|
104
|
+
// Sync currentBatch with history
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
setCurrentBatch(history[currentIndex]);
|
|
107
|
+
}, [history, currentIndex]);
|
|
108
|
+
// Update when stableImageIds actually changes (content-wise)
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
// Only update if the actual content is different, not just reference
|
|
111
|
+
const currentIdString = allImageIds.join(',');
|
|
112
|
+
const newIdString = stableImageIds.join(',');
|
|
113
|
+
if (currentIdString !== newIdString) {
|
|
114
|
+
const newBatch = createInitialBatch(stableImageIds);
|
|
115
|
+
setAllImageIds([...stableImageIds]); // Create new array to avoid reference issues
|
|
116
|
+
setSelectedIds([...stableImageIds]); // Default select all new images
|
|
117
|
+
setHistory([newBatch]);
|
|
118
|
+
setCurrentIndex(0);
|
|
119
|
+
setCurrentBatch(newBatch);
|
|
120
|
+
if (internalOptions.devWarnings) {
|
|
121
|
+
console.log('useAdjustmentHistoryBatch: ImageIds updated', {
|
|
122
|
+
from: allImageIds,
|
|
123
|
+
to: stableImageIds
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}, [stableImageIds, allImageIds, createInitialBatch, internalOptions.devWarnings]);
|
|
128
|
+
// Memory usage calculation
|
|
129
|
+
const getMemoryUsage = useCallback(() => {
|
|
130
|
+
try {
|
|
131
|
+
const historyString = JSON.stringify(history);
|
|
132
|
+
return historyString.length * 2; // Rough estimate: 2 bytes per character
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.warn('Failed to estimate memory usage:', error);
|
|
136
|
+
return history.length * allImageIds.length * 1000; // Fallback estimate
|
|
137
|
+
}
|
|
138
|
+
}, [history, allImageIds.length]);
|
|
139
|
+
// Trim history to specified size
|
|
140
|
+
const trimHistoryToSize = useCallback((size) => {
|
|
141
|
+
if (size <= 0)
|
|
142
|
+
return;
|
|
143
|
+
setHistory(prevHistory => {
|
|
144
|
+
if (prevHistory.length <= size)
|
|
145
|
+
return prevHistory;
|
|
146
|
+
const startIndex = Math.max(0, prevHistory.length - size);
|
|
147
|
+
const trimmedHistory = prevHistory.slice(startIndex);
|
|
148
|
+
// Adjust current index
|
|
149
|
+
setCurrentIndex(prevIndex => {
|
|
150
|
+
const adjustedIndex = prevIndex - startIndex;
|
|
151
|
+
return Math.max(0, Math.min(adjustedIndex, trimmedHistory.length - 1));
|
|
152
|
+
});
|
|
153
|
+
return trimmedHistory;
|
|
154
|
+
});
|
|
155
|
+
}, []);
|
|
156
|
+
// Apply max size limit
|
|
157
|
+
const enforceMaxSize = useCallback(() => {
|
|
158
|
+
if (maxSizeRef.current === 'unlimited')
|
|
159
|
+
return;
|
|
160
|
+
const maxSize = maxSizeRef.current;
|
|
161
|
+
if (history.length > maxSize) {
|
|
162
|
+
trimHistoryToSize(maxSize);
|
|
163
|
+
}
|
|
164
|
+
}, [history.length, trimHistoryToSize]);
|
|
165
|
+
// Push new batch state to history
|
|
166
|
+
const pushBatchState = useCallback((newBatch) => {
|
|
167
|
+
// Skip if batch hasn't changed
|
|
168
|
+
if (compareBatchStates(newBatch, currentBatch)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Update currentBatch immediately for smooth UI
|
|
172
|
+
setCurrentBatch(newBatch);
|
|
173
|
+
// Update history
|
|
174
|
+
setHistory(prevHistory => {
|
|
175
|
+
const truncatedHistory = prevHistory.slice(0, currentIndex + 1);
|
|
176
|
+
const newHistory = [...truncatedHistory, newBatch];
|
|
177
|
+
setCurrentIndex(newHistory.length - 1);
|
|
178
|
+
return newHistory;
|
|
179
|
+
});
|
|
180
|
+
}, [currentBatch, currentIndex]);
|
|
181
|
+
// Apply adjustment deltas to selected images
|
|
182
|
+
const adjustSelected = useCallback((delta) => {
|
|
183
|
+
if (selectedIds.length === 0)
|
|
184
|
+
return;
|
|
185
|
+
const newBatch = { ...currentBatch };
|
|
186
|
+
for (const imageId of selectedIds) {
|
|
187
|
+
if (newBatch[imageId]) {
|
|
188
|
+
newBatch[imageId] = {
|
|
189
|
+
...newBatch[imageId],
|
|
190
|
+
...Object.fromEntries(Object.entries(delta).map(([key, value]) => [
|
|
191
|
+
key,
|
|
192
|
+
newBatch[imageId][key] + value
|
|
193
|
+
]))
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
pushBatchState(newBatch);
|
|
198
|
+
}, [selectedIds, currentBatch, pushBatchState]);
|
|
199
|
+
// Set specific adjustment states for specified images
|
|
200
|
+
const setAdjustments = useCallback((adjustments) => {
|
|
201
|
+
const newBatch = { ...currentBatch };
|
|
202
|
+
for (const [imageId, adjustment] of Object.entries(adjustments)) {
|
|
203
|
+
if (newBatch[imageId]) {
|
|
204
|
+
newBatch[imageId] = { ...newBatch[imageId], ...adjustment };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
pushBatchState(newBatch);
|
|
208
|
+
}, [currentBatch, pushBatchState]);
|
|
209
|
+
// Undo last changes to selected images
|
|
210
|
+
const undo = useCallback(() => {
|
|
211
|
+
if (currentIndex > 0 && selectedIds.length > 0) {
|
|
212
|
+
const previousBatch = history[currentIndex - 1];
|
|
213
|
+
const newBatch = { ...currentBatch };
|
|
214
|
+
// Only restore selected images from previous state
|
|
215
|
+
for (const imageId of selectedIds) {
|
|
216
|
+
if (previousBatch[imageId] && newBatch[imageId]) {
|
|
217
|
+
newBatch[imageId] = { ...previousBatch[imageId] };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Update current batch and move index back
|
|
221
|
+
setCurrentBatch(newBatch);
|
|
222
|
+
setCurrentIndex(currentIndex - 1);
|
|
223
|
+
// Update the current history entry with the new mixed state
|
|
224
|
+
setHistory(prevHistory => {
|
|
225
|
+
const newHistory = [...prevHistory];
|
|
226
|
+
newHistory[currentIndex] = newBatch;
|
|
227
|
+
return newHistory;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}, [currentIndex, selectedIds, history, currentBatch]);
|
|
231
|
+
// Redo next changes to selected images
|
|
232
|
+
const redo = useCallback(() => {
|
|
233
|
+
if (currentIndex < history.length - 1 && selectedIds.length > 0) {
|
|
234
|
+
const nextBatch = history[currentIndex + 1];
|
|
235
|
+
const newBatch = { ...currentBatch };
|
|
236
|
+
// Only restore selected images from next state
|
|
237
|
+
for (const imageId of selectedIds) {
|
|
238
|
+
if (nextBatch[imageId] && newBatch[imageId]) {
|
|
239
|
+
newBatch[imageId] = { ...nextBatch[imageId] };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Update current batch and move index forward
|
|
243
|
+
setCurrentBatch(newBatch);
|
|
244
|
+
setCurrentIndex(currentIndex + 1);
|
|
245
|
+
// Update the current history entry with the new mixed state
|
|
246
|
+
setHistory(prevHistory => {
|
|
247
|
+
const newHistory = [...prevHistory];
|
|
248
|
+
newHistory[currentIndex + 1] = newBatch;
|
|
249
|
+
return newHistory;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}, [currentIndex, selectedIds, history, currentBatch]);
|
|
253
|
+
// Reset selected images to default state
|
|
254
|
+
const reset = useCallback((imageIds) => {
|
|
255
|
+
const idsToReset = imageIds || selectedIds;
|
|
256
|
+
if (idsToReset.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
const newBatch = { ...currentBatch };
|
|
259
|
+
const defaultState = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
260
|
+
for (const imageId of idsToReset) {
|
|
261
|
+
if (newBatch[imageId]) {
|
|
262
|
+
newBatch[imageId] = { ...defaultState };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
pushBatchState(newBatch);
|
|
266
|
+
}, [selectedIds, currentBatch, pushBatchState, internalOptions.defaultAdjustmentState]);
|
|
267
|
+
// Selection management
|
|
268
|
+
const setSelection = useCallback((imageIds) => {
|
|
269
|
+
// Validate that all imageIds exist
|
|
270
|
+
const validIds = imageIds.filter(id => allImageIds.includes(id));
|
|
271
|
+
setSelectedIds(validIds);
|
|
272
|
+
if (devWarningsRef.current && validIds.length !== imageIds.length) {
|
|
273
|
+
console.warn('Some image IDs in setSelection do not exist:', imageIds.filter(id => !allImageIds.includes(id)));
|
|
274
|
+
}
|
|
275
|
+
}, [allImageIds]);
|
|
276
|
+
const toggleSelection = useCallback((imageId) => {
|
|
277
|
+
if (!allImageIds.includes(imageId)) {
|
|
278
|
+
if (devWarningsRef.current) {
|
|
279
|
+
console.warn('toggleSelection: Image ID does not exist:', imageId);
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
setSelectedIds(prev => prev.includes(imageId)
|
|
284
|
+
? prev.filter(id => id !== imageId)
|
|
285
|
+
: [...prev, imageId]);
|
|
286
|
+
}, [allImageIds]);
|
|
287
|
+
const selectAll = useCallback(() => {
|
|
288
|
+
setSelectedIds([...allImageIds]);
|
|
289
|
+
}, [allImageIds]);
|
|
290
|
+
const clearSelection = useCallback(() => {
|
|
291
|
+
setSelectedIds([]);
|
|
292
|
+
}, []);
|
|
293
|
+
// Other history actions
|
|
294
|
+
const jumpToIndex = useCallback((index) => {
|
|
295
|
+
if (index >= 0 && index < history.length) {
|
|
296
|
+
setCurrentIndex(index);
|
|
297
|
+
setCurrentBatch(history[index]);
|
|
298
|
+
}
|
|
299
|
+
}, [history]);
|
|
300
|
+
const clearHistory = useCallback(() => {
|
|
301
|
+
const freshBatch = createInitialBatch(allImageIds);
|
|
302
|
+
setHistory([freshBatch]);
|
|
303
|
+
setCurrentIndex(0);
|
|
304
|
+
setCurrentBatch(freshBatch);
|
|
305
|
+
}, [allImageIds, createInitialBatch]);
|
|
306
|
+
const getCurrentBatch = useCallback(() => {
|
|
307
|
+
return { ...currentBatch };
|
|
308
|
+
}, [currentBatch]);
|
|
309
|
+
const syncBatch = useCallback((newBatch, targetIndex) => {
|
|
310
|
+
// Validate input
|
|
311
|
+
if (!newBatch || typeof newBatch !== 'object') {
|
|
312
|
+
console.warn('syncBatch: newBatch must be a valid BatchAdjustmentState object');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
// Validate all items are AdjustmentState objects
|
|
316
|
+
const isValidBatch = Object.values(newBatch).every(state => state && typeof state === 'object' &&
|
|
317
|
+
typeof state.tempScore === 'number' &&
|
|
318
|
+
typeof state.tintScore === 'number' &&
|
|
319
|
+
typeof state.vibranceScore === 'number' &&
|
|
320
|
+
typeof state.saturationScore === 'number' &&
|
|
321
|
+
typeof state.exposureScore === 'number' &&
|
|
322
|
+
typeof state.highlightsScore === 'number' &&
|
|
323
|
+
typeof state.shadowsScore === 'number' &&
|
|
324
|
+
typeof state.whitesScore === 'number' &&
|
|
325
|
+
typeof state.blacksScore === 'number' &&
|
|
326
|
+
typeof state.contrastScore === 'number' &&
|
|
327
|
+
typeof state.clarityScore === 'number' &&
|
|
328
|
+
typeof state.sharpnessScore === 'number');
|
|
329
|
+
if (!isValidBatch) {
|
|
330
|
+
console.warn('syncBatch: All values in newBatch must be valid AdjustmentState objects');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// Update current state
|
|
334
|
+
setCurrentBatch({ ...newBatch });
|
|
335
|
+
// Replace history with single entry
|
|
336
|
+
setHistory([{ ...newBatch }]);
|
|
337
|
+
setCurrentIndex(0);
|
|
338
|
+
if (devWarningsRef.current) {
|
|
339
|
+
console.log('syncBatch: Synchronized batch state with', Object.keys(newBatch).length, 'images');
|
|
340
|
+
}
|
|
341
|
+
}, []);
|
|
342
|
+
// Configuration actions
|
|
343
|
+
const setMaxSize = useCallback((size) => {
|
|
344
|
+
maxSizeRef.current = size;
|
|
345
|
+
if (size !== 'unlimited') {
|
|
346
|
+
enforceMaxSize();
|
|
347
|
+
}
|
|
348
|
+
}, [enforceMaxSize]);
|
|
349
|
+
const addImages = useCallback((imageIds, selectNew = true) => {
|
|
350
|
+
const newIds = imageIds.filter(id => !allImageIds.includes(id));
|
|
351
|
+
if (newIds.length === 0)
|
|
352
|
+
return;
|
|
353
|
+
const updatedAllIds = [...allImageIds, ...newIds];
|
|
354
|
+
const newBatch = { ...currentBatch };
|
|
355
|
+
// Add default states for new images
|
|
356
|
+
for (const id of newIds) {
|
|
357
|
+
newBatch[id] = createDefaultAdjustmentState(internalOptions.defaultAdjustmentState);
|
|
358
|
+
}
|
|
359
|
+
setAllImageIds(updatedAllIds);
|
|
360
|
+
setCurrentBatch(newBatch);
|
|
361
|
+
// Update history
|
|
362
|
+
setHistory(prevHistory => {
|
|
363
|
+
const newHistory = [...prevHistory];
|
|
364
|
+
newHistory[currentIndex] = newBatch;
|
|
365
|
+
return newHistory;
|
|
366
|
+
});
|
|
367
|
+
// Optionally select new images
|
|
368
|
+
if (selectNew) {
|
|
369
|
+
setSelectedIds(prev => [...prev, ...newIds]);
|
|
370
|
+
}
|
|
371
|
+
}, [allImageIds, currentBatch, currentIndex, internalOptions.defaultAdjustmentState]);
|
|
372
|
+
const removeImages = useCallback((imageIds) => {
|
|
373
|
+
const updatedAllIds = allImageIds.filter(id => !imageIds.includes(id));
|
|
374
|
+
const updatedSelectedIds = selectedIds.filter(id => !imageIds.includes(id));
|
|
375
|
+
const newBatch = { ...currentBatch };
|
|
376
|
+
// Remove images from batch
|
|
377
|
+
for (const id of imageIds) {
|
|
378
|
+
delete newBatch[id];
|
|
379
|
+
}
|
|
380
|
+
setAllImageIds(updatedAllIds);
|
|
381
|
+
setSelectedIds(updatedSelectedIds);
|
|
382
|
+
setCurrentBatch(newBatch);
|
|
383
|
+
// Update history
|
|
384
|
+
setHistory(prevHistory => {
|
|
385
|
+
const newHistory = prevHistory.map(batch => {
|
|
386
|
+
const cleanedBatch = { ...batch };
|
|
387
|
+
for (const id of imageIds) {
|
|
388
|
+
delete cleanedBatch[id];
|
|
389
|
+
}
|
|
390
|
+
return cleanedBatch;
|
|
391
|
+
});
|
|
392
|
+
return newHistory;
|
|
393
|
+
});
|
|
394
|
+
}, [allImageIds, selectedIds, currentBatch]);
|
|
395
|
+
// History info object
|
|
396
|
+
const historyInfo = useMemo(() => ({
|
|
397
|
+
canUndo: currentIndex > 0 && selectedIds.length > 0,
|
|
398
|
+
canRedo: currentIndex < history.length - 1 && selectedIds.length > 0,
|
|
399
|
+
currentIndex,
|
|
400
|
+
totalStates: history.length,
|
|
401
|
+
selectedCount: selectedIds.length,
|
|
402
|
+
totalImages: allImageIds.length,
|
|
403
|
+
historySize: getMemoryUsage()
|
|
404
|
+
}), [currentIndex, history.length, selectedIds.length, allImageIds.length, getMemoryUsage]);
|
|
405
|
+
// Actions object - stabilized with useMemo
|
|
406
|
+
const actions = useMemo(() => ({
|
|
407
|
+
adjustSelected,
|
|
408
|
+
setAdjustments,
|
|
409
|
+
undo,
|
|
410
|
+
redo,
|
|
411
|
+
reset,
|
|
412
|
+
setSelection,
|
|
413
|
+
toggleSelection,
|
|
414
|
+
selectAll,
|
|
415
|
+
clearSelection,
|
|
416
|
+
jumpToIndex,
|
|
417
|
+
clearHistory,
|
|
418
|
+
getCurrentBatch,
|
|
419
|
+
syncBatch
|
|
420
|
+
}), [
|
|
421
|
+
adjustSelected, setAdjustments, undo, redo, reset,
|
|
422
|
+
setSelection, toggleSelection, selectAll, clearSelection,
|
|
423
|
+
jumpToIndex, clearHistory, getCurrentBatch, syncBatch
|
|
424
|
+
]);
|
|
425
|
+
// Config object - stabilized with useMemo
|
|
426
|
+
const config = useMemo(() => ({
|
|
427
|
+
setMaxSize,
|
|
428
|
+
getMemoryUsage,
|
|
429
|
+
addImages,
|
|
430
|
+
removeImages
|
|
431
|
+
}), [setMaxSize, getMemoryUsage, addImages, removeImages]);
|
|
432
|
+
// Apply max size enforcement when history changes
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
enforceMaxSize();
|
|
435
|
+
}, [enforceMaxSize]);
|
|
436
|
+
return {
|
|
437
|
+
currentBatch,
|
|
438
|
+
selectedIds,
|
|
439
|
+
allImageIds,
|
|
440
|
+
historyInfo,
|
|
441
|
+
actions,
|
|
442
|
+
config
|
|
443
|
+
};
|
|
444
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export { default as HModalMobile } from './components/editor/HModalMobile';
|
|
|
20
20
|
export { default as HPresetOptionsMenu } from './components/editor/HPresetOptionMenu';
|
|
21
21
|
export { HAlertInternetBox, HAlertCopyBox, HAlertInternetConnectionBox, HAlertPresetSave } from './components/editor/HAlertBox';
|
|
22
22
|
export { useHonchoEditorBulk } from './hooks/editor/useHonchoEditorBulk';
|
|
23
|
+
export { useAdjustmentHistory, type UseAdjustmentHistoryReturn, type HistoryInfo, type HistoryActions, type HistoryConfig } from './hooks/useAdjustmentHistory';
|
|
24
|
+
export { useAdjustmentHistoryBatch, type UseAdjustmentHistoryBatchReturn, type BatchAdjustmentState, type BatchHistoryInfo, type BatchHistoryActions, type BatchHistoryConfig } from './hooks/useAdjustmentHistoryBatch';
|
|
23
25
|
export { default as useColors } from './themes/colors';
|
|
24
26
|
export { default as useHonchoTypography } from './themes/honchoTheme';
|
|
25
27
|
export { default as useIsMobile } from './utils/isMobile';
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,9 @@ export { default as HModalMobile } from './components/editor/HModalMobile';
|
|
|
18
18
|
export { default as HPresetOptionsMenu } from './components/editor/HPresetOptionMenu';
|
|
19
19
|
export { HAlertInternetBox, HAlertCopyBox, HAlertInternetConnectionBox, HAlertPresetSave } from './components/editor/HAlertBox';
|
|
20
20
|
export { useHonchoEditorBulk } from './hooks/editor/useHonchoEditorBulk';
|
|
21
|
+
// --- History Hooks ---
|
|
22
|
+
export { useAdjustmentHistory } from './hooks/useAdjustmentHistory';
|
|
23
|
+
export { useAdjustmentHistoryBatch } from './hooks/useAdjustmentHistoryBatch';
|
|
21
24
|
// --- Theme & Utils ---
|
|
22
25
|
export { default as useColors } from './themes/colors';
|
|
23
26
|
export { default as useHonchoTypography } from './themes/honchoTheme';
|