@yogiswara/honcho-editor-ui 2.8.10 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/color.d.ts +9 -0
  2. package/dist/color.js +9 -0
  3. package/dist/components/editor/GalleryAlbum/AlbumImageGallery.d.ts +8 -0
  4. package/dist/components/editor/GalleryAlbum/AlbumImageGallery.js +28 -0
  5. package/dist/components/editor/GalleryAlbum/ImageItem.d.ts +10 -0
  6. package/dist/components/editor/GalleryAlbum/ImageItem.js +81 -0
  7. package/dist/components/editor/HAccordionAspectRatio.d.ts +14 -0
  8. package/dist/components/editor/HAccordionAspectRatio.js +102 -0
  9. package/dist/components/editor/HAccordionColor.d.ts +16 -0
  10. package/dist/components/editor/HAccordionColor.js +282 -0
  11. package/dist/components/editor/HAccordionColorAdjustment.d.ts +35 -0
  12. package/dist/components/editor/HAccordionColorAdjustment.js +31 -0
  13. package/dist/components/editor/HAccordionDetails.d.ts +12 -0
  14. package/dist/components/editor/HAccordionDetails.js +183 -0
  15. package/dist/components/editor/HAccordionLight.d.ts +20 -0
  16. package/dist/components/editor/HAccordionLight.js +414 -0
  17. package/dist/components/editor/HAccordionPreset.d.ts +23 -0
  18. package/dist/components/editor/HAccordionPreset.js +52 -0
  19. package/dist/components/editor/HAlertBox.d.ts +8 -0
  20. package/dist/components/editor/HAlertBox.js +55 -0
  21. package/dist/components/editor/HAspectRatioMobile.d.ts +0 -0
  22. package/dist/components/editor/HAspectRatioMobile.js +1 -0
  23. package/dist/components/editor/HBulkAccordionColorAdjustment.d.ts +55 -0
  24. package/dist/components/editor/HBulkAccordionColorAdjustment.js +31 -0
  25. package/dist/components/editor/HBulkAccordionColorAdjustmentColors.d.ts +20 -0
  26. package/dist/components/editor/HBulkAccordionColorAdjustmentColors.js +121 -0
  27. package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.d.ts +12 -0
  28. package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.js +65 -0
  29. package/dist/components/editor/HBulkAccordionColorAdjustmentLight.d.ts +28 -0
  30. package/dist/components/editor/HBulkAccordionColorAdjustmentLight.js +177 -0
  31. package/dist/components/editor/HBulkColorAdjustmentMobile.d.ts +53 -0
  32. package/dist/components/editor/HBulkColorAdjustmentMobile.js +16 -0
  33. package/dist/components/editor/HBulkColorMobile.d.ts +20 -0
  34. package/dist/components/editor/HBulkColorMobile.js +121 -0
  35. package/dist/components/editor/HBulkDetailsMobile.d.ts +12 -0
  36. package/dist/components/editor/HBulkDetailsMobile.js +65 -0
  37. package/dist/components/editor/HBulkLightMobile.d.ts +28 -0
  38. package/dist/components/editor/HBulkLightMobile.js +192 -0
  39. package/dist/components/editor/HBulkPreset.d.ts +24 -0
  40. package/dist/components/editor/HBulkPreset.js +43 -0
  41. package/dist/components/editor/HBulkPresetMobile.d.ts +15 -0
  42. package/dist/components/editor/HBulkPresetMobile.js +26 -0
  43. package/dist/components/editor/HDialogBox.d.ts +18 -0
  44. package/dist/components/editor/HDialogBox.js +51 -0
  45. package/dist/components/editor/HDialogCopy.d.ts +41 -0
  46. package/dist/components/editor/HDialogCopy.js +80 -0
  47. package/dist/components/editor/HFooter.d.ts +12 -0
  48. package/dist/components/editor/HFooter.js +24 -0
  49. package/dist/components/editor/HHeaderEditor.d.ts +19 -0
  50. package/dist/components/editor/HHeaderEditor.js +36 -0
  51. package/dist/components/editor/HImageEditorBulkDekstop.d.ts +15 -0
  52. package/dist/components/editor/HImageEditorBulkDekstop.js +29 -0
  53. package/dist/components/editor/HImageEditorBulkMobile.d.ts +72 -0
  54. package/dist/components/editor/HImageEditorBulkMobile.js +81 -0
  55. package/dist/components/editor/HImageEditorDekstop.d.ts +15 -0
  56. package/dist/components/editor/HImageEditorDekstop.js +29 -0
  57. package/dist/components/editor/HImageEditorMobile.d.ts +53 -0
  58. package/dist/components/editor/HImageEditorMobile.js +92 -0
  59. package/dist/components/editor/HImageEditorMobileLayout.d.ts +14 -0
  60. package/dist/components/editor/HImageEditorMobileLayout.js +58 -0
  61. package/dist/components/editor/HModalEditorDekstop.d.ts +13 -0
  62. package/dist/components/editor/HModalEditorDekstop.js +22 -0
  63. package/dist/components/editor/HModalMobile.d.ts +13 -0
  64. package/dist/components/editor/HModalMobile.js +9 -0
  65. package/dist/components/editor/HPresetDelete.d.ts +7 -0
  66. package/dist/components/editor/HPresetDelete.js +7 -0
  67. package/dist/components/editor/HPresetOptionMenu.d.ts +9 -0
  68. package/dist/components/editor/HPresetOptionMenu.js +20 -0
  69. package/dist/components/editor/HSliderColorMobile.d.ts +16 -0
  70. package/dist/components/editor/HSliderColorMobile.js +270 -0
  71. package/dist/components/editor/HSliderDetailsMobile.d.ts +12 -0
  72. package/dist/components/editor/HSliderDetailsMobile.js +154 -0
  73. package/dist/components/editor/HSliderLightMobile.d.ts +20 -0
  74. package/dist/components/editor/HSliderLightMobile.js +420 -0
  75. package/dist/components/editor/HTabAspectRatioMobile.d.ts +0 -0
  76. package/dist/components/editor/HTabAspectRatioMobile.js +1 -0
  77. package/dist/components/editor/HTabColorAdjustmentMobile.d.ts +35 -0
  78. package/dist/components/editor/HTabColorAdjustmentMobile.js +8 -0
  79. package/dist/components/editor/HTabPresetMobile.d.ts +14 -0
  80. package/dist/components/editor/HTabPresetMobile.js +10 -0
  81. package/dist/components/editor/HTextField.d.ts +14 -0
  82. package/dist/components/editor/HTextField.js +51 -0
  83. package/dist/components/editor/HWatermarkView.d.ts +6 -0
  84. package/dist/components/editor/HWatermarkView.js +16 -0
  85. package/dist/components/editor/svg/Tick.d.ts +2 -0
  86. package/dist/components/editor/svg/Tick.js +6 -0
  87. package/dist/components/modal/HModalDialog.d.ts +12 -0
  88. package/dist/components/modal/HModalDialog.js +18 -0
  89. package/dist/components/modal/HModalRename.d.ts +14 -0
  90. package/dist/components/modal/HModalRename.js +35 -0
  91. package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +3 -0
  92. package/dist/hooks/demo/HonchoEditorBulkDemo.js +410 -0
  93. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +3 -0
  94. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +354 -0
  95. package/dist/hooks/demo/index.d.ts +2 -0
  96. package/dist/hooks/demo/index.js +2 -0
  97. package/dist/hooks/editor/type.d.ts +174 -0
  98. package/dist/hooks/editor/type.js +1 -0
  99. package/dist/hooks/editor/useHonchoEditorBulk.d.ts +96 -0
  100. package/dist/hooks/editor/useHonchoEditorBulk.js +427 -0
  101. package/dist/hooks/editor/useHonchoEditorSingle.d.ts +44 -0
  102. package/dist/hooks/editor/useHonchoEditorSingle.js +163 -0
  103. package/dist/hooks/useAdjustmentHistory.d.ts +95 -0
  104. package/dist/hooks/useAdjustmentHistory.js +578 -0
  105. package/dist/hooks/useAdjustmentHistoryBatch.d.ts +177 -0
  106. package/dist/hooks/useAdjustmentHistoryBatch.js +1189 -0
  107. package/dist/hooks/useGallerySwipe.d.ts +36 -0
  108. package/dist/hooks/useGallerySwipe.js +344 -0
  109. package/dist/hooks/usePaging.d.ts +89 -0
  110. package/dist/hooks/usePaging.js +211 -0
  111. package/dist/hooks/usePreset.d.ts +82 -0
  112. package/dist/hooks/usePreset.js +344 -0
  113. package/dist/index.d.ts +39 -1474
  114. package/dist/index.js +44 -10960
  115. package/dist/lib/context/EditorContext.d.ts +28 -0
  116. package/dist/lib/context/EditorContext.js +60 -0
  117. package/dist/lib/context/EditorProcessingService.d.ts +36 -0
  118. package/dist/lib/context/EditorProcessingService.js +249 -0
  119. package/dist/lib/editor/honcho-editor.d.ts +324 -0
  120. package/dist/lib/editor/honcho-editor.js +825 -0
  121. package/dist/lib/hooks/useEditor.d.ts +22 -0
  122. package/dist/lib/hooks/useEditor.js +35 -0
  123. package/dist/lib/hooks/useEditorHeadless.d.ts +34 -0
  124. package/dist/lib/hooks/useEditorHeadless.js +207 -0
  125. package/dist/lib/hooks/useImageProcessor.d.ts +18 -0
  126. package/dist/lib/hooks/useImageProcessor.js +113 -0
  127. package/dist/setupTests.d.ts +1 -0
  128. package/dist/setupTests.js +1 -0
  129. package/dist/themes/colors.d.ts +12 -0
  130. package/dist/themes/colors.js +12 -0
  131. package/dist/themes/honchoTheme.d.ts +25 -0
  132. package/dist/themes/honchoTheme.js +94 -0
  133. package/dist/utils/adjustment.d.ts +6 -0
  134. package/dist/utils/adjustment.js +48 -0
  135. package/dist/utils/imageLoader.d.ts +11 -0
  136. package/dist/utils/imageLoader.js +48 -0
  137. package/dist/utils/isMobile.d.ts +1 -0
  138. package/dist/utils/isMobile.js +5 -0
  139. package/package.json +6 -12
  140. package/dist/index.d.mts +0 -1474
  141. package/dist/index.js.map +0 -1
  142. package/dist/index.mjs +0 -10939
  143. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,95 @@
