@yogiswara/honcho-editor-ui 2.6.6 → 2.6.7

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.
@@ -26,34 +26,34 @@ export default function HAccordionPreset(props) {
26
26
  }
27
27
  };
28
28
  const isPanelExpanded = (panelName) => props.expandedPanels.includes(panelName);
29
- return (_jsx(_Fragment, { children: _jsxs(Box, { children: [_jsxs(Accordion, { sx: accordionStyle, expanded: isPanelExpanded('preset'), onChange: props.onChange('preset'), disableGutters: true, children: [_jsx(AccordionSummary, { sx: { pr: 0 }, children: _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { sx: { ...typography.titleMedium, color: colors.surface }, children: "Preset" }), _jsx(CardMedia, { component: "img", image: isPanelExpanded('preset') ? "/v1/svg/expanded-editor.svg" : "/v1/svg/expand-editor.svg", sx: { width: "11.67px", height: "5.83px" } })] }) }), _jsx(AccordionDetails, { sx: { pr: "4px" }, children: _jsxs(Stack, { direction: "column", gap: "8px", sx: { pt: '0px', pb: '0px', mx: '0px', width: '100%' }, children: [props.presets.map((preset) => (_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [props.selectedPreset != preset.id && (_jsx(Button, { sx: {
30
- width: '100px',
31
- textWrap: 'nowrap',
32
- overflow: 'hidden',
33
- textOverflow: 'ellipsis',
34
- display: 'block',
35
- textTransform: 'none',
36
- color: colors.surface,
37
- pr: "120px",
38
- pl: "0px",
39
- ml: "0px",
40
- justifyContent: 'flex-start',
41
- ...typography.bodyMedium
42
- }, onClick: () => props.onSelectPreset(preset.id), children: preset.name })), props.selectedPreset === preset.id && (_jsx(Button, { sx: {
43
- width: '84px',
44
- textWrap: 'nowrap',
45
- overflow: 'hidden',
46
- textOverflow: 'ellipsis',
47
- display: 'block',
48
- color: colors.surface,
49
- pr: "82px",
50
- pl: "0px",
51
- ml: "0px",
52
- justifyContent: 'flex-start',
53
- ...typography.bodyMedium
54
- }, onClick: () => props.onSelectPreset(preset.id), children: preset.name })), _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, children: [props.selectedPreset === preset.id && (_jsx(CardMedia, { component: "img", image: "v1/svg/check-ratio-editor.svg", sx: { width: "20px", height: "20px" } })), _jsx(IconButton, { "aria-label": preset.name, onClick: (event) => props.onPresetMenuClick(event, preset.id), sx: { padding: "0px", margin: "0px" }, children: _jsx(CardMedia, { component: "img", image: "/v1/svg/dots-editor.svg", alt: "Options", sx: { padding: "0px", margin: "0px" } }) })] })] }, preset.id))), _jsx(Button, { variant: "text", sx: { color: colors.surface, border: "1px solid",
55
- borderColor: colors.surface,
56
- borderRadius: "40px",
57
- textTransform: 'none',
58
- }, onClick: props.onOpenPresetModal, children: "Create Preset" })] }) })] }), _jsxs(Accordion, { sx: accordionStyle, expanded: isPanelExpanded('watermark'), onChange: props.onChange('watermark'), disableGutters: true, children: [_jsx(AccordionSummary, { sx: { pr: 0 }, children: _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { sx: { ...typography.titleMedium, color: colors.surface }, children: "Watermark" }), _jsx(CardMedia, { component: "img", image: isPanelExpanded('watermark') ? "/v1/svg/expanded-editor.svg" : "/v1/svg/expand-editor.svg", sx: { width: "11.67px", height: "5.83px" } })] }) }), _jsx(AccordionDetails, { children: _jsxs(Stack, { direction: "column", children: [_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { color: "initial", children: "Jonathan" }), _jsx(IconButton, { "aria-label": "options", children: _jsx(CardMedia, { component: "img", image: "/v1/svg/dots-editor.svg", alt: "Options" }) })] }), _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { color: "initial", children: "Jonathan" }), _jsx(IconButton, { "aria-label": "options", children: _jsx(CardMedia, { component: "img", image: "/v1/svg/dots-editor.svg", alt: "Options" }) })] }), _jsx(Button, { variant: "text", sx: { color: colors.surface, border: "1px solid", borderColor: colors.surface, borderRadius: "40px", textTransform: 'none', }, onClick: props.onOpenWatermarkView, children: "Create Watermark" })] }) })] })] }) }));
29
+ return (_jsx(_Fragment, { children: _jsx(Box, { children: _jsxs(Accordion, { sx: accordionStyle, expanded: isPanelExpanded('preset'), onChange: props.onChange('preset'), disableGutters: true, children: [_jsx(AccordionSummary, { sx: { pr: 0 }, children: _jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [_jsx(Typography, { sx: { ...typography.titleMedium, color: colors.surface }, children: "Preset" }), _jsx(CardMedia, { component: "img", image: isPanelExpanded('preset') ? "/v1/svg/expanded-editor.svg" : "/v1/svg/expand-editor.svg", sx: { width: "11.67px", height: "5.83px" } })] }) }), _jsx(AccordionDetails, { sx: { pr: "4px" }, children: _jsxs(Stack, { direction: "column", gap: "8px", sx: { pt: '0px', pb: '0px', mx: '0px', width: '100%' }, children: [props.presets.map((preset) => (_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { width: '100%' }, children: [props.selectedPreset != preset.id && (_jsx(Button, { sx: {
30
+ width: '100px',
31
+ textWrap: 'nowrap',
32
+ overflow: 'hidden',
33
+ textOverflow: 'ellipsis',
34
+ display: 'block',
35
+ textTransform: 'none',
36
+ color: colors.surface,
37
+ pr: "120px",
38
+ pl: "0px",
39
+ ml: "0px",
40
+ justifyContent: 'flex-start',
41
+ ...typography.bodyMedium
42
+ }, onClick: () => props.onSelectPreset(preset.id), children: preset.name })), props.selectedPreset === preset.id && (_jsx(Button, { sx: {
43
+ width: '84px',
44
+ textWrap: 'nowrap',
45
+ overflow: 'hidden',
46
+ textOverflow: 'ellipsis',
47
+ display: 'block',
48
+ color: colors.surface,
49
+ pr: "82px",
50
+ pl: "0px",
51
+ ml: "0px",
52
+ justifyContent: 'flex-start',
53
+ ...typography.bodyMedium
54
+ }, onClick: () => props.onSelectPreset(preset.id), children: preset.name })), _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, children: [props.selectedPreset === preset.id && (_jsx(CardMedia, { component: "img", image: "v1/svg/check-ratio-editor.svg", sx: { width: "20px", height: "20px" } })), _jsx(IconButton, { "aria-label": preset.name, onClick: (event) => props.onPresetMenuClick(event, preset.id), sx: { padding: "0px", margin: "0px" }, children: _jsx(CardMedia, { component: "img", image: "/v1/svg/dots-editor.svg", alt: "Options", sx: { padding: "0px", margin: "0px" } }) })] })] }, preset.id))), _jsx(Button, { variant: "text", sx: { color: colors.surface, border: "1px solid",
55
+ borderColor: colors.surface,
56
+ borderRadius: "40px",
57
+ textTransform: 'none',
58
+ }, onClick: props.onOpenPresetModal, children: "Create Preset" })] }) })] }) }) }));
59
59
  }
@@ -63,6 +63,7 @@ const createMockGallery = (id, adjustments) => ({
63
63
  });
64
64
  // Mock Controller implementation
65
65
  const createMockController = () => {
66
+ console.log('[Controller] 🏭 createMockController() called - Creating new mock controller instance for bulk editor');
66
67
  const mockImages = [
67
68
  createMockGallery('1', { tempScore: 5, exposureScore: 2 }),
68
69
  createMockGallery('2'),
@@ -77,19 +78,24 @@ const createMockController = () => {
77
78
  ];
78
79
  return {
79
80
  onGetImage: async (uid, imageId) => {
81
+ console.log(`[Controller] 📷 onGetImage called: uid=${uid}, imageId=${imageId}`);
80
82
  await new Promise(resolve => setTimeout(resolve, 500));
81
83
  const image = mockImages.find(img => img.id === imageId);
82
- if (!image)
84
+ if (!image) {
85
+ console.error(`[Controller] ❌ Image ${imageId} not found`);
83
86
  throw new Error(`Image ${imageId} not found`);
87
+ }
88
+ console.log(`[Controller] 📷 onGetImage returning image:`, image.id);
84
89
  return image;
85
90
  },
86
91
  getImageList: async (uid, eventId, page) => {
92
+ console.log(`[Controller] 📋 getImageList called: uid=${uid}, eventId=${eventId}, page=${page}`);
87
93
  await new Promise(resolve => setTimeout(resolve, 800));
88
94
  const pageSize = 4;
89
95
  const startIndex = (page - 1) * pageSize;
90
96
  const endIndex = startIndex + pageSize;
91
97
  const pageImages = mockImages.slice(startIndex, endIndex);
92
- return {
98
+ const result = {
93
99
  gallery: pageImages,
94
100
  limit: pageSize,
95
101
  current_page: page,
@@ -97,50 +103,72 @@ const createMockController = () => {
97
103
  next_page: endIndex < mockImages.length ? page + 1 : 0,
98
104
  sum_of_image: pageImages.length,
99
105
  };
106
+ console.log(`[Controller] 📋 getImageList returning ${pageImages.length} images for page ${page}`, result);
107
+ return result;
100
108
  },
101
109
  syncConfig: async (uid) => {
110
+ console.log(`[Controller] 🔄 syncConfig called: uid=${uid}`);
102
111
  await new Promise(resolve => setTimeout(resolve, 200));
112
+ console.log(`[Controller] 🔄 syncConfig completed for uid=${uid}`);
103
113
  },
104
114
  handleBack: (uid, imageId) => {
105
- console.log(`Back to: ${imageId}`);
115
+ console.log(`[Controller] ⬅️ handleBack called: uid=${uid}, imageId=${imageId}`);
116
+ console.log(`[Controller] ⬅️ Back to: ${imageId}`);
106
117
  },
107
118
  getPresets: async (uid) => {
108
- return [
119
+ console.log(`[Controller] 🎨 getPresets called: uid=${uid}`);
120
+ await new Promise(resolve => setTimeout(resolve, 300));
121
+ const presets = [
109
122
  { id: '1', name: 'Warm Sunset', is_default: false, temperature: 15, tint: 5, saturation: 8, vibrance: 12, exposure: 2, contrast: 5, highlights: -10, shadows: 8, whites: 3, blacks: -5, clarity: 4, sharpness: 6 },
110
123
  { id: '2', name: 'Cool Morning', is_default: false, temperature: -12, tint: -3, saturation: -2, vibrance: 5, exposure: 1, contrast: 3, highlights: -5, shadows: 12, whites: 8, blacks: -8, clarity: 6, sharpness: 4 },
111
124
  { id: '3', name: 'High Contrast', is_default: false, temperature: 0, tint: 0, saturation: 5, vibrance: 8, exposure: 0, contrast: 20, highlights: -15, shadows: 15, whites: 10, blacks: -10, clarity: 15, sharpness: 8 },
112
125
  ];
126
+ console.log(`[Controller] 🎨 getPresets returning ${presets.length} presets`, presets.map(p => ({ id: p.id, name: p.name })));
127
+ return presets;
113
128
  },
114
129
  createPreset: async (uid, name, settings) => {
115
- console.log(`Creating preset: ${name}`, settings);
130
+ console.log(`[Controller] ➕ createPreset called: uid=${uid}, name=${name}`, settings);
131
+ await new Promise(resolve => setTimeout(resolve, 500));
132
+ console.log(`[Controller] ➕ createPreset completed: ${name}`);
116
133
  },
117
134
  deletePreset: async (uid, presetId) => {
118
- console.log(`Deleting preset: ${presetId}`);
135
+ console.log(`[Controller] 🗑️ deletePreset called: uid=${uid}, presetId=${presetId}`);
136
+ await new Promise(resolve => setTimeout(resolve, 300));
137
+ console.log(`[Controller] 🗑️ deletePreset completed: ${presetId}`);
119
138
  },
120
139
  updatePreset: async (uid, data) => {
121
- console.log(`Updating preset:`, data);
140
+ console.log(`[Controller] 🔄 updatePreset called: uid=${uid}`, data);
141
+ await new Promise(resolve => setTimeout(resolve, 300));
142
+ console.log(`[Controller] 🔄 updatePreset completed:`, data.name);
122
143
  },
123
144
  createEditorConfig: async (uid, payload) => {
145
+ console.log(`[Controller] ⚙️ createEditorConfig called: uid=${uid}`, payload);
124
146
  await new Promise(resolve => setTimeout(resolve, 300));
125
- console.log('Creating editor config:', payload);
147
+ console.log(`[Controller] ⚙️ createEditorConfig completed for gallery_id=${payload.gallery_id}, task_id=${payload.task_id}`);
126
148
  },
127
149
  getEditorHistory: async (uid, imageId) => {
150
+ console.log(`[Controller] 📚 getEditorHistory called: uid=${uid}, imageId=${imageId}`);
128
151
  await new Promise(resolve => setTimeout(resolve, 200));
129
- // Mock history response - return empty history for demo
130
- return {
152
+ const result = {
131
153
  current_task_id: "",
132
154
  history: []
133
155
  };
156
+ console.log(`[Controller] 📚 getEditorHistory returning empty history for demo:`, result);
157
+ return result;
134
158
  },
135
159
  getGalleryUpdateTimestamp: async (uid, eventId) => {
160
+ console.log(`[Controller] ⏰ getGalleryUpdateTimestamp called: uid=${uid}, eventId=${eventId}`);
136
161
  await new Promise(resolve => setTimeout(resolve, 100));
137
- return {
162
+ const result = {
138
163
  gallery: []
139
164
  };
165
+ console.log(`[Controller] ⏰ getGalleryUpdateTimestamp returning:`, result);
166
+ return result;
140
167
  },
141
168
  setHistoryIndex: async (uid, imageId, taskId) => {
169
+ console.log(`[Controller] 📍 setHistoryIndex called: uid=${uid}, imageId=${imageId}, taskId=${taskId}`);
142
170
  await new Promise(resolve => setTimeout(resolve, 100));
143
- console.log(`Setting history index for image ${imageId} to task ${taskId}`);
171
+ console.log(`[Controller] 📍 setHistoryIndex completed - Set history index for image ${imageId} to task ${taskId}`);
144
172
  },
145
173
  };
146
174
  };
@@ -225,4 +253,11 @@ export const HonchoEditorBulkDemo = () => {
225
253
  } })] }), adjustments.length > 0 && (_jsx(Box, { mt: 1, children: _jsx(Stack, { direction: "row", spacing: 0.5, flexWrap: "wrap", children: adjustments.map((adj, index) => (_jsx(Chip, { label: adj, size: "small", variant: "outlined", sx: { fontSize: '0.7rem', height: 20 } }, index))) }) }))] })] }) }, image.key));