1
+ import { AdjustmentState, Controller } from './editor/type';
2
+ /**
3
+ * Configuration options for the adjustment history hook
4
+ */
5
+ export interface HistoryOptions {
6
+ /** Maximum number of history entries to keep. Use 'unlimited' for no limit */
7
+ maxSize?: number | 'unlimited';
8
+ /** Whether to enable batch mode for grouping multiple changes */
9
+ enableBatching?: boolean;
10
+ /** Enable development warnings for performance issues */
11
+ devWarnings?: boolean;
12
+ }
13
+ /**
14
+ * Information about the current history state
15
+ */
16
+ export interface HistoryInfo {
17
+ /** Whether undo operation is available */
18
+ canUndo: boolean;
19
+ /** Whether redo operation is available */
20
+ canRedo: boolean;
21
+ /** Current position in history (0-based index) */
22
+ currentIndex: number;
23
+ /** Total number of states in history */
24
+ totalStates: number;
25
+ /** Current size of history in memory */
26
+ historySize: number;
27
+ /** Whether batch mode is currently active */
28
+ isBatchMode: boolean;
29
+ }
30
+ /**
31
+ * Actions available for history management
32
+ */
33
+ export interface HistoryActions {
34
+ /** Add a new adjustment state to history */
35
+ pushState: (state: AdjustmentState) => void;
36
+ /** Undo to previous adjustment state */
37
+ undo: () => void;
38
+ /** Redo to next adjustment state */
39
+ redo: () => void;
40
+ /** Jump to specific index in history */
41
+ jumpToIndex: (index: number) => void;
42
+ /** Clear all history and start fresh */
43
+ clearHistory: () => void;
44
+ /** Get a copy of the entire history array */
45
+ getHistory: () => AdjustmentState[];
46
+ /** Trim history to specified size, keeping most recent entries */
47
+ trimHistory: (keepLast: number) => void;
48
+ /** Sync history from backend using getEditorHistory */
49
+ syncFromBackend: () => Promise<void>;
50
+ }
51
+ /**
52
+ * Configuration actions for runtime adjustment
53
+ */
54
+ export interface HistoryConfig {
55
+ /** Set maximum history size */
56
+ setMaxSize: (size: number | 'unlimited') => void;
57
+ /** Enable or disable batch mode */
58
+ setBatchMode: (enabled: boolean, forceHistory?: boolean) => Promise<void>;
59
+ /** Get current memory usage estimate */
60
+ getMemoryUsage: () => number;
61
+ }
62
+ /**
63
+ * Return type for the useAdjustmentHistory hook
64
+ */
65
+ export interface UseAdjustmentHistoryReturn {
66
+ /** Current adjustment state value */
67
+ currentState: AdjustmentState;
68
+ /** Current index in history */
69
+ currentIndex: number;
70
+ /** Information about history state */
71
+ historyInfo: HistoryInfo;
72
+ /** Available history actions */
73
+ actions: HistoryActions;
74
+ /** Configuration options */
75
+ config: HistoryConfig;
76
+ }
77
+ /**
78
+ * Advanced hook for managing AdjustmentState history with undo/redo functionality.
79
+ *
80
+ * Features:
81
+ * - Unlimited or configurable history size
82
+ * - Batch mode for grouping multiple changes into single undo operations
83
+ * - Memory usage monitoring and optimization
84
+ * - Internal stabilization to prevent re-render issues
85
+ * - Jump to any point in history
86
+ * - Automatic AdjustmentState comparison
87
+ *
88
+ * @param initialState - The initial AdjustmentState value
89
+ * @param controller - Controller for backend operations (optional)
90
+ * @param firebaseUid - Firebase UID for backend operations (optional)
91
+ * @param currentImageId - Current image ID for backend operations (optional)
92
+ * @param options - Configuration options for history behavior
93
+ * @returns Object with current state, history info, actions, and config
94
+ */
95
+ export declare function useAdjustmentHistory(initialState: AdjustmentState, controller?: Controller, firebaseUid?: string, currentImageId?: string, options?: HistoryOptions): UseAdjustmentHistoryReturn;
@@ -0,0 +1,578 @@
1
+ import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
2
+ /**
3
+ * Convert AdjustmentState to ColorAdjustment format for backend
4
+ */
5
+ const convertAdjustmentStateToColorAdjustment = (adjustmentState) => {
6
+ return {
7
+ temperature: adjustmentState.tempScore,
8
+ tint: adjustmentState.tintScore,
9
+ saturation: adjustmentState.saturationScore,
10
+ vibrance: adjustmentState.vibranceScore,
11
+ exposure: adjustmentState.exposureScore,
12
+ contrast: adjustmentState.contrastScore,
13
+ highlights: adjustmentState.highlightsScore,
14
+ shadows: adjustmentState.shadowsScore,
15
+ whites: adjustmentState.whitesScore,
16
+ blacks: adjustmentState.blacksScore,
17
+ clarity: adjustmentState.clarityScore,
18
+ sharpness: adjustmentState.sharpnessScore,
19
+ };
20
+ };
21
+ /**
22
+ * Convert ColorAdjustment from backend to AdjustmentState format
23
+ */
24
+ const convertColorAdjustmentToAdjustmentState = (colorAdjustment) => {
25
+ return {
26
+ tempScore: colorAdjustment.temperature,
27
+ tintScore: colorAdjustment.tint,
28
+ saturationScore: colorAdjustment.saturation,
29
+ vibranceScore: colorAdjustment.vibrance,
30
+ exposureScore: colorAdjustment.exposure,
31
+ contrastScore: colorAdjustment.contrast,
32
+ highlightsScore: colorAdjustment.highlights,
33
+ shadowsScore: colorAdjustment.shadows,
34
+ whitesScore: colorAdjustment.whites,
35
+ blacksScore: colorAdjustment.blacks,
36
+ clarityScore: colorAdjustment.clarity,
37
+ sharpnessScore: colorAdjustment.sharpness,
38
+ };
39
+ };
40
+ /**
41
+ * Compare two AdjustmentState objects for equality
42
+ * Uses JSON.stringify for deep comparison of all adjustment values
43
+ */
44
+ const compareAdjustmentStates = (a, b) => {
45
+ try {
46
+ return JSON.stringify(a) === JSON.stringify(b);
47
+ }
48
+ catch (error) {
49
+ // Fallback to manual comparison if JSON.stringify fails
50
+ console.warn('Failed to compare adjustment states with JSON.stringify, falling back to manual comparison:', error);
51
+ return (a.tempScore === b.tempScore &&
52
+ a.tintScore === b.tintScore &&
53
+ a.vibranceScore === b.vibranceScore &&
54
+ a.saturationScore === b.saturationScore &&
55
+ a.exposureScore === b.exposureScore &&
56
+ a.highlightsScore === b.highlightsScore &&
57
+ a.shadowsScore === b.shadowsScore &&
58
+ a.whitesScore === b.whitesScore &&
59
+ a.blacksScore === b.blacksScore &&
60
+ a.contrastScore === b.contrastScore &&
61
+ a.clarityScore === b.clarityScore &&
62
+ a.sharpnessScore === b.sharpnessScore);
63
+ }
64
+ };
65
+ /**
66
+ * Advanced hook for managing AdjustmentState history with undo/redo functionality.
67
+ *
68
+ * Features:
69
+ * - Unlimited or configurable history size
70
+ * - Batch mode for grouping multiple changes into single undo operations
71
+ * - Memory usage monitoring and optimization
72
+ * - Internal stabilization to prevent re-render issues
73
+ * - Jump to any point in history
74
+ * - Automatic AdjustmentState comparison
75
+ *
76
+ * @param initialState - The initial AdjustmentState value
77
+ * @param controller - Controller for backend operations (optional)
78
+ * @param firebaseUid - Firebase UID for backend operations (optional)
79
+ * @param currentImageId - Current image ID for backend operations (optional)
80
+ * @param options - Configuration options for history behavior
81
+ * @returns Object with current state, history info, actions, and config
82
+ */
83
+ export function useAdjustmentHistory(initialState, controller, firebaseUid, currentImageId, options = {}) {
84
+ // Internal stabilization - prevent re-renders from options object recreation
85
+ const internalOptions = useMemo(() => ({
86
+ maxSize: options.maxSize ?? 'unlimited',
87
+ enableBatching: options.enableBatching ?? false,
88
+ devWarnings: options.devWarnings ?? false,
89
+ controller: controller,
90
+ firebaseUid: firebaseUid,
91
+ currentImageId: currentImageId
92
+ }), [
93
+ options.maxSize,
94
+ options.enableBatching,
95
+ options.devWarnings,
96
+ controller,
97
+ firebaseUid,
98
+ currentImageId
99
+ ]);
100
+ // Core state management
101
+ const [history, setHistory] = useState([{ state: initialState, taskId: `initial_${Date.now()}` }]);
102
+ const [currentState, setCurrentState] = useState(initialState);
103
+ const currentIndexRef = useRef(0);
104
+ const currentStateRef = useRef(initialState);
105
+ // Batch mode state - ref to avoid triggering effects
106
+ const batchModeRef = useRef(internalOptions.enableBatching);
107
+ const batchStartIndexRef = useRef(null);
108
+ const batchStartStateRef = useRef(null);
109
+ const batchModeProcessingRef = useRef(false); // Guard against double execution
110
+ // Configuration refs - prevent re-renders when config changes
111
+ const maxSizeRef = useRef(internalOptions.maxSize);
112
+ const devWarningsRef = useRef(internalOptions.devWarnings);
113
+ // Computed current index for return value (defined early for dependencies)
114
+ const currentIndex = currentIndexRef.current;
115
+ // Performance monitoring
116
+ const performanceRef = useRef({
117
+ lastHistorySize: 1,
118
+ lastUpdateTime: Date.now(),
119
+ largeHistoryWarningShown: false
120
+ });
121
+ // Sync currentState with history when not in batch mode
122
+ // useEffect(() => {
123
+ // if (!batchModeRef.current) {
124
+ // console.log(`[useAdjustmentHistory] Syncing currentState with history:`, history[currentIndex]?.state);
125
+ // setCurrentState(history[currentIndex]?.state || initialState);
126
+ // }
127
+ // }, [history, currentIndex, initialState]);
128
+ useEffect(() => {
129
+ if (internalOptions.currentImageId) {
130
+ syncFromBackend().catch(console.error);
131
+ }
132
+ }, [internalOptions.currentImageId, internalOptions.firebaseUid, internalOptions.controller]);
133
+ const getMemoryUsage = useCallback(() => {
134
+ try {
135
+ const historyString = JSON.stringify(history);
136
+ return historyString.length * 2; // Rough estimate: 2 bytes per character
137
+ }
138
+ catch (error) {
139
+ console.warn('Failed to estimate memory usage:', error);
140
+ return history.length * 1000; // Fallback estimate
141
+ }
142
+ }, [history]);
143
+ // Development warnings for performance
144
+ const checkPerformance = useCallback(() => {
145
+ if (!devWarningsRef.current)
146
+ return;
147
+ const now = Date.now();
148
+ const perfData = performanceRef.current;
149
+ // Warn about large history sizes
150
+ if (history.length > 1000 && !perfData.largeHistoryWarningShown) {
151
+ console.warn(`useAdjustmentHistory: Large history size detected (${history.length} entries). Consider setting a maxSize limit.`);
152
+ perfData.largeHistoryWarningShown = true;
153
+ }
154
+ // Update performance tracking
155
+ perfData.lastHistorySize = history.length;
156
+ perfData.lastUpdateTime = now;
157
+ }, [history.length]);
158
+ // Trim history to specified size, keeping most recent entries
159
+ const trimHistoryToSize = useCallback((size) => {
160
+ if (size <= 0)
161
+ return;
162
+ setHistory(prevHistory => {
163
+ if (prevHistory.length <= size)
164
+ return prevHistory;
165
+ const startIndex = Math.max(0, prevHistory.length - size);
166
+ const trimmedHistory = prevHistory.slice(startIndex);
167
+ // Adjust current index to maintain relative position
168
+ const prevIndex = currentIndexRef.current;
169
+ const adjustedIndex = prevIndex - startIndex;
170
+ currentIndexRef.current = Math.max(0, Math.min(adjustedIndex, trimmedHistory.length - 1));
171
+ return trimmedHistory;
172
+ });
173
+ }, []);
174
+ // Apply max size limit when history grows
175
+ const enforceMaxSize = useCallback(() => {
176
+ if (maxSizeRef.current === 'unlimited')
177
+ return;
178
+ const maxSize = maxSizeRef.current;
179
+ if (history.length > maxSize) {
180
+ trimHistoryToSize(maxSize);
181
+ }
182
+ }, [history.length, trimHistoryToSize]);
183
+ // Push new state to history
184
+ const pushState = useCallback((newState) => {
185
+ // Always update currentState immediately for smooth UI
186
+ currentStateRef.current = newState;
187
+ setCurrentState(newState);
188
+ if (batchModeRef.current) {
189
+ // In batch mode: Don't update history yet, just update UI state
190
+ // History will be updated when batch mode ends
191
+ console.log(`[useAdjustmentHistory] Pushing state in batch mode:`, newState);
192
+ return;
193
+ }
194
+ // Normal mode: Update history immediately
195
+ console.log(`[useAdjustmentHistory] Pushing new state to history:`, newState);
196
+ setHistory(prevHistory => {
197
+ const truncatedHistory = prevHistory.slice(0, currentIndexRef.current + 1);
198
+ // const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
199
+ // we put the taskId empty to check this state no need to save it
200
+ const newHistory = [...truncatedHistory, { state: newState, taskId: "" }];
201
+ currentIndexRef.current = newHistory.length - 1;
202
+ return newHistory;
203
+ });
204
+ }, []);
205
+ // Undo to previous state
206
+ const undo = useCallback(async () => {
207
+ if (currentIndexRef.current > 0) {
208
+ const newIndex = currentIndexRef.current - 1;
209
+ const historyEntry = history[newIndex];
210
+ const newState = historyEntry.state;
211
+ currentIndexRef.current = newIndex;
212
+ currentStateRef.current = newState;
213
+ setCurrentState(newState);
214
+ console.log(`[useAdjustmentHistory] Undoing to index ${newIndex}:`, newState);
215
+ // Call controller to set history index in backend if taskId exists
216
+ if (historyEntry.taskId && internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
217
+ try {
218
+ console.log(`🔙 Undo: Setting history index to taskId: ${historyEntry.taskId}`);
219
+ await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, internalOptions.currentImageId, historyEntry.taskId);
220
+ console.log('✅ Successfully set history index for undo');
221
+ }
222
+ catch (error) {
223
+ console.error('❌ Failed to set history index for undo:', error);
224
+ }
225
+ }
226
+ else {
227
+ console.log("🔙 Undo: No taskId available for setting history index", historyEntry.taskId, internalOptions.firebaseUid, internalOptions.currentImageId);
228
+ }
229
+ // Exit batch mode when undoing
230
+ if (batchModeRef.current) {
231
+ batchModeRef.current = false;
232
+ batchStartIndexRef.current = null;
233
+ batchStartStateRef.current = null;
234
+ }
235
+ }
236
+ }, [history, internalOptions]);
237
+ // Redo to next state
238
+ const redo = useCallback(async () => {
239
+ if (currentIndexRef.current < history.length - 1) {
240
+ const newIndex = currentIndexRef.current + 1;
241
+ const historyEntry = history[newIndex];
242
+ const newState = historyEntry.state;
243
+ currentIndexRef.current = newIndex;
244
+ currentStateRef.current = newState;
245
+ setCurrentState(newState);
246
+ console.log(`[useAdjustmentHistory] Redoing to index ${newIndex}:`, newState);
247
+ // Call controller to set history index in backend if taskId exists
248
+ if (historyEntry.taskId && internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
249
+ try {
250
+ console.log(`🔄 Redo: Setting history index to taskId: ${historyEntry.taskId}`);
251
+ await internalOptions.controller.setHistoryIndex(internalOptions.firebaseUid, internalOptions.currentImageId, historyEntry.taskId);
252
+ console.log('✅ Successfully set history index for redo');
253
+ }
254
+ catch (error) {
255
+ console.error('❌ Failed to set history index for redo:', error);
256
+ }
257
+ }
258
+ else {
259
+ console.warn('🔄 Redo: No taskId available for setting history index', historyEntry.taskId, internalOptions.firebaseUid, internalOptions.currentImageId);
260
+ }
261
+ }
262
+ }, [history, internalOptions]);
263
+ // Reset history with new initial state
264
+ // const reset = useCallback((newInitialState: AdjustmentState) => {
265
+ // console.log("Reset called setHistory");
266
+ // setHistory([{ state: newInitialState, taskId: `reset_${Date.now()}` }]);
267
+ // currentIndexRef.current = 0;
268
+ // currentStateRef.current = newInitialState;
269
+ // batchModeRef.current = internalOptions.enableBatching;
270
+ // batchStartIndexRef.current = null;
271
+ // batchStartStateRef.current = null;
272
+ // }, [internalOptions.enableBatching]);
273
+ // Jump to specific index in history
274
+ const jumpToIndex = useCallback((index) => {
275
+ if (index >= 0 && index < history.length) {
276
+ const historyEntry = history[index];
277
+ const newState = historyEntry.state;
278
+ currentIndexRef.current = index;
279
+ currentStateRef.current = newState;
280
+ setCurrentState(newState);
281
+ // Exit batch mode when jumping
282
+ if (batchModeRef.current) {
283
+ batchModeRef.current = false;
284
+ batchStartIndexRef.current = null;
285
+ batchStartStateRef.current = null;
286
+ }
287
+ }
288
+ }, [history]);
289
+ // Clear all history and start fresh
290
+ const clearHistory = useCallback(() => {
291
+ console.log("clearHistory called setHistory");
292
+ setHistory([{ state: currentStateRef.current, taskId: `clear_${Date.now()}` }]);
293
+ currentIndexRef.current = 0;
294
+ batchModeRef.current = internalOptions.enableBatching;
295
+ batchStartIndexRef.current = null;
296
+ batchStartStateRef.current = null;
297
+ }, [internalOptions.enableBatching]);
298
+ // Get copy of entire history
299
+ const getHistory = useCallback(() => {
300
+ return history.map(entry => entry.state);
301
+ }, [history]);
302
+ // Manually trim history
303
+ const trimHistory = useCallback((keepLast) => {
304
+ trimHistoryToSize(keepLast);
305
+ }, [trimHistoryToSize]);
306
+ // Sync/replace entire history with new list
307
+ const syncHistory = useCallback((newHistory, targetIndex) => {
308
+ throw Error('syncHistory is not implemented yet');
309
+ // // Validate input
310
+ // if (!Array.isArray(newHistory) || newHistory.length === 0) {
311
+ // console.warn('syncHistory: newHistory must be a non-empty array');
312
+ // return;
313
+ // }
314
+ // // Validate all items are AdjustmentState objects
315
+ // const isValidHistory = newHistory.every(state =>
316
+ // 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
+ // );
330
+ // if (!isValidHistory) {
331
+ // console.warn('syncHistory: All items in newHistory must be valid AdjustmentState objects');
332
+ // return;
333
+ // }
334
+ // // Exit batch mode if active
335
+ // if (batchModeRef.current) {
336
+ // batchModeRef.current = false;
337
+ // batchStartIndexRef.current = null;
338
+ // batchStartStateRef.current = null;
339
+ // }
340
+ // // Determine target index
341
+ // let finalIndex = targetIndex ?? newHistory.length - 1; // Default to last item
342
+ // finalIndex = Math.max(0, Math.min(finalIndex, newHistory.length - 1)); // Clamp to valid range
343
+ // // Create a copy of the new history to avoid mutations and convert to HistoryEntry format
344
+ // const historyToSet = newHistory.map((state, index) => ({
345
+ // state: { ...state },
346
+ // taskId: `sync_${Date.now()}_${index}`
347
+ // }));
348
+ // // Apply max size limit if needed
349
+ // if (maxSizeRef.current !== 'unlimited' && historyToSet.length > maxSizeRef.current) {
350
+ // const trimAmount = historyToSet.length - maxSizeRef.current;
351
+ // const trimmedHistory = historyToSet.slice(trimAmount);
352
+ // // Adjust target index
353
+ // finalIndex = Math.max(0, finalIndex - trimAmount);
354
+ // setHistory(trimmedHistory);
355
+ // setCurrentIndex(finalIndex);
356
+ // console.log(`syncHistory: Trimmed ${trimAmount} entries to respect maxSize of ${maxSizeRef.current}`);
357
+ // setCurrentState(trimmedHistory[finalIndex].state);
358
+ // if (devWarningsRef.current) {
359
+ // console.warn(`syncHistory: Trimmed ${trimAmount} entries to respect maxSize of ${maxSizeRef.current}`);
360
+ // }
361
+ // } else {
362
+ // // Set history as-is
363
+ // setHistory(historyToSet);
364
+ // setCurrentIndex(finalIndex);
365
+ // console.log(`syncHistory: Synchronized ${historyToSet.length} states, current index: ${finalIndex}`);
366
+ // setCurrentState(historyToSet[finalIndex].state);
367
+ // }
368
+ // if (devWarningsRef.current) {
369
+ // console.log(`syncHistory: Synchronized ${historyToSet.length} states, current index: ${finalIndex}`);
370
+ // }
371
+ }, []);
372
+ // Configuration setters
373
+ const setMaxSize = useCallback((size) => {
374
+ maxSizeRef.current = size;
375
+ if (size !== 'unlimited') {
376
+ enforceMaxSize();
377
+ }
378
+ }, [enforceMaxSize]);
379
+ const setBatchMode = useCallback(async (enabled, forceHistory = false) => {
380
+ const wasInBatch = batchModeRef.current;
381
+ console.log(`🔧 setBatchMode called: enabled=${enabled}, wasInBatch=${wasInBatch}, currentIndex=${currentIndexRef.current}, historyLength=${history.length}`);
382
+ if (enabled && !wasInBatch) {
383
+ // Starting batch mode - save current state as batch start
384
+ batchModeRef.current = true;
385
+ batchStartIndexRef.current = currentIndexRef.current;
386
+ batchStartStateRef.current = currentStateRef.current;
387
+ console.log("🔧 setBatchMode(true): Starting batch mode", {
388
+ currentState: currentStateRef.current,
389
+ currentIndex: currentIndexRef.current,
390
+ batchStartState: currentStateRef.current
391
+ });
392
+ }
393
+ else if (!enabled && wasInBatch) {
394
+ // Guard against double execution
395
+ console.log("🔧 setBatchMode: Ending batch mode", batchModeProcessingRef.current);
396
+ if (batchModeProcessingRef.current) {
397
+ console.log("⚠️ setBatchMode(false) already processing, skipping duplicate call");
398
+ return;
399
+ }
400
+ console.log("🔧 setBatchMode: Ending batch mode", currentStateRef.current, `batchStartIndex=${batchStartIndexRef.current}`);
401
+ // Ending batch mode - commit final state to history
402
+ batchModeProcessingRef.current = true;
403
+ batchModeRef.current = false;
404
+ // Only add to history if state actually changed from batch start
405
+ const statesEqual = batchStartStateRef.current ?
406
+ compareAdjustmentStates(currentStateRef.current, batchStartStateRef.current) : true;
407
+ console.log("🔧 setBatchMode(false): Comparing states", {
408
+ currentState: currentStateRef.current,
409
+ batchStartState: batchStartStateRef.current,
410
+ statesEqual,
411
+ forceHistory,
412
+ willAddToHistory: batchStartStateRef.current && (!statesEqual || forceHistory)
413
+ });
414
+ if (batchStartStateRef.current && (!statesEqual || forceHistory)) {
415
+ // Generate a unique task ID for this history entry
416
+ const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
417
+ console.log('📋 Before SetHistory state for backend:', batchStartIndexRef.current, currentStateRef.current, history.length);
418
+ // Store variables for backend call BEFORE setHistory
419
+ let replaceFromTaskId;
420
+ const batchStartIndex = batchStartIndexRef.current ?? currentIndexRef.current;
421
+ const wasInMiddleOfHistory = batchStartIndex < history.length - 1;
422
+ if (wasInMiddleOfHistory) {
423
+ // Get the task_id from the original history entry to use as replace_from
424
+ const currentHistoryEntry = history[batchStartIndex];
425
+ replaceFromTaskId = currentHistoryEntry?.taskId;
426
+ console.log(`📍 Was in middle of history (index ${batchStartIndex}), using replace_from: ${replaceFromTaskId}`);
427
+ }
428
+ else {
429
+ console.log(`📍 At latest history (index ${batchStartIndex}), no replace_from needed`);
430
+ }
431
+ setHistory(prevHistory => {
432
+ // Check if we were in the middle of history BEFORE any truncation
433
+ // Handle case where batchStartIndexRef.current might be null (e.g., after undo)
434
+ const batchStartIndex = batchStartIndexRef.current ?? currentIndexRef.current;
435
+ const truncatedHistory = prevHistory.slice(0, batchStartIndex + 1);
436
+ const newHistoryEntry = {
437
+ state: currentStateRef.current,
438
+ taskId: taskId
439
+ };
440
+ const newHistory = [...truncatedHistory, newHistoryEntry];
441
+ currentIndexRef.current = newHistory.length - 1;
442
+ return newHistory;
443
+ });
444
+ // Call controller to create history in backend - OUTSIDE of setHistory callback
445
+ if (internalOptions.controller && internalOptions.firebaseUid && internalOptions.currentImageId) {
446
+ try {
447
+ console.log('🔄 Creating editor history in backend for batch mode end');
448
+ console.log('📋 Current state for backend:', batchStartIndexRef.current, currentStateRef.current, history.length);
449
+ const createEditorConfigPayload = {
450
+ gallery_id: internalOptions.currentImageId,
451
+ task_id: taskId,
452
+ color_adjustment: convertAdjustmentStateToColorAdjustment(currentStateRef.current),
453
+ ...(replaceFromTaskId && { replace_from: replaceFromTaskId })
454
+ };
455
+ // Create editor config with current adjustments
456
+ internalOptions.controller.createEditorConfig(internalOptions.firebaseUid, createEditorConfigPayload).then(() => {
457
+ console.log('✅ Successfully created editor history in backend');
458
+ }).catch((error) => {
459
+ console.error('❌ Failed to create editor history in backend:', error);
460
+ });
461
+ }
462
+ catch (error) {
463
+ console.error('❌ Error calling controller.createEditorConfig:', error);
464
+ }
465
+ }
466
+ else {
467
+ if (internalOptions.devWarnings) {
468
+ console.warn('⚠️ Controller, firebaseUid, or currentImageId not provided - skipping backend history creation');
469
+ }
470
+ }
471
+ }
472
+ else {
473
+ if (forceHistory) {
474
+ console.log("🔧 setBatchMode: Force history requested but no batch start state");
475
+ }
476
+ else {
477
+ console.log("🔧 setBatchMode: No changes since batch start, not adding to history");
478
+ }
479
+ }
480
+ batchStartIndexRef.current = null;
481
+ batchStartStateRef.current = null;
482
+ batchModeProcessingRef.current = false; // Reset processing flag
483
+ }
484
+ }, [internalOptions, history]);
485
+ // Sync history from backend using getEditorHistory
486
+ const syncFromBackend = useCallback(async () => {
487
+ if (!internalOptions.controller || !internalOptions.firebaseUid || !internalOptions.currentImageId) {
488
+ console.warn('⚠️ syncFromBackend: Controller, firebaseUid, or currentImageId not provided - cannot sync from backend');
489
+ return;
490
+ }
491
+ try {
492
+ console.log('🔄 Syncing history from backend using getEditorHistory');
493
+ const historyResponse = await internalOptions.controller.getEditorHistory(internalOptions.firebaseUid, internalOptions.currentImageId);
494
+ // Sort history by timestamp (oldest first) before processing
495
+ const sortedHistory = [...historyResponse.history].sort((a, b) => {
496
+ const timeA = new Date(a.log.created_at).getTime();
497
+ const timeB = new Date(b.log.created_at).getTime();
498
+ return timeA - timeB; // Ascending order (oldest first)
499
+ });
500
+ // Convert backend history to AdjustmentState format with taskIds
501
+ const backendHistoryEntries = sortedHistory.map((entry) => ({
502
+ state: convertColorAdjustmentToAdjustmentState(entry.editor_config.color_adjustment),
503
+ taskId: entry.task_id
504
+ }));
505
+ // Find the index of the current task
506
+ let currentTaskIndex = -1;
507
+ if (historyResponse.current_task_id) {
508
+ currentTaskIndex = backendHistoryEntries.findIndex(entry => entry.taskId === historyResponse.current_task_id);
509
+ }
510
+ // If current_task_id is not found in history, default to the last entry
511
+ if (currentTaskIndex === -1 && backendHistoryEntries.length > 0) {
512
+ currentTaskIndex = backendHistoryEntries.length - 1;
513
+ console.warn(`⚠️ Current task ID "${historyResponse.current_task_id}" not found in history, defaulting to last entry`);
514
+ }
515
+ // Handle empty history case
516
+ if (backendHistoryEntries.length === 0) {
517
+ console.log('📝 Backend history is empty, keeping current local history');
518
+ return;
519
+ }
520
+ // Exit batch mode if active
521
+ if (batchModeRef.current) {
522
+ batchModeRef.current = false;
523
+ batchStartIndexRef.current = null;
524
+ batchStartStateRef.current = null;
525
+ }
526
+ // Update history state with backend data
527
+ setHistory(backendHistoryEntries);
528
+ currentIndexRef.current = Math.max(0, currentTaskIndex);
529
+ console.log(`📍 Setting current index to: ${Math.max(0, currentTaskIndex)} (task_id: ${historyResponse.current_task_id})`);
530
+ currentStateRef.current = backendHistoryEntries[Math.max(0, currentTaskIndex)].state;
531
+ setCurrentState(backendHistoryEntries[Math.max(0, currentTaskIndex)].state);
532
+ console.log(`✅ Successfully synced ${backendHistoryEntries.length} history entries from backend`);
533
+ console.log(`📍 Set current index to: ${Math.max(0, currentTaskIndex)} (task_id: ${historyResponse.current_task_id})`);
534
+ }
535
+ catch (error) {
536
+ console.error('❌ Failed to sync history from backend:', error);
537
+ throw error;
538
+ }
539
+ }, [internalOptions]);
540
+ // History info object
541
+ const historyInfo = useMemo(() => ({
542
+ canUndo: currentIndex > 0,
543
+ canRedo: currentIndex < history.length - 1,
544
+ currentIndex: currentIndex,
545
+ totalStates: history.length,
546
+ historySize: getMemoryUsage(),
547
+ isBatchMode: batchModeRef.current
548
+ }), [history.length, getMemoryUsage, currentIndex]);
549
+ // Actions object - stabilized with useMemo
550
+ const actions = useMemo(() => ({
551
+ pushState,
552
+ undo,
553
+ redo,
554
+ jumpToIndex,
555
+ clearHistory,
556
+ getHistory,
557
+ trimHistory,
558
+ syncFromBackend
559
+ }), [pushState, undo, redo, jumpToIndex, clearHistory, getHistory, trimHistory, syncFromBackend]);
560
+ // Config object - stabilized with useMemo
561
+ const config = useMemo(() => ({
562
+ setMaxSize,
563
+ setBatchMode,
564
+ getMemoryUsage
565
+ }), [setMaxSize, setBatchMode, getMemoryUsage]);
566
+ // Apply max size enforcement when history changes
567
+ useEffect(() => {
568
+ enforceMaxSize();
569
+ checkPerformance();
570
+ }, [enforceMaxSize, checkPerformance]);
571
+ return {
572
+ currentState,
573
+ currentIndex,
574
+ historyInfo,
575
+ actions,
576
+ config
577
+ };
578
+ }