226
254
  }) }), isLoading && imageData.length === 0 && (_jsx(Box, { display: "flex", justifyContent: "center", py: 4, children: _jsx(CircularProgress, {}) })), _jsxs(Box, { mt: 4, children: [_jsx(Divider, {}), _jsx(Typography, { variant: "body2", color: "text.secondary", align: "center", sx: { mt: 2 }, children: "Demo Notes: This uses mock data and simulated API calls. Select images and try the bulk adjustment controls above." })] })] }));
227
255
  };
256
+ // Add debugging function to global scope for manual testing
257
+ if (typeof window !== 'undefined') {
258
+ window.testPresetSelection = () => {
259
+ console.log("🧪 Manual preset test - this would normally be called by the UI");
260
+ console.log("🧪 To test properly, use the actual preset dropdown in the UI above");
261
+ };
262
+ }
228
263
  export default HonchoEditorBulkDemo;
@@ -260,7 +260,7 @@ export const HonchoEditorSingleCleanDemo = () => {
260
260
  if (!newPresetName.trim())
261
261
  return;
262
262
  try {
263
- const preset = await actions.createPreset(newPresetName);
263
+ const preset = await actions.createPreset(newPresetName, state.currentAdjustments);
264
264
  if (preset) {
265
265
  setNewPresetName('');
266
266
  console.log('Preset created successfully:', preset);
@@ -163,7 +163,7 @@ export interface Controller {
163
163
  updatePreset(firebaseUid: string, data: Preset): Promise<void>;
164
164
  createEditorConfig(firebaseUid: string, payload: CreateEditorTaskRequest): Promise<void>;
165
165
  getEditorHistory(firebaseUid: string, imageID: string): Promise<GetHistoryResponse>;
166
- getGalleryUpdateTimestamp(firebaseUid: string, eventID: string): Promise<GetGalleryUpdateTimestampResponse>;
166
+ getGalleryUpdateTimestamp(firebaseUid: string, eventID: string, timestamp: number): Promise<GetGalleryUpdateTimestampResponse>;
167
167
  setHistoryIndex(firebaseUid: string, imageID: string, taskID: string): Promise<void>;
168
168
  }
169
169
  export interface ImageItem {
@@ -11,6 +11,21 @@ export interface PhotoData {
11
11
  isSelected: boolean;
12
12
  adjustments?: Partial<AdjustmentValues>;
13
13
  }
14
+ /**
15
+ * Hook for bulk photo editing with multi-image backend synchronization.
16
+ *
17
+ * **Multi-Image Backend Sync:**
18
+ * - Each selected image maintains its own history and backend state
19
+ * - History operations (undo/redo) trigger separate controller.setHistoryIndex() calls for each selected image
20
+ * - Preset application triggers separate controller.createEditorConfig() calls for each selected image
21
+ * - Adjustment changes trigger separate controller.createEditorConfig() calls for each selected image
22
+ *
23
+ * **Key Features:**
24
+ * - Individual history tracking per image with backend persistence
25
+ * - Bulk operations that respect per-image differences
26
+ * - Automatic backend synchronization for all state changes
27
+ * - Comprehensive logging of multi-image operations
28
+ */
14
29
  export declare function useHonchoEditorBulk(controller: Controller, eventID: string, firebaseUid: string): {
15
30
  imageData: PhotoData[];
16
31
  isLoading: boolean;
@@ -22,7 +37,6 @@ export declare function useHonchoEditorBulk(controller: Controller, eventID: str
22
37
  selectedBulkPreset: string;
23
38
  activePreset: Preset | null;
24
39
  handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
25
- handleOpenPresetModal: () => void;
26
40
  presetActions: import("../usePreset").PresetActions;
27
41
  handleToggleImageSelection: (imageId: string) => void;
28
42
  handleLoadMore: () => Promise<void>;
@@ -66,7 +66,23 @@ const adjustmentsMatch = (a, b) => {
66
66
  ];
67
67
  return keys.every(key => (a[key] || 0) === (b[key] || 0));
68
68
  };
69
+ /**
70
+ * Hook for bulk photo editing with multi-image backend synchronization.
71
+ *
72
+ * **Multi-Image Backend Sync:**
73
+ * - Each selected image maintains its own history and backend state
74
+ * - History operations (undo/redo) trigger separate controller.setHistoryIndex() calls for each selected image
75
+ * - Preset application triggers separate controller.createEditorConfig() calls for each selected image
76
+ * - Adjustment changes trigger separate controller.createEditorConfig() calls for each selected image
77
+ *
78
+ * **Key Features:**
79
+ * - Individual history tracking per image with backend persistence
80
+ * - Bulk operations that respect per-image differences
81
+ * - Automatic backend synchronization for all state changes
82
+ * - Comprehensive logging of multi-image operations
83
+ */
69
84
  export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
85
+ console.log('[useHonchoEditorBulk] 🏭 Initializing with controller for multi-image backend sync');
70
86
  const { currentBatch, selectedIds, actions: batchActions, historyInfo } = useAdjustmentHistoryBatch({
71
87
  controller,
72
88
  firebaseUid,
@@ -147,16 +163,30 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
147
163
  syncAdjustmentRef.current = batchActions.syncAdjustment;
148
164
  // Safe sync: Only sync new images to prevent infinite loops
149
165
  useEffect(() => {
166
+ console.log('[useHonchoEditorBulk] 🔄 Sync effect triggered:', {
167
+ imagesLength: images.length,
168
+ imageIds: images.map(img => img.id),
169
+ lastSyncedImageIds: Array.from(lastSyncedImageIds.current),
170
+ lastSyncedCount: lastSyncedImageIds.current.size
171
+ });
150
172
  if (images.length === 0)
151
173
  return;
152
174
  // Check if we have new images that haven't been synced
153
175
  const currentImageIds = new Set(images.map(img => img.id));
154
176
  const hasNewImages = images.some(img => !lastSyncedImageIds.current.has(img.id));
177
+ console.log('[useHonchoEditorBulk] 🔍 New images check:', {
178
+ currentImageIds: Array.from(currentImageIds),
179
+ hasNewImages,
180
+ newImages: images.filter(img => !lastSyncedImageIds.current.has(img.id)).map(img => img.id)
181
+ });
155
182
  if (hasNewImages) {
156
- console.log('[useHonchoEditorBulk] Syncing new images to batch:', images.length);
183
+ console.log('[useHonchoEditorBulk] ⚠️ SYNC TRIGGER: Syncing new images to batch (THIS RESETS HISTORY!):', images.length);
157
184
  syncAdjustmentRef.current(images.map(mapToImageAdjustmentConfig));
158
185
  lastSyncedImageIds.current = currentImageIds;
159
186
  }
187
+ else {
188
+ console.log('[useHonchoEditorBulk] ✅ No new images, skipping sync (history preserved)');
189
+ }
160
190
  }, [images]); // Only depend on images, not batchActions
161
191
  const handleBackCallbackBulk = useCallback(() => {
162
192
  const lastSelectedId = selectedIds.length > 0 ? selectedIds[selectedIds.length - 1] : "";
@@ -165,20 +195,50 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
165
195
  const handleSelectBulkPreset = useCallback((event) => {
166
196
  const presetId = event.target.value;
167
197
  setSelectedBulkPreset(presetId);
168
- if (presetId && selectedIds.length > 0) {
169
- // Find the preset
170
- const preset = presets.find(p => p.id === presetId);
171
- if (preset) {
172
- // Convert preset to adjustment state
173
- const presetAdjustments = presetToAdjustmentState(preset);
174
- // Apply preset directly to all selected images using the clean action
175
- batchActions.adjustSelectedWithPreset(presetAdjustments);
176
- }
198
+ console.log('[useHonchoEditorBulk] 🎨 Preset selection attempt:', {
199
+ presetId,
200
+ selectedImages: selectedIds,
201
+ imageCount: selectedIds.length,
202
+ hasController: !!controller,
203
+ hasFirebaseUid: !!firebaseUid
204
+ });
205
+ if (!presetId) {
206
+ console.log('[useHonchoEditorBulk] ❌ No preset ID provided, skipping backend call');
207
+ return;
177
208
  }
178
- }, [presets, selectedIds, batchActions]);
209
+ if (selectedIds.length === 0) {
210
+ console.log('[useHonchoEditorBulk] ❌ No images selected, skipping backend call');
211
+ return;
212
+ }
213
+ console.log('[useHonchoEditorBulk] 🎨 Applying preset to multiple images:', {
214
+ presetId,
215
+ selectedImages: selectedIds,
216
+ imageCount: selectedIds.length
217
+ });
218
+ // Find the preset
219
+ const preset = presets.find(p => p.id === presetId);
220
+ if (!preset) {
221
+ console.log('[useHonchoEditorBulk] ❌ Preset not found in presets list:', presetId);
222
+ return;
223
+ }
224
+ // Convert preset to adjustment state
225
+ const presetAdjustments = presetToAdjustmentState(preset);
226
+ console.log('[useHonchoEditorBulk] 📤 Sending preset adjustments to controller for each image:', presetAdjustments);
227
+ console.log('[useHonchoEditorBulk] 🔄 About to call batchActions.adjustSelectedWithPreset');
228
+ // Apply preset directly to all selected images using the clean action
229
+ // This will trigger backend calls for each selected image via adjustSelectedWithPreset
230
+ batchActions.adjustSelectedWithPreset(presetAdjustments);
231
+ console.log('[useHonchoEditorBulk] ✅ Called batchActions.adjustSelectedWithPreset, backend calls should be triggered');
232
+ }, [presets, selectedIds, batchActions, controller, firebaseUid]);
179
233
  // This factory creates functions that adjust a value for all selected images
180
234
  const createRelativeAdjuster = useCallback((key, amount) => () => {
181
- console.debug("createRelativeAdjuster called:", { key, amount, selectedIds });
235
+ console.debug(`[useHonchoEditorBulk] 🎛️ Bulk ${key} adjustment:`, {
236
+ key,
237
+ amount,
238
+ selectedImages: selectedIds,
239
+ imageCount: selectedIds.length
240
+ });
241
+ console.debug('[useHonchoEditorBulk] 📤 Sending bulk adjustment to controller for each selected image');
182
242
  console.debug("currentBatch.allImages before:", currentBatch.allImages);
183
243
  batchActions.adjustSelected({ [key]: amount });
184
244
  // Debug after a short delay to see if the state changed
@@ -290,7 +350,6 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
290
350
  activePreset,
291
351
  handleSelectBulkPreset,
292
352
  // Preset creation handlers
293
- handleOpenPresetModal: () => { }, // TODO: Implement preset modal for bulk editing
294
353
  presetActions, // Expose preset actions for create/rename/delete
295
354
  handleToggleImageSelection: batchActions.toggleSelection,
296
355
  handleLoadMore: actions.loadMore,
@@ -347,8 +406,22 @@ export function useHonchoEditorBulk(controller, eventID, firebaseUid) {
347
406
  handleBulkSharpnessIncrease,
348
407
  handleBulkSharpnessIncreaseMax,
349
408
  // History actions
350
- handleUndo: batchActions.undo,
351
- handleRedo: batchActions.redo,
409
+ handleUndo: () => {
410
+ console.log('[useHonchoEditorBulk] ⏪ Undo requested for multiple images:', {
411
+ selectedImages: selectedIds,
412
+ imageCount: selectedIds.length
413
+ });
414
+ console.log('[useHonchoEditorBulk] 📤 Sending undo (setHistoryIndex) to controller for each image');
415
+ return batchActions.undo();
416
+ },
417
+ handleRedo: () => {
418
+ console.log('[useHonchoEditorBulk] ⏩ Redo requested for multiple images:', {
419
+ selectedImages: selectedIds,
420
+ imageCount: selectedIds.length
421
+ });
422
+ console.log('[useHonchoEditorBulk] 📤 Sending redo (setHistoryIndex) to controller for each image');
423
+ return batchActions.redo();
424
+ },
352
425
  handleReset: batchActions.reset,
353
426
  historyInfo,
354
427
  };
@@ -33,7 +33,8 @@ export interface UseHonchoEditorSingleActions {
33
33
  reset: () => void;
34
34
  loadPresets: () => Promise<void>;
35
35
  applyPreset: (preset: Preset) => void;
36
- createPreset: (name: string) => Promise<Preset | null>;
36
+ createPreset: (name: string, adjustments: AdjustmentState) => Promise<Preset | null>;
37
+ renamePreset: (presetId: string, newName: string) => Promise<boolean>;
37
38
  deletePreset: (presetId: string) => Promise<void>;
38
39
  getEditorAdjustments: () => AdjustmentValues;
39
40
  }
@@ -87,9 +87,12 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
87
87
  // Then sync to backend
88
88
  await adjustmentHistory.config.syncToBackend();
89
89
  }, [adjustmentHistory.actions.pushState, adjustmentHistory.config.syncToBackend]);
90
- const createPreset = useCallback(async (name) => {
91
- return await presetHook.actions.create(name, adjustmentHistory.currentState);
92
- }, [presetHook.actions.create, adjustmentHistory.currentState]);
90
+ const createPreset = useCallback(async (name, adjustments) => {
91
+ return await presetHook.actions.create(name, adjustments);
92
+ }, [presetHook.actions.create]);
93
+ const renamePreset = useCallback(async (presetId, newName) => {
94
+ return await presetHook.actions.rename(presetId, newName);
95
+ }, [presetHook.actions.rename]);
93
96
  const deletePreset = useCallback(async (presetId) => {
94
97
  await presetHook.actions.delete(presetId);
95
98
  }, [presetHook.actions.delete]);
@@ -127,6 +130,7 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
127
130
  loadPresets,
128
131
  applyPreset,
129
132
  createPreset,
133
+ renamePreset,
130
134
  deletePreset,
131
135
  // Adjustment conversion for editor
132
136
  getEditorAdjustments
@@ -87,6 +87,8 @@ export interface BatchHistoryActions {
87
87
  setSelection: (configs: ImageAdjustmentConfig[]) => void;
88
88
  /** Sync/replace adjustment for images (clears their history) */
89
89
  syncAdjustment: (configs: ImageAdjustmentConfig[]) => void;
90
+ /** Check for gallery updates and sync history for updated images */
91
+ syncGalleryUpdates: () => Promise<void>;
90
92
  /** Add or remove image from selection */
91
93
  toggleSelection: (imageId: string) => void;
92
94
  /** Select all images */