@yogiswara/honcho-editor-ui 1.0.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 (91) hide show
  1. package/dist/components/editor/HAccordionAspectRatio.d.ts +14 -0
  2. package/dist/components/editor/HAccordionAspectRatio.js +102 -0
  3. package/dist/components/editor/HAccordionColor.d.ts +12 -0
  4. package/dist/components/editor/HAccordionColor.js +234 -0
  5. package/dist/components/editor/HAccordionColorAdjustment.d.ts +31 -0
  6. package/dist/components/editor/HAccordionColorAdjustment.js +37 -0
  7. package/dist/components/editor/HAccordionDetails.d.ts +8 -0
  8. package/dist/components/editor/HAccordionDetails.js +133 -0
  9. package/dist/components/editor/HAccordionLight.d.ts +16 -0
  10. package/dist/components/editor/HAccordionLight.js +342 -0
  11. package/dist/components/editor/HAccordionPreset.d.ts +23 -0
  12. package/dist/components/editor/HAccordionPreset.js +59 -0
  13. package/dist/components/editor/HAlertBox.d.ts +7 -0
  14. package/dist/components/editor/HAlertBox.js +46 -0
  15. package/dist/components/editor/HAspectRatioMobile.d.ts +0 -0
  16. package/dist/components/editor/HAspectRatioMobile.js +1 -0
  17. package/dist/components/editor/HBulkAccordionColorAdjustment.d.ts +55 -0
  18. package/dist/components/editor/HBulkAccordionColorAdjustment.js +31 -0
  19. package/dist/components/editor/HBulkAccordionColorAdjustmentColors.d.ts +20 -0
  20. package/dist/components/editor/HBulkAccordionColorAdjustmentColors.js +121 -0
  21. package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.d.ts +12 -0
  22. package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.js +65 -0
  23. package/dist/components/editor/HBulkAccordionColorAdjustmentLight.d.ts +28 -0
  24. package/dist/components/editor/HBulkAccordionColorAdjustmentLight.js +177 -0
  25. package/dist/components/editor/HBulkColorAdjustmentMobile.d.ts +53 -0
  26. package/dist/components/editor/HBulkColorAdjustmentMobile.js +16 -0
  27. package/dist/components/editor/HBulkColorMobile.d.ts +20 -0
  28. package/dist/components/editor/HBulkColorMobile.js +121 -0
  29. package/dist/components/editor/HBulkDetailsMobile.d.ts +12 -0
  30. package/dist/components/editor/HBulkDetailsMobile.js +65 -0
  31. package/dist/components/editor/HBulkLightMobile.d.ts +28 -0
  32. package/dist/components/editor/HBulkLightMobile.js +192 -0
  33. package/dist/components/editor/HBulkPreset.d.ts +24 -0
  34. package/dist/components/editor/HBulkPreset.js +33 -0
  35. package/dist/components/editor/HBulkPresetMobile.d.ts +15 -0
  36. package/dist/components/editor/HBulkPresetMobile.js +26 -0
  37. package/dist/components/editor/HDialogBox.d.ts +18 -0
  38. package/dist/components/editor/HDialogBox.js +51 -0
  39. package/dist/components/editor/HDialogCopy.d.ts +40 -0
  40. package/dist/components/editor/HDialogCopy.js +80 -0
  41. package/dist/components/editor/HFooter.d.ts +12 -0
  42. package/dist/components/editor/HFooter.js +24 -0
  43. package/dist/components/editor/HHeaderEditor.d.ts +17 -0
  44. package/dist/components/editor/HHeaderEditor.js +27 -0
  45. package/dist/components/editor/HImageEditorBulkDekstop.d.ts +15 -0
  46. package/dist/components/editor/HImageEditorBulkDekstop.js +26 -0
  47. package/dist/components/editor/HImageEditorBulkMobile.d.ts +72 -0
  48. package/dist/components/editor/HImageEditorBulkMobile.js +81 -0
  49. package/dist/components/editor/HImageEditorDekstop.d.ts +15 -0
  50. package/dist/components/editor/HImageEditorDekstop.js +29 -0
  51. package/dist/components/editor/HImageEditorMobile.d.ts +47 -0
  52. package/dist/components/editor/HImageEditorMobile.js +91 -0
  53. package/dist/components/editor/HImageEditorMobileLayout.d.ts +14 -0
  54. package/dist/components/editor/HImageEditorMobileLayout.js +57 -0
  55. package/dist/components/editor/HImageEditorPage.d.ts +1 -0
  56. package/dist/components/editor/HImageEditorPage.js +187 -0
  57. package/dist/components/editor/HModalEditorDekstop.d.ts +13 -0
  58. package/dist/components/editor/HModalEditorDekstop.js +22 -0
  59. package/dist/components/editor/HModalMobile.d.ts +12 -0
  60. package/dist/components/editor/HModalMobile.js +7 -0
  61. package/dist/components/editor/HPresetOptionMenu.d.ts +11 -0
  62. package/dist/components/editor/HPresetOptionMenu.js +20 -0
  63. package/dist/components/editor/HSliderColorMobile.d.ts +12 -0
  64. package/dist/components/editor/HSliderColorMobile.js +222 -0
  65. package/dist/components/editor/HSliderDetailsMobile.d.ts +8 -0
  66. package/dist/components/editor/HSliderDetailsMobile.js +130 -0
  67. package/dist/components/editor/HSliderLightMobile.d.ts +16 -0
  68. package/dist/components/editor/HSliderLightMobile.js +342 -0
  69. package/dist/components/editor/HTabAspectRatioMobile.d.ts +0 -0
  70. package/dist/components/editor/HTabAspectRatioMobile.js +1 -0
  71. package/dist/components/editor/HTabColorAdjustmentMobile.d.ts +29 -0
  72. package/dist/components/editor/HTabColorAdjustmentMobile.js +16 -0
  73. package/dist/components/editor/HTabPresetMobile.d.ts +14 -0
  74. package/dist/components/editor/HTabPresetMobile.js +10 -0
  75. package/dist/components/editor/HTextField.d.ts +14 -0
  76. package/dist/components/editor/HTextField.js +51 -0
  77. package/dist/components/editor/HWatermarkView.d.ts +6 -0
  78. package/dist/components/editor/HWatermarkView.js +16 -0
  79. package/dist/hooks/editor/useHonchoEditor.d.ts +272 -0
  80. package/dist/hooks/editor/useHonchoEditor.js +1203 -0
  81. package/dist/index.d.ts +23 -0
  82. package/dist/index.js +23 -0
  83. package/dist/lib/editor/honcho-editor.d.ts +324 -0
  84. package/dist/lib/editor/honcho-editor.js +825 -0
  85. package/dist/themes/colors.d.ts +12 -0
  86. package/dist/themes/colors.js +12 -0
  87. package/dist/themes/honchoTheme.d.ts +25 -0
  88. package/dist/themes/honchoTheme.js +94 -0
  89. package/dist/utils/isMobile.d.ts +1 -0
  90. package/dist/utils/isMobile.js +5 -0
  91. package/package.json +41 -0
@@ -0,0 +1,1203 @@
1
+ 'use client';
2
+ import { useState, useRef, useCallback, useEffect } from 'react';
3
+ import { HonchoEditor } from '@/lib/editor/honcho-editor';
4
+ const initialAdjustments = {
5
+ tempScore: 0, tintScore: 0, vibranceScore: 0, exposureScore: 0, highlightsScore: 0, shadowsScore: 0,
6
+ whitesScore: 0, blacksScore: 0, saturationScore: 0, contrastScore: 0, clarityScore: 0, sharpnessScore: 0,
7
+ };
8
+ const clamp = (value) => Math.max(-100, Math.min(100, value));
9
+ export function useHonchoEditor(controller) {
10
+ // MARK: - Core Editor State & Refs
11
+ const editorRef = useRef(null);
12
+ const canvasRef = useRef(null);
13
+ const canvasContainerRef = useRef(null);
14
+ const fileInputRef = useRef(null);
15
+ const [editorStatus, setEditorStatus] = useState("Initializing...");
16
+ const [isEditorReady, setIsEditorReady] = useState(false);
17
+ const [isImageLoaded, setIsImageLoaded] = useState(false);
18
+ const [zoomLevel, setZoomLevel] = useState(1);
19
+ // MARK: - Adjustment & History State
20
+ // const [adjustments, setAdjustments] = useState<AdjustmentState>(initialAdjustments);
21
+ const [history, setHistory] = useState([initialAdjustments]);
22
+ const [historyIndex, setHistoryIndex] = useState(0);
23
+ const [isViewingOriginal, setIsViewingOriginal] = useState(false);
24
+ const [copiedAdjustments, setCopiedAdjustments] = useState(null);
25
+ const [copyColorChecks, setCopyColorChecks] = useState({ temperature: true, tint: true, vibrance: true, saturation: true });
26
+ const [copyLightChecks, setCopyLightChecks] = useState({ exposure: true, contrast: true, highlights: true, shadows: true, whites: true, blacks: true });
27
+ const [copyDetailsChecks, setCopyDetailsChecks] = useState({ clarity: true, sharpness: true });
28
+ const [copyDialogExpanded, setCopyDialogExpanded] = useState({ color: true, light: true, details: true });
29
+ const [adjustmentsMap, setAdjustmentsMap] = useState(new Map());
30
+ // Individual Adjustment State
31
+ const [tempScore, setTempScore] = useState(0);
32
+ const [tintScore, setTintScore] = useState(0);
33
+ const [vibranceScore, setVibranceScore] = useState(0);
34
+ const [saturationScore, setSaturationScore] = useState(0);
35
+ const [exposureScore, setExposureScore] = useState(0);
36
+ const [highlightsScore, setHighlightsScore] = useState(0);
37
+ const [shadowsScore, setShadowsScore] = useState(0);
38
+ const [whitesScore, setWhitesScore] = useState(0);
39
+ const [blacksScore, setBlacksScore] = useState(0);
40
+ const [contrastScore, setContrastScore] = useState(0);
41
+ const [clarityScore, setClarityScore] = useState(0);
42
+ const [sharpnessScore, setSharpnessScore] = useState(0);
43
+ // MARK: - UI & App State (Moved from page.tsx)
44
+ // General UI State
45
+ const [isOnline, setIsOnline] = useState(true);
46
+ const [isConnectionSlow, setIsConnectionSlow] = useState(false);
47
+ const [showCopyAlert, setShowCopyAlert] = useState(false);
48
+ const [isCopyDialogOpen, setCopyDialogOpen] = useState(false);
49
+ const [isPublished, setIsPublished] = useState(false);
50
+ const [activePanel, setActivePanel] = useState('colorAdjustment');
51
+ const [activeSubPanel, setActiveSubPanel] = useState('');
52
+ const [headerMenuAnchorEl, setHeaderMenuAnchorEl] = useState(null);
53
+ const [anchorMenuZoom, setAnchorMenuZoom] = useState(null);
54
+ // Panel Expansion State
55
+ const [colorAdjustmentExpandedPanels, setColorAdjustmentExpandedPanels] = useState(['whiteBalance']);
56
+ const [presetExpandedPanels, setPresetExpandedPanels] = useState(['preset']);
57
+ // Watermark State
58
+ const [isCreatingWatermark, setIsCreatingWatermark] = useState(false);
59
+ // Preset State
60
+ const [isPresetModalOpen, setPresetModalOpen] = useState(false);
61
+ const [isPresetModalOpenMobile, setPresetModalOpenMobile] = useState(false);
62
+ const [presets, setPresets] = useState([]);
63
+ const [presetName, setPresetName] = useState("Type Here");
64
+ const [isPresetCreated, setIsPresetCreated] = useState(false);
65
+ const [selectedMobilePreset, setSelectedMobilePreset] = useState('preset1');
66
+ const [selectedDesktopPreset, setSelectedDesktopPreset] = useState('preset1');
67
+ const [selectedBulkPreset, setSelectedBulkPreset] = useState('preset1');
68
+ const [presetMenuAnchorEl, setPresetMenuAnchorEl] = useState(null);
69
+ const [activePresetMenuId, setActivePresetMenuId] = useState(null);
70
+ const [isRenameModalOpen, setRenameModalOpen] = useState(false);
71
+ const [presetToRename, setPresetToRename] = useState(null);
72
+ const [newPresetName, setNewPresetName] = useState("");
73
+ // Aspect Ratio State
74
+ // Note: not used yet
75
+ const [currentAspectRatio, setCurrentAspectRatio] = useState('potrait');
76
+ const [currentSquareRatio, setCurrentSquareRatio] = useState('original');
77
+ const [currentWideRatio, setCurrentWideRatio] = useState('1:1');
78
+ const [angelScore, setAngleScore] = useState(0);
79
+ const [widthSizePX, setWidthSizePX] = useState(0);
80
+ const [heightSizePX, setHeightSizePX] = useState(0);
81
+ // Bulk Editing State
82
+ const [isBulkEditing, setIsBulkEditing] = useState(false);
83
+ const [selectedImages, setSelectedImages] = useState('Select');
84
+ const [imageList, setImageList] = useState([]);
85
+ const [selectedImageIds, setSelectedImageIds] = useState(new Set());
86
+ // MARK: Framse- (Later use)
87
+ const [isFrameApplied, setIsFrameApplied] = useState(false);
88
+ // State for Copying specific adjustments
89
+ const [colorAdjustments, setColorAdjustments] = useState(true);
90
+ const [lightAdjustments, setLightAdjustments] = useState(true);
91
+ const [detailsAdjustments, setDetailsAdjustments] = useState(true);
92
+ // for connection native
93
+ const [displayedToken, setDisplayedToken] = useState(null);
94
+ // MARK: dragable
95
+ const PEEK_HEIGHT = 20;
96
+ const COLLAPSED_HEIGHT = 165;
97
+ const PANEL_CHROME_HEIGHT = 10;
98
+ // Mobile Draggable Panel State
99
+ const [panelHeight, setPanelHeight] = useState(COLLAPSED_HEIGHT);
100
+ const [contentHeight, setContentHeight] = useState(0);
101
+ const [isDragging, setIsDragging] = useState(false);
102
+ const dragStartPos = useRef(0);
103
+ const initialHeight = useRef(0);
104
+ const panelRef = useRef(null);
105
+ const contentRef = useRef(null);
106
+ // Mobile Panel Drag Handlers
107
+ const handleContentHeightChange = useCallback((height) => {
108
+ if (height > 0 && height !== contentHeight)
109
+ setContentHeight(height);
110
+ }, [contentHeight]);
111
+ const handleDragStart = useCallback((e) => {
112
+ setIsDragging(true);
113
+ const startY = 'touches' in e ? e.touches[0].clientY : e.clientY;
114
+ dragStartPos.current = startY;
115
+ initialHeight.current = panelHeight;
116
+ if (panelRef.current)
117
+ panelRef.current.style.transition = 'none';
118
+ }, [panelHeight]);
119
+ const handleDragMove = useCallback((e) => {
120
+ if (!isDragging)
121
+ return;
122
+ const currentY = 'touches' in e ? e.touches[0].clientY : e.clientY;
123
+ const deltaY = dragStartPos.current - currentY;
124
+ const newHeight = initialHeight.current + deltaY;
125
+ const dynamicPanelFullHeight = contentHeight + PANEL_CHROME_HEIGHT;
126
+ const clampedHeight = Math.max(PEEK_HEIGHT, Math.min(newHeight, dynamicPanelFullHeight));
127
+ setPanelHeight(clampedHeight);
128
+ }, [isDragging, contentHeight]);
129
+ const handleDragEnd = useCallback(() => {
130
+ if (!isDragging)
131
+ return;
132
+ setIsDragging(false);
133
+ dragStartPos.current = 0;
134
+ if (panelRef.current)
135
+ panelRef.current.style.transition = 'height 0.3s ease-in-out';
136
+ const dynamicPanelFullHeight = contentHeight + PANEL_CHROME_HEIGHT;
137
+ const snapPointLow = (PEEK_HEIGHT + COLLAPSED_HEIGHT) / 2;
138
+ const snapPointHigh = (COLLAPSED_HEIGHT + dynamicPanelFullHeight) / 2;
139
+ if (panelHeight < snapPointLow) {
140
+ setPanelHeight(PEEK_HEIGHT);
141
+ }
142
+ else if (panelHeight >= snapPointLow && panelHeight < snapPointHigh) {
143
+ setPanelHeight(COLLAPSED_HEIGHT);
144
+ }
145
+ else {
146
+ setPanelHeight(dynamicPanelFullHeight);
147
+ }
148
+ }, [isDragging, panelHeight, contentHeight]);
149
+ // Keyboard Shortcut Handler
150
+ const handleKeyDown = useCallback((event) => {
151
+ const target = event.target;
152
+ if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
153
+ return;
154
+ if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
155
+ event.preventDefault();
156
+ handleOpenCopyDialog(); // Assumes handleOpenCopyDialog is defined in the hook
157
+ }
158
+ }, [ /* handleOpenCopyDialog dependency */]);
159
+ // Effect for measuring mobile panel content
160
+ useEffect(() => {
161
+ const timeoutId = setTimeout(() => {
162
+ if (contentRef.current) {
163
+ const height = contentRef.current.scrollHeight;
164
+ setContentHeight(height);
165
+ }
166
+ }, 50);
167
+ return () => clearTimeout(timeoutId);
168
+ }, [activeSubPanel, isBulkEditing]);
169
+ // Effect for keyboard shortcuts
170
+ useEffect(() => {
171
+ window.addEventListener('keydown', handleKeyDown);
172
+ return () => {
173
+ window.removeEventListener('keydown', handleKeyDown);
174
+ };
175
+ }, [handleKeyDown]);
176
+ // Effect for drag listeners
177
+ useEffect(() => {
178
+ if (isDragging) {
179
+ window.addEventListener('mousemove', handleDragMove);
180
+ window.addEventListener('mouseup', handleDragEnd);
181
+ window.addEventListener('touchmove', handleDragMove);
182
+ window.addEventListener('touchend', handleDragEnd);
183
+ }
184
+ return () => {
185
+ window.removeEventListener('mousemove', handleDragMove);
186
+ window.removeEventListener('mouseup', handleDragEnd);
187
+ window.removeEventListener('touchmove', handleDragMove);
188
+ window.removeEventListener('touchend', handleDragEnd);
189
+ };
190
+ }, [isDragging, handleDragMove, handleDragEnd]);
191
+ useEffect(() => {
192
+ // Cast navigator to our custom type to access the connection property safely
193
+ const navigatorWithConnection = navigator;
194
+ if (!navigatorWithConnection.connection) {
195
+ return;
196
+ }
197
+ const navigatorConnection = navigatorWithConnection.connection;
198
+ const updateConnectionStatus = () => {
199
+ const slowConnectionTypes = ['slow-2g', '2g', '3g'];
200
+ const isSlow = navigatorConnection.saveData ||
201
+ slowConnectionTypes.includes(navigatorConnection.effectiveType);
202
+ setIsConnectionSlow(isSlow);
203
+ };
204
+ // Check status immediately
205
+ updateConnectionStatus();
206
+ // Add event listener for changes
207
+ navigatorConnection.addEventListener('change', updateConnectionStatus);
208
+ // Cleanup on unmount
209
+ return () => {
210
+ navigatorConnection.removeEventListener('change', updateConnectionStatus);
211
+ };
212
+ }, []);
213
+ // MARK: - Core Editor Logic
214
+ const updateCanvas = useCallback(() => {
215
+ if (editorRef.current?.getInitialized() && canvasRef.current) {
216
+ editorRef.current.processImage();
217
+ editorRef.current.renderToCanvas(canvasRef.current);
218
+ }
219
+ }, []);
220
+ const loadImage = useCallback(async (file) => {
221
+ if (!editorRef.current) {
222
+ setEditorStatus("Editor not ready.");
223
+ return;
224
+ }
225
+ setEditorStatus("Loading image...");
226
+ try {
227
+ await editorRef.current.loadImageFromFile(file);
228
+ setIsImageLoaded(true);
229
+ }
230
+ catch (e) {
231
+ console.error("Error loading image:", e);
232
+ setEditorStatus("Error: Could not load the image.");
233
+ setIsImageLoaded(false);
234
+ }
235
+ }, []);
236
+ const applyUiStateToSelectedImages = useCallback((uiState) => {
237
+ setAdjustmentsMap(prevMap => {
238
+ const newMap = new Map(prevMap);
239
+ selectedImageIds.forEach(id => {
240
+ newMap.set(id, uiState);
241
+ });
242
+ return newMap;
243
+ });
244
+ }, [selectedImageIds]);
245
+ const loadImageFromUrl = useCallback(async (url) => {
246
+ try {
247
+ setEditorStatus("Downloading image...");
248
+ const response = await fetch(url);
249
+ if (!response.ok)
250
+ throw new Error(`Failed to fetch image from URL: ${url}`);
251
+ const blob = await response.blob();
252
+ const filename = url.substring(url.lastIndexOf('/') + 1) || 'image.jpg';
253
+ const file = new File([blob], filename, { type: blob.type });
254
+ await loadImage(file); // Pass the final File object to the core loader
255
+ }
256
+ catch (error) {
257
+ console.error(error);
258
+ setEditorStatus("Error: Could not load image from URL.");
259
+ }
260
+ }, [loadImage]);
261
+ const loadImageFromId = useCallback(async (imageId) => {
262
+ if (!controller)
263
+ return;
264
+ setEditorStatus("Fetching image URL...");
265
+ try {
266
+ const imageUrl = await controller.onGetImage(imageId);
267
+ if (imageUrl) {
268
+ await loadImageFromUrl(imageUrl);
269
+ }
270
+ else {
271
+ throw new Error("Controller did not return an image URL.");
272
+ }
273
+ }
274
+ catch (error) {
275
+ console.error("Failed to fetch or load image via controller:", error);
276
+ setEditorStatus("Error: Could not fetch the image.");
277
+ }
278
+ }, [controller, loadImageFromUrl]);
279
+ useEffect(() => {
280
+ // Define the function that the native app will call to load an image
281
+ const loadInitialImageFromNative = (imageId) => {
282
+ if (typeof imageId === 'string' && imageId) {
283
+ console.log(`[WebView Bridge] Received command to load imageId: ${imageId}`);
284
+ // Use the loadImageFromId function directly from the hook's scope
285
+ loadImageFromId(imageId);
286
+ }
287
+ else {
288
+ console.error(`[WebView Bridge] Invalid imageId received from native:`, imageId);
289
+ }
290
+ };
291
+ // Expose both functions on the window object for native code to access
292
+ window.loadInitialImageFromNative = loadInitialImageFromNative;
293
+ // Cleanup function to remove the global handlers when the component unmounts
294
+ return () => {
295
+ delete window.loadInitialImageFromNative;
296
+ delete window.setAuthToken;
297
+ };
298
+ }, [loadImageFromId]);
299
+ const handleFileChange = (event) => {
300
+ const files = event.target?.files;
301
+ if (!files || files.length === 0)
302
+ return;
303
+ applyAdjustmentState(initialAdjustments);
304
+ setHistory([initialAdjustments]);
305
+ setHistoryIndex(0);
306
+ if (files.length === 1) {
307
+ setIsBulkEditing(false);
308
+ setImageList([]);
309
+ setSelectedImageIds(new Set());
310
+ setAdjustmentsMap(new Map());
311
+ loadImage(files[0]);
312
+ }
313
+ else {
314
+ setIsBulkEditing(true);
315
+ const newImageList = Array.from(files).map((file, index) => ({
316
+ id: `${file.name}-${Date.now()}-${index}`,
317
+ name: file.name,
318
+ file: file,
319
+ url: URL.createObjectURL(file),
320
+ }));
321
+ const newAdjustmentsMap = new Map();
322
+ newImageList.forEach(image => {
323
+ newAdjustmentsMap.set(image.id, { ...initialAdjustments });
324
+ });
325
+ setAdjustmentsMap(newAdjustmentsMap);
326
+ setImageList(newImageList);
327
+ setIsImageLoaded(true);
328
+ setSelectedImageIds(new Set(newImageList.map(img => img.id)));
329
+ }
330
+ };
331
+ const applyAdjustmentState = useCallback((state) => {
332
+ // Always update the UI controls
333
+ setTempScore(state.tempScore);
334
+ setTintScore(state.tintScore);
335
+ setVibranceScore(state.vibranceScore);
336
+ setExposureScore(state.exposureScore);
337
+ setHighlightsScore(state.highlightsScore);
338
+ setShadowsScore(state.shadowsScore);
339
+ setWhitesScore(state.whitesScore);
340
+ setBlacksScore(state.blacksScore);
341
+ setSaturationScore(state.saturationScore);
342
+ setContrastScore(state.contrastScore);
343
+ setClarityScore(state.clarityScore);
344
+ setSharpnessScore(state.sharpnessScore);
345
+ // If in bulk mode, apply this state to all selected images
346
+ if (isBulkEditing) {
347
+ applyUiStateToSelectedImages(state);
348
+ }
349
+ }, [isBulkEditing, applyUiStateToSelectedImages]);
350
+ const handleRevert = useCallback(() => {
351
+ // This will reset the UI controls and, if in bulk mode, the selected images
352
+ applyAdjustmentState(initialAdjustments);
353
+ // For single image mode, also reset the underlying canvas engine
354
+ if (!isBulkEditing && editorRef.current) {
355
+ editorRef.current.resetAdjustments();
356
+ }
357
+ }, [applyAdjustmentState, isBulkEditing]);
358
+ const handleUndo = useCallback(() => {
359
+ if (historyIndex > 0) {
360
+ const prevIndex = historyIndex - 1;
361
+ applyAdjustmentState(history[prevIndex]);
362
+ setHistoryIndex(prevIndex);
363
+ }
364
+ }, [history, historyIndex, applyAdjustmentState]);
365
+ const handleRedo = useCallback(() => {
366
+ if (historyIndex < history.length - 1) {
367
+ const nextIndex = historyIndex + 1;
368
+ applyAdjustmentState(history[nextIndex]);
369
+ setHistoryIndex(nextIndex);
370
+ }
371
+ }, [history, historyIndex, applyAdjustmentState]);
372
+ // MARK: - Bulk Editor Functions For Desktop and Mobile
373
+ // const adjustTempBulk = useCallback((uiAmount: number) => {
374
+ // setTempScore(prevScore => {
375
+ // const newScore = clamp(prevScore + uiAmount);
376
+ // console.log("Adjusting temperature. New score:", newScore);
377
+ // return newScore;
378
+ // });
379
+ // }, []);
380
+ // const adjustTintBulk = useCallback((uiAmount: number) => {
381
+ // setTintScore(prevScore => {
382
+ // const newScore = clamp(prevScore + uiAmount);
383
+ // console.log("Adjusting tint. New score:", newScore);
384
+ // return newScore;
385
+ // });
386
+ // }, []);
387
+ // const adjustVibranceBulk = useCallback((uiAmount: number) => {
388
+ // setVibranceScore(prevScore => {
389
+ // const newScore = clamp(prevScore + uiAmount);
390
+ // console.log("Adjusting vibrance. New score:", newScore);
391
+ // return newScore;
392
+ // });
393
+ // }, []);
394
+ // const adjustSaturationBulk = useCallback((uiAmount: number) => {
395
+ // setSaturationScore(prevScore => {
396
+ // const newScore = clamp(prevScore + uiAmount);
397
+ // console.log("Adjusting saturation. New score:", newScore);
398
+ // return newScore;
399
+ // });
400
+ // }, []);
401
+ // const adjustExposureBulk = useCallback((uiAmount: number) => {
402
+ // setExposureScore(prevScore => {
403
+ // const newScore = clamp(prevScore + uiAmount);
404
+ // console.log("Adjusting exposure. New score:", newScore);
405
+ // return newScore;
406
+ // });
407
+ // }, []);
408
+ // const adjustContrastBulk = useCallback((uiAmount: number) => {
409
+ // setContrastScore(prevScore => {
410
+ // const newScore = clamp(prevScore + uiAmount);
411
+ // console.log("Adjusting contrast. New score:", newScore);
412
+ // return newScore;
413
+ // });
414
+ // }, []);
415
+ // const adjustHighlightsBulk = useCallback((uiAmount: number) => {
416
+ // setHighlightsScore(prevScore => {
417
+ // const newScore = clamp(prevScore + uiAmount);
418
+ // console.log("Adjusting highlights. New score:", newScore);
419
+ // return newScore;
420
+ // });
421
+ // }, []);
422
+ // const adjustShadowsBulk = useCallback((uiAmount: number) => {
423
+ // setShadowsScore(prevScore => {
424
+ // const newScore = clamp(prevScore + uiAmount);
425
+ // console.log("Adjusting shadows. New score:", newScore);
426
+ // return newScore;
427
+ // });
428
+ // }, []);
429
+ // const adjustWhitesBulk = useCallback((uiAmount: number) => {
430
+ // setWhitesScore(prevScore => {
431
+ // const newScore = clamp(prevScore + uiAmount);
432
+ // console.log("Adjusting whites. New score:", newScore);
433
+ // return newScore;
434
+ // });
435
+ // }, []);
436
+ // const adjustBlacksBulk = useCallback((uiAmount: number) => {
437
+ // setBlacksScore(prevScore => {
438
+ // const newScore = clamp(prevScore + uiAmount);
439
+ // console.log("Adjusting blacks. New score:", newScore);
440
+ // return newScore;
441
+ // });
442
+ // }, []);
443
+ // const adjustClarityBulk = useCallback((uiAmount: number) => {
444
+ // setClarityScore(prevScore => {
445
+ // const newScore = clamp(prevScore + uiAmount);
446
+ // console.log("Adjusting clarity. New score:", newScore);
447
+ // return newScore;
448
+ // });
449
+ // }, []);
450
+ // const adjustSharpnessBulk = useCallback((uiAmount: number) => {
451
+ // setSharpnessScore(prevScore => {
452
+ // const newScore = clamp(prevScore + uiAmount);
453
+ // console.log("Adjusting sharpness. New score:", newScore);
454
+ // return newScore;
455
+ // });
456
+ // }, []);
457
+ const handleToggleImageSelection = useCallback((imageId) => {
458
+ const newSelectedIds = new Set(selectedImageIds);
459
+ const isCurrentlySelected = newSelectedIds.has(imageId);
460
+ if (isCurrentlySelected) {
461
+ if (newSelectedIds.size > 1) {
462
+ newSelectedIds.delete(imageId);
463
+ }
464
+ }
465
+ else {
466
+ newSelectedIds.add(imageId);
467
+ // Apply the current UI's adjustments to the newly selected image.
468
+ setAdjustmentsMap(prevMap => {
469
+ const newMap = new Map(prevMap);
470
+ const currentUiState = {
471
+ tempScore, tintScore, vibranceScore, saturationScore,
472
+ exposureScore, highlightsScore, shadowsScore, whitesScore,
473
+ blacksScore, contrastScore, clarityScore, sharpnessScore
474
+ };
475
+ newMap.set(imageId, currentUiState);
476
+ return newMap;
477
+ });
478
+ }
479
+ setSelectedImageIds(newSelectedIds);
480
+ }, [selectedImageIds, tempScore, tintScore, vibranceScore, saturationScore, exposureScore, highlightsScore, shadowsScore, whitesScore, blacksScore, contrastScore, clarityScore, sharpnessScore]);
481
+ const createAbsoluteSetter = (key, setter) => (value) => {
482
+ setter(value); // Update UI slider
483
+ if (isBulkEditing) {
484
+ setAdjustmentsMap(prevMap => {
485
+ const newMap = new Map(prevMap);
486
+ selectedImageIds.forEach(id => {
487
+ const currentState = newMap.get(id) || initialAdjustments;
488
+ newMap.set(id, { ...currentState, [key]: value });
489
+ });
490
+ return newMap;
491
+ });
492
+ }
493
+ };
494
+ const createRelativeAdjuster = (key, uiSetter, amount) => () => {
495
+ uiSetter(prev => clamp(prev + amount));
496
+ if (isBulkEditing) {
497
+ setAdjustmentsMap(prevMap => {
498
+ const newMap = new Map(prevMap);
499
+ selectedImageIds.forEach(id => {
500
+ const currentState = newMap.get(id) || initialAdjustments;
501
+ const currentValue = currentState[key];
502
+ const newValue = clamp(currentValue + amount);
503
+ newMap.set(id, { ...currentState, [key]: newValue });
504
+ });
505
+ console.log("this is UI Setter: ", uiSetter);
506
+ return newMap;
507
+ });
508
+ }
509
+ };
510
+ const setTempScoreAbs = createAbsoluteSetter('tempScore', setTempScore);
511
+ const setTintScoreAbs = createAbsoluteSetter('tintScore', setTintScore);
512
+ const setVibranceScoreAbs = createAbsoluteSetter('vibranceScore', setVibranceScore);
513
+ const setSaturationScoreAbs = createAbsoluteSetter('saturationScore', setSaturationScore);
514
+ const setExposureScoreAbs = createAbsoluteSetter('exposureScore', setExposureScore);
515
+ const setHighlightsScoreAbs = createAbsoluteSetter('highlightsScore', setHighlightsScore);
516
+ const setShadowsScoreAbs = createAbsoluteSetter('shadowsScore', setShadowsScore);
517
+ const setWhitesScoreAbs = createAbsoluteSetter('whitesScore', setWhitesScore);
518
+ const setBlacksScoreAbs = createAbsoluteSetter('blacksScore', setBlacksScore);
519
+ const setContrastScoreAbs = createAbsoluteSetter('contrastScore', setContrastScore);
520
+ const setClarityScoreAbs = createAbsoluteSetter('clarityScore', setClarityScore);
521
+ const setSharpnessScoreAbs = createAbsoluteSetter('sharpnessScore', setSharpnessScore);
522
+ // MARK: - Bulk Editor Handlers
523
+ const handleBulkTempDecreaseMax = createRelativeAdjuster('tempScore', setTempScore, -20);
524
+ const handleBulkTempDecrease = createRelativeAdjuster('tempScore', setTempScore, -5);
525
+ const handleBulkTempIncrease = createRelativeAdjuster('tempScore', setTempScore, 5);
526
+ const handleBulkTempIncreaseMax = createRelativeAdjuster('tempScore', setTempScore, 20);
527
+ const handleBulkTintDecreaseMax = createRelativeAdjuster('tintScore', setTintScore, -20);
528
+ const handleBulkTintDecrease = createRelativeAdjuster('tintScore', setTintScore, -5);
529
+ const handleBulkTintIncrease = createRelativeAdjuster('tintScore', setTintScore, 5);
530
+ const handleBulkTintIncreaseMax = createRelativeAdjuster('tintScore', setTintScore, 20);
531
+ const handleBulkVibranceDecreaseMax = createRelativeAdjuster('vibranceScore', setVibranceScore, -20);
532
+ const handleBulkVibranceDecrease = createRelativeAdjuster('vibranceScore', setVibranceScore, -5);
533
+ const handleBulkVibranceIncrease = createRelativeAdjuster('vibranceScore', setVibranceScore, 5);
534
+ const handleBulkVibranceIncreaseMax = createRelativeAdjuster('vibranceScore', setVibranceScore, 20);
535
+ const handleBulkSaturationDecreaseMax = createRelativeAdjuster('saturationScore', setSaturationScore, -20);
536
+ const handleBulkSaturationDecrease = createRelativeAdjuster('saturationScore', setSaturationScore, -5);
537
+ const handleBulkSaturationIncrease = createRelativeAdjuster('saturationScore', setSaturationScore, 5);
538
+ const handleBulkSaturationIncreaseMax = createRelativeAdjuster('saturationScore', setSaturationScore, 20);
539
+ const handleBulkExposureDecreaseMax = createRelativeAdjuster('exposureScore', setExposureScore, -20);
540
+ const handleBulkExposureDecrease = createRelativeAdjuster('exposureScore', setExposureScore, -5);
541
+ const handleBulkExposureIncrease = createRelativeAdjuster('exposureScore', setExposureScore, 5);
542
+ const handleBulkExposureIncreaseMax = createRelativeAdjuster('exposureScore', setExposureScore, 20);
543
+ const handleBulkContrastDecreaseMax = createRelativeAdjuster('contrastScore', setContrastScore, -20);
544
+ const handleBulkContrastDecrease = createRelativeAdjuster('contrastScore', setContrastScore, -5);
545
+ const handleBulkContrastIncrease = createRelativeAdjuster('contrastScore', setContrastScore, 5);
546
+ const handleBulkContrastIncreaseMax = createRelativeAdjuster('contrastScore', setContrastScore, 20);
547
+ const handleBulkHighlightsDecreaseMax = createRelativeAdjuster('highlightsScore', setHighlightsScore, -20);
548
+ const handleBulkHighlightsDecrease = createRelativeAdjuster('highlightsScore', setHighlightsScore, -5);
549
+ const handleBulkHighlightsIncrease = createRelativeAdjuster('highlightsScore', setHighlightsScore, 5);
550
+ const handleBulkHighlightsIncreaseMax = createRelativeAdjuster('highlightsScore', setHighlightsScore, 20);
551
+ const handleBulkShadowsDecreaseMax = createRelativeAdjuster('shadowsScore', setShadowsScore, -20);
552
+ const handleBulkShadowsDecrease = createRelativeAdjuster('shadowsScore', setShadowsScore, -5);
553
+ const handleBulkShadowsIncrease = createRelativeAdjuster('shadowsScore', setShadowsScore, 5);
554
+ const handleBulkShadowsIncreaseMax = createRelativeAdjuster('shadowsScore', setShadowsScore, 20);
555
+ const handleBulkWhitesDecreaseMax = createRelativeAdjuster('whitesScore', setWhitesScore, -20);
556
+ const handleBulkWhitesDecrease = createRelativeAdjuster('whitesScore', setWhitesScore, -5);
557
+ const handleBulkWhitesIncrease = createRelativeAdjuster('whitesScore', setWhitesScore, 5);
558
+ const handleBulkWhitesIncreaseMax = createRelativeAdjuster('whitesScore', setWhitesScore, 20);
559
+ const handleBulkBlacksDecreaseMax = createRelativeAdjuster('blacksScore', setBlacksScore, -20);
560
+ const handleBulkBlacksDecrease = createRelativeAdjuster('blacksScore', setBlacksScore, -5);
561
+ const handleBulkBlacksIncrease = createRelativeAdjuster('blacksScore', setBlacksScore, 5);
562
+ const handleBulkBlacksIncreaseMax = createRelativeAdjuster('blacksScore', setBlacksScore, 20);
563
+ const handleBulkClarityDecreaseMax = createRelativeAdjuster('clarityScore', setClarityScore, -20);
564
+ const handleBulkClarityDecrease = createRelativeAdjuster('clarityScore', setClarityScore, -5);
565
+ const handleBulkClarityIncrease = createRelativeAdjuster('clarityScore', setClarityScore, 5);
566
+ const handleBulkClarityIncreaseMax = createRelativeAdjuster('clarityScore', setClarityScore, 20);
567
+ const handleBulkSharpnessDecreaseMax = createRelativeAdjuster('sharpnessScore', setSharpnessScore, -20);
568
+ const handleBulkSharpnessDecrease = createRelativeAdjuster('sharpnessScore', setSharpnessScore, -5);
569
+ const handleBulkSharpnessIncrease = createRelativeAdjuster('sharpnessScore', setSharpnessScore, 5);
570
+ const handleBulkSharpnessIncreaseMax = createRelativeAdjuster('sharpnessScore', setSharpnessScore, 20);
571
+ const handleScriptReady = useCallback(async () => {
572
+ console.log("[Editor] Script tag is ready."); // Log entry
573
+ if (typeof window.Module === 'function' && !editorRef.current) {
574
+ console.log("[Editor] window.Module found. Initializing editor..."); // Log entry
575
+ try {
576
+ setEditorStatus("Loading WASM module...");
577
+ const editor = new HonchoEditor();
578
+ await editor.initialize(true);
579
+ editorRef.current = editor;
580
+ setIsEditorReady(true);
581
+ setEditorStatus("Ready! Select an image to start.");
582
+ console.log("[Editor] Initialization successful."); // Log entry
583
+ }
584
+ catch (error) {
585
+ console.error("[Editor] CRITICAL: Editor initialization failed:", error); // Critical error log
586
+ setEditorStatus(`Error: Could not load editor. See device logs.`);
587
+ }
588
+ }
589
+ else {
590
+ console.warn("[Editor] handleScriptReady called but conditions not met.", {
591
+ isModuleFunction: typeof window.Module === 'function',
592
+ isEditorAlreadyInitialized: !!editorRef.current
593
+ });
594
+ }
595
+ }, []);
596
+ // MARK: - UI Handlers (Moved from page.tsx)
597
+ // Header and Dialog Handlers
598
+ const handleHeaderMenuClick = (event) => setHeaderMenuAnchorEl(event.currentTarget);
599
+ const handleHeaderMenuClose = () => setHeaderMenuAnchorEl(null);
600
+ const handleAlertClose = () => {
601
+ setIsConnectionSlow(false);
602
+ };
603
+ const handleOpenCopyDialog = () => {
604
+ const newColorChecks = {
605
+ temperature: tempScore !== 0,
606
+ tint: tintScore !== 0,
607
+ vibrance: vibranceScore !== 0,
608
+ saturation: saturationScore !== 0,
609
+ };
610
+ const newLightChecks = {
611
+ exposure: exposureScore !== 0,
612
+ contrast: contrastScore !== 0,
613
+ highlights: highlightsScore !== 0,
614
+ shadows: shadowsScore !== 0,
615
+ whites: whitesScore !== 0,
616
+ blacks: blacksScore !== 0,
617
+ };
618
+ const newDetailsChecks = {
619
+ clarity: clarityScore !== 0,
620
+ sharpness: sharpnessScore !== 0,
621
+ };
622
+ setCopyColorChecks(newColorChecks);
623
+ setCopyLightChecks(newLightChecks);
624
+ setCopyDetailsChecks(newDetailsChecks);
625
+ setCopyDialogExpanded({
626
+ color: Object.values(newColorChecks).some(isChecked => isChecked),
627
+ light: Object.values(newLightChecks).some(isChecked => isChecked),
628
+ details: Object.values(newDetailsChecks).some(isChecked => isChecked),
629
+ });
630
+ setCopyDialogOpen(true);
631
+ handleHeaderMenuClose();
632
+ };
633
+ const handleCloseCopyDialog = () => setCopyDialogOpen(false);
634
+ const handleCopyParentChange = (event, setter) => {
635
+ const isChecked = event.target.checked;
636
+ setter((prev) => {
637
+ const newState = {};
638
+ Object.keys(prev).forEach(key => { newState[key] = isChecked; });
639
+ return newState;
640
+ });
641
+ };
642
+ const handleCopyChildChange = (event, setter) => {
643
+ setter((prev) => ({
644
+ ...prev,
645
+ [event.target.name]: event.target.checked,
646
+ }));
647
+ };
648
+ const handleToggleCopyDialogExpand = (section) => {
649
+ setCopyDialogExpanded(prev => ({ ...prev, [section]: !prev[section] }));
650
+ };
651
+ const handleCopyEdit = useCallback(() => {
652
+ const adjustmentsToCopy = {};
653
+ // Color Adjustments
654
+ if (copyColorChecks.temperature)
655
+ adjustmentsToCopy.tempScore = tempScore;
656
+ if (copyColorChecks.tint)
657
+ adjustmentsToCopy.tintScore = tintScore;
658
+ if (copyColorChecks.vibrance)
659
+ adjustmentsToCopy.vibranceScore = vibranceScore;
660
+ if (copyColorChecks.saturation)
661
+ adjustmentsToCopy.saturationScore = saturationScore;
662
+ // Light Adjustments
663
+ if (copyLightChecks.exposure)
664
+ adjustmentsToCopy.exposureScore = exposureScore;
665
+ if (copyLightChecks.contrast)
666
+ adjustmentsToCopy.contrastScore = contrastScore;
667
+ if (copyLightChecks.highlights)
668
+ adjustmentsToCopy.highlightsScore = highlightsScore;
669
+ if (copyLightChecks.shadows)
670
+ adjustmentsToCopy.shadowsScore = shadowsScore;
671
+ if (copyLightChecks.whites)
672
+ adjustmentsToCopy.whitesScore = whitesScore;
673
+ if (copyLightChecks.blacks)
674
+ adjustmentsToCopy.blacksScore = blacksScore;
675
+ // Details Adjustments
676
+ if (copyDetailsChecks.clarity)
677
+ adjustmentsToCopy.clarityScore = clarityScore;
678
+ if (copyDetailsChecks.sharpness)
679
+ adjustmentsToCopy.sharpnessScore = sharpnessScore;
680
+ // Combine with existing copied adjustments to not lose unchecked values from a previous copy
681
+ setCopiedAdjustments(prev => ({ ...initialAdjustments, ...prev, ...adjustmentsToCopy }));
682
+ console.log("Copied selected adjustments:", adjustmentsToCopy);
683
+ }, [
684
+ copyColorChecks, copyLightChecks, copyDetailsChecks,
685
+ tempScore, tintScore, vibranceScore, saturationScore, exposureScore, contrastScore,
686
+ highlightsScore, shadowsScore, whitesScore, blacksScore, clarityScore, sharpnessScore
687
+ ]);
688
+ const handleConfirmCopy = () => { handleCopyEdit(); handleCloseCopyDialog(); setShowCopyAlert(true); };
689
+ const handlePasteEdit = useCallback(() => {
690
+ if (copiedAdjustments) {
691
+ applyAdjustmentState(copiedAdjustments);
692
+ }
693
+ }, [copiedAdjustments, applyAdjustmentState]);
694
+ // Panel Handlers
695
+ const handleColorAccordionChange = (panel) => (_, isExpanded) => {
696
+ setColorAdjustmentExpandedPanels(prev => isExpanded ? [...new Set([...prev, panel])] : prev.filter(p => p !== panel));
697
+ };
698
+ const handlePresetAccordionChange = (panel) => (_, isExpanded) => {
699
+ setPresetExpandedPanels(prev => isExpanded ? [...new Set([...prev, panel])] : prev.filter(p => p !== panel));
700
+ };
701
+ // MARK: - Preset Handlers
702
+ // Also it calls for the backend endpoint
703
+ const fetchPresets = useCallback(async () => {
704
+ if (!controller)
705
+ return;
706
+ try {
707
+ const fetchedPresets = await controller.getPresets();
708
+ setPresets(fetchedPresets);
709
+ }
710
+ catch (error) {
711
+ console.error("Failed to fetch presets:", error);
712
+ }
713
+ }, [controller]);
714
+ const handleSelectMobilePreset = (presetId) => setSelectedMobilePreset(presetId);
715
+ const handleSelectDesktopPreset = (presetId) => setSelectedDesktopPreset(presetId);
716
+ const handlePresetMenuClick = (event, presetId) => {
717
+ event.stopPropagation();
718
+ setPresetMenuAnchorEl(event.currentTarget);
719
+ setActivePresetMenuId(presetId);
720
+ };
721
+ const handlePresetMenuClose = () => { setPresetMenuAnchorEl(null); setActivePresetMenuId(null); };
722
+ const handleRemovePreset = () => { console.log(`Remove: ${activePresetMenuId}`); handlePresetMenuClose(); };
723
+ const handleRenamePreset = useCallback(async (newName) => {
724
+ if (!controller || !activePresetMenuId)
725
+ return;
726
+ try {
727
+ await controller.renamePreset(activePresetMenuId, newName);
728
+ // On success, update the preset in local state
729
+ setPresets(prev => prev.map(p => p.id === activePresetMenuId ? { ...p, name: newName } : p));
730
+ }
731
+ catch (error) {
732
+ console.error("Failed to rename preset:", error);
733
+ }
734
+ handlePresetMenuClose();
735
+ }, [controller, activePresetMenuId]);
736
+ const handleDeletePreset = useCallback(async () => {
737
+ if (!controller || !activePresetMenuId)
738
+ return;
739
+ try {
740
+ await controller.deletePreset(activePresetMenuId);
741
+ // On success, remove the preset from local state
742
+ setPresets(prevPresets => prevPresets.filter(p => p.id !== activePresetMenuId));
743
+ }
744
+ catch (error) {
745
+ console.error("Failed to delete preset:", error);
746
+ }
747
+ handlePresetMenuClose(); // Close the options menu
748
+ }, [controller, activePresetMenuId]);
749
+ // Preset Modal Handlers
750
+ const handleOpenPresetModal = () => { setIsPresetCreated(false); setPresetModalOpen(true); };
751
+ const handleClosePresetModal = () => setPresetModalOpen(false);
752
+ const handleCreatePreset = useCallback(async () => {
753
+ if (!controller)
754
+ return;
755
+ const currentAdjustments = { tempScore, tintScore, vibranceScore, exposureScore, highlightsScore, shadowsScore, whitesScore, blacksScore, saturationScore, contrastScore, clarityScore, sharpnessScore };
756
+ try {
757
+ const newPreset = await controller.createPreset(presetName, currentAdjustments);
758
+ if (newPreset) {
759
+ // Add the new preset returned from the API to our local state
760
+ setPresets(prevPresets => [...prevPresets, newPreset]);
761
+ }
762
+ }
763
+ catch (error) {
764
+ console.error("Failed to create preset:", error);
765
+ }
766
+ console.log("Creating preset:", presetName);
767
+ const newPreset = { id: `preset${presets.length + 1}`, name: presetName };
768
+ setPresets(prevPresets => [...prevPresets, newPreset]);
769
+ setIsPresetCreated(true);
770
+ handleClosePresetModal();
771
+ setTimeout(() => setIsPresetCreated(false), 1000);
772
+ }, [controller, presetName, tempScore, tintScore, exposureScore, highlightsScore, shadowsScore, whitesScore, blacksScore, saturationScore, contrastScore, clarityScore, sharpnessScore]);
773
+ const handleOpenPresetModalMobile = () => { setIsPresetCreated(false); setPresetModalOpenMobile(true); };
774
+ const handleClosePresetModalMobile = () => setPresetModalOpenMobile(false);
775
+ const handleCreatePresetMobile = () => {
776
+ console.log("Creating mobile preset:", presetName);
777
+ const newPreset = { id: `preset${presets.length + 1}`, name: presetName };
778
+ setPresets(prevPresets => [...prevPresets, newPreset]);
779
+ setIsPresetCreated(true);
780
+ handleClosePresetModalMobile();
781
+ setTimeout(() => setIsPresetCreated(false), 1000);
782
+ };
783
+ const handleNameChange = (event) => setPresetName(event.target.value);
784
+ // Watermark Handlers
785
+ const handleOpenWatermarkView = () => setIsCreatingWatermark(true);
786
+ const handleSaveWatermark = () => setIsCreatingWatermark(false);
787
+ const handleCancelWatermark = () => setIsCreatingWatermark(false);
788
+ const handleOpenRenameModal = useCallback(() => {
789
+ if (!activePresetMenuId)
790
+ return;
791
+ const preset = presets.find(p => p.id === activePresetMenuId);
792
+ if (preset) {
793
+ setPresetToRename(preset);
794
+ setNewPresetName(preset.name); // Pre-fill the input with the current name
795
+ setRenameModalOpen(true);
796
+ }
797
+ handlePresetMenuClose(); // Close the small options menu
798
+ }, [activePresetMenuId, presets]);
799
+ const handleCloseRenameModal = () => {
800
+ setRenameModalOpen(false);
801
+ setPresetToRename(null);
802
+ setNewPresetName("");
803
+ };
804
+ const handleConfirmRename = useCallback(async () => {
805
+ if (!presetToRename || !newPresetName)
806
+ return;
807
+ try {
808
+ await controller.renamePreset(presetToRename.id, newPresetName);
809
+ // On success, update the preset in local state
810
+ setPresets(prev => prev.map(p => p.id === presetToRename.id ? { ...p, name: newPresetName } : p));
811
+ }
812
+ catch (error) {
813
+ console.error("Failed to rename preset:", error);
814
+ }
815
+ handleCloseRenameModal();
816
+ }, [controller, presetToRename, newPresetName]);
817
+ // Bulk Editing Handlers
818
+ const toggleBulkEditing = () => {
819
+ setIsBulkEditing(prev => {
820
+ const isNowBulk = !prev;
821
+ setSelectedImages(isNowBulk ? 'Selected' : 'Select');
822
+ return isNowBulk;
823
+ });
824
+ };
825
+ const handleSelectBulkPreset = (event) => setSelectedBulkPreset(event.target.value);
826
+ // MARK : Image original and canvas
827
+ const handleShowOriginal = useCallback(() => {
828
+ if (!editorRef.current || !isImageLoaded)
829
+ return;
830
+ console.log("Showing original image...");
831
+ // 1. Set the flag to true to pause history recording
832
+ setIsViewingOriginal(true);
833
+ // 2. Apply the initial state to the view
834
+ applyAdjustmentState(initialAdjustments);
835
+ }, [isImageLoaded, applyAdjustmentState]);
836
+ const handleShowEdited = useCallback(() => {
837
+ if (!editorRef.current || !isImageLoaded)
838
+ return;
839
+ console.log("Restoring edited image...");
840
+ const latestState = history[historyIndex];
841
+ if (latestState) {
842
+ // 3. Re-apply the latest state from history
843
+ applyAdjustmentState(latestState);
844
+ }
845
+ // 4. Set the flag back to false AFTER the state has been restored.
846
+ // A small timeout ensures this runs after the re-render.
847
+ setTimeout(() => setIsViewingOriginal(false), 0);
848
+ }, [isImageLoaded, history, historyIndex, applyAdjustmentState]);
849
+ // MARK: - Zoom Handlers
850
+ const handleZoomAction = useCallback((action) => {
851
+ let newZoom = zoomLevel;
852
+ const zoomStep = 1.25;
853
+ switch (action) {
854
+ case 'in':
855
+ newZoom *= zoomStep;
856
+ break;
857
+ case 'out':
858
+ newZoom /= zoomStep;
859
+ break;
860
+ case 'fit':
861
+ newZoom = 1;
862
+ break;
863
+ case '50%':
864
+ newZoom = 0.5;
865
+ break;
866
+ case '100%':
867
+ newZoom = 1;
868
+ break;
869
+ case '200%':
870
+ newZoom = 2;
871
+ break;
872
+ }
873
+ setZoomLevel(Math.max(0.1, Math.min(newZoom, 8)));
874
+ }, [zoomLevel]);
875
+ const handleWheelZoom = useCallback((event) => {
876
+ if (!isImageLoaded)
877
+ return;
878
+ event.preventDefault(); // Prevent page from scrolling
879
+ const zoomFactor = 1.1;
880
+ let newZoom = zoomLevel;
881
+ if (event.deltaY < 0) {
882
+ newZoom *= zoomFactor; // Scroll up to zoom in
883
+ }
884
+ else {
885
+ newZoom /= zoomFactor; // Scroll down to zoom out
886
+ }
887
+ setZoomLevel(Math.max(0.1, Math.min(newZoom, 8)));
888
+ }, [zoomLevel, isImageLoaded]);
889
+ useEffect(() => {
890
+ if (canvasRef.current) {
891
+ canvasRef.current.style.transition = 'transform 0.1s ease-out';
892
+ canvasRef.current.style.transform = `scale(${zoomLevel})`;
893
+ }
894
+ }, [zoomLevel]);
895
+ // MARK: - Effects
896
+ // Preset Image List
897
+ useEffect(() => {
898
+ fetchPresets();
899
+ }, [controller, fetchPresets]);
900
+ // Image Load
901
+ useEffect(() => {
902
+ if (isImageLoaded && editorRef.current && canvasRef.current) {
903
+ const { width, height } = editorRef.current.getImageSize();
904
+ canvasRef.current.width = width;
905
+ canvasRef.current.height = height;
906
+ updateCanvas();
907
+ setEditorStatus("Image loaded successfully!");
908
+ }
909
+ }, [isImageLoaded, updateCanvas]);
910
+ // Adjustment USE EFFECTS
911
+ useEffect(() => { if (isImageLoaded) {
912
+ editorRef.current?.setExposure(exposureScore);
913
+ updateCanvas();
914
+ } }, [exposureScore, isImageLoaded, updateCanvas]);
915
+ useEffect(() => { if (isImageLoaded) {
916
+ editorRef.current?.setVibrance(vibranceScore);
917
+ updateCanvas();
918
+ } }, [vibranceScore, isImageLoaded, updateCanvas]);
919
+ useEffect(() => { if (isImageLoaded) {
920
+ editorRef.current?.setContrast(contrastScore);
921
+ updateCanvas();
922
+ } }, [contrastScore, isImageLoaded, updateCanvas]);
923
+ useEffect(() => { if (isImageLoaded) {
924
+ editorRef.current?.setHighlights(highlightsScore);
925
+ updateCanvas();
926
+ } }, [highlightsScore, isImageLoaded, updateCanvas]);
927
+ useEffect(() => { if (isImageLoaded) {
928
+ editorRef.current?.setShadows(shadowsScore);
929
+ updateCanvas();
930
+ } }, [shadowsScore, isImageLoaded, updateCanvas]);
931
+ useEffect(() => { if (isImageLoaded) {
932
+ editorRef.current?.setSaturation(saturationScore);
933
+ updateCanvas();
934
+ } }, [saturationScore, isImageLoaded, updateCanvas]);
935
+ useEffect(() => { if (isImageLoaded) {
936
+ editorRef.current?.setTemperature(tempScore);
937
+ updateCanvas();
938
+ } }, [tempScore, isImageLoaded, updateCanvas]);
939
+ useEffect(() => { if (isImageLoaded) {
940
+ editorRef.current?.setTint(tintScore);
941
+ updateCanvas();
942
+ } }, [tintScore, isImageLoaded, updateCanvas]);
943
+ useEffect(() => { if (isImageLoaded) {
944
+ editorRef.current?.setBlacks(blacksScore);
945
+ updateCanvas();
946
+ } }, [blacksScore, isImageLoaded, updateCanvas]);
947
+ useEffect(() => { if (isImageLoaded) {
948
+ editorRef.current?.setWhites(whitesScore);
949
+ updateCanvas();
950
+ } }, [whitesScore, isImageLoaded, updateCanvas]);
951
+ useEffect(() => { if (isImageLoaded) {
952
+ editorRef.current?.setClarity(clarityScore);
953
+ updateCanvas();
954
+ } }, [clarityScore, isImageLoaded, updateCanvas]);
955
+ useEffect(() => { if (isImageLoaded) {
956
+ editorRef.current?.setSharpness(sharpnessScore);
957
+ updateCanvas();
958
+ } }, [sharpnessScore, isImageLoaded, updateCanvas]);
959
+ useEffect(() => {
960
+ // 5. Add a check to ignore state changes while viewing the original
961
+ if (!isImageLoaded || isViewingOriginal)
962
+ return;
963
+ const newState = { tempScore, tintScore, vibranceScore, exposureScore, highlightsScore, shadowsScore, whitesScore, blacksScore, saturationScore, contrastScore, clarityScore, sharpnessScore };
964
+ if (JSON.stringify(history[historyIndex]) === JSON.stringify(newState))
965
+ return;
966
+ const newHistory = history.slice(0, historyIndex + 1);
967
+ setHistory([...newHistory, newState]);
968
+ setHistoryIndex(newHistory.length);
969
+ }, [
970
+ tempScore, tintScore, vibranceScore, exposureScore, highlightsScore, shadowsScore,
971
+ whitesScore, blacksScore, saturationScore, contrastScore, clarityScore, sharpnessScore,
972
+ isImageLoaded, history, historyIndex,
973
+ isViewingOriginal
974
+ ]);
975
+ useEffect(() => {
976
+ if (showCopyAlert) {
977
+ const timer = setTimeout(() => setShowCopyAlert(false), 2000);
978
+ return () => clearTimeout(timer);
979
+ }
980
+ }, [showCopyAlert]);
981
+ useEffect(() => {
982
+ const handleOnline = () => setIsOnline(true);
983
+ const handleOffline = () => setIsOnline(false);
984
+ window.addEventListener('online', handleOnline);
985
+ window.addEventListener('offline', handleOffline);
986
+ return () => {
987
+ window.removeEventListener('online', handleOnline);
988
+ window.removeEventListener('offline', handleOffline);
989
+ };
990
+ }, []);
991
+ useEffect(() => {
992
+ // The function returned by useEffect is the cleanup function.
993
+ // It will run only when the component that uses this hook unmounts.
994
+ return () => {
995
+ if (editorRef.current) {
996
+ console.log("Cleaning up Honcho Editor instance...");
997
+ editorRef.current.cleanup(); // This calls the C++ cleanup function
998
+ }
999
+ };
1000
+ }, []);
1001
+ return {
1002
+ // Refs
1003
+ canvasRef,
1004
+ canvasContainerRef,
1005
+ fileInputRef,
1006
+ displayedToken,
1007
+ handleBack: controller.handleBack,
1008
+ onGetImage: controller.onGetImage,
1009
+ getImageList: controller.getImageList,
1010
+ syncConfig: controller.syncConfig,
1011
+ getPresets: controller.getPresets,
1012
+ createPreset: controller.createPreset,
1013
+ deletePreset: controller.deletePreset,
1014
+ renamePreset: controller.renamePreset,
1015
+ // Refs for mobile panel
1016
+ panelRef,
1017
+ contentRef,
1018
+ // State for mobile panel
1019
+ panelHeight,
1020
+ // Handlers for mobile panel
1021
+ handleDragStart,
1022
+ handleContentHeightChange,
1023
+ // Status & State
1024
+ editorStatus,
1025
+ isEditorReady,
1026
+ isImageLoaded,
1027
+ isPasteAvailable: copiedAdjustments !== null,
1028
+ isOnline,
1029
+ isConnectionSlow,
1030
+ showCopyAlert,
1031
+ isCopyDialogOpen,
1032
+ isPublished,
1033
+ activePanel,
1034
+ activeSubPanel,
1035
+ headerMenuAnchorEl,
1036
+ anchorMenuZoom,
1037
+ colorAdjustmentExpandedPanels,
1038
+ presetExpandedPanels,
1039
+ isCreatingWatermark,
1040
+ isPresetModalOpen,
1041
+ isPresetModalOpenMobile,
1042
+ presetName,
1043
+ isPresetCreated,
1044
+ selectedMobilePreset,
1045
+ selectedDesktopPreset,
1046
+ selectedBulkPreset,
1047
+ presetMenuAnchorEl,
1048
+ activePresetMenuId,
1049
+ currentAspectRatio,
1050
+ currentSquareRatio,
1051
+ currentWideRatio,
1052
+ angelScore,
1053
+ widthSizePX,
1054
+ heightSizePX,
1055
+ isBulkEditing,
1056
+ selectedImages,
1057
+ colorAdjustments,
1058
+ lightAdjustments,
1059
+ detailsAdjustments,
1060
+ handleShowOriginal,
1061
+ handleShowEdited,
1062
+ handleWheelZoom,
1063
+ handleZoomAction,
1064
+ zoomLevelText: `${Math.round(zoomLevel * 100)}%`,
1065
+ presets,
1066
+ // Functions
1067
+ handleScriptReady,
1068
+ handleFileChange,
1069
+ handleAlertClose,
1070
+ loadImageFromId,
1071
+ loadImageFromUrl,
1072
+ handleRevert,
1073
+ handleUndo,
1074
+ handleRedo,
1075
+ handleOpenCopyDialog,
1076
+ handleCloseCopyDialog,
1077
+ copyColorChecks,
1078
+ setCopyColorChecks,
1079
+ copyLightChecks,
1080
+ setCopyLightChecks,
1081
+ copyDetailsChecks,
1082
+ setCopyDetailsChecks,
1083
+ copyDialogExpanded,
1084
+ handleCopyParentChange,
1085
+ handleCopyChildChange,
1086
+ handleToggleCopyDialogExpand,
1087
+ handleConfirmCopy,
1088
+ handleCopyEdit,
1089
+ handlePasteEdit,
1090
+ // adjustClarityBulk,
1091
+ // adjustSharpnessBulk,
1092
+ // Setters & Handlers
1093
+ setActivePanel,
1094
+ setActiveSubPanel,
1095
+ setHeaderMenuAnchorEl,
1096
+ setAnchorMenuZoom,
1097
+ handleHeaderMenuClick,
1098
+ handleHeaderMenuClose,
1099
+ setColorAdjustments,
1100
+ setLightAdjustments,
1101
+ setDetailsAdjustments,
1102
+ handleColorAccordionChange,
1103
+ handlePresetAccordionChange,
1104
+ handleSelectMobilePreset,
1105
+ handleSelectDesktopPreset,
1106
+ handlePresetMenuClick,
1107
+ handlePresetMenuClose,
1108
+ handleCreatePreset,
1109
+ handleRemovePreset,
1110
+ handleRenamePreset,
1111
+ handleDeletePreset,
1112
+ handleOpenPresetModal,
1113
+ handleClosePresetModal,
1114
+ handleOpenPresetModalMobile,
1115
+ handleClosePresetModalMobile,
1116
+ handleCreatePresetMobile,
1117
+ setPresetName,
1118
+ handleNameChange,
1119
+ isRenameModalOpen,
1120
+ presetToRename,
1121
+ newPresetName,
1122
+ setNewPresetName,
1123
+ handleOpenRenameModal,
1124
+ handleCloseRenameModal,
1125
+ handleConfirmRename,
1126
+ handleOpenWatermarkView,
1127
+ handleSaveWatermark,
1128
+ handleCancelWatermark,
1129
+ toggleBulkEditing,
1130
+ handleSelectBulkPreset,
1131
+ // Adjustment State & Setters
1132
+ tempScore, setTempScore: setTempScoreAbs,
1133
+ tintScore, setTintScore: setTintScoreAbs,
1134
+ vibranceScore, setVibranceScore: setVibranceScoreAbs,
1135
+ saturationScore, setSaturationScore: setSaturationScoreAbs,
1136
+ exposureScore, setExposureScore: setExposureScoreAbs,
1137
+ highlightsScore, setHighlightsScore: setHighlightsScoreAbs,
1138
+ shadowsScore, setShadowsScore: setShadowsScoreAbs,
1139
+ whitesScore, setWhitesScore: setWhitesScoreAbs,
1140
+ blacksScore, setBlacksScore: setBlacksScoreAbs,
1141
+ contrastScore, setContrastScore: setContrastScoreAbs,
1142
+ clarityScore, setClarityScore: setClarityScoreAbs,
1143
+ sharpnessScore, setSharpnessScore: setSharpnessScoreAbs,
1144
+ // Bulk Adjustment Handlers
1145
+ // Note: These handlers are for image list
1146
+ imageList,
1147
+ adjustmentsMap,
1148
+ selectedImageIds,
1149
+ handleToggleImageSelection,
1150
+ // Note: These handlers are for bulk adjustments
1151
+ // Adjustment Colors
1152
+ handleBulkTempDecreaseMax,
1153
+ handleBulkTempDecrease,
1154
+ handleBulkTempIncrease,
1155
+ handleBulkTempIncreaseMax,
1156
+ handleBulkTintDecreaseMax,
1157
+ handleBulkTintDecrease,
1158
+ handleBulkTintIncrease,
1159
+ handleBulkTintIncreaseMax,
1160
+ handleBulkVibranceDecreaseMax,
1161
+ handleBulkVibranceDecrease,
1162
+ handleBulkVibranceIncrease,
1163
+ handleBulkVibranceIncreaseMax,
1164
+ handleBulkSaturationDecreaseMax,
1165
+ handleBulkSaturationDecrease,
1166
+ handleBulkSaturationIncrease,
1167
+ handleBulkSaturationIncreaseMax,
1168
+ // Adjustment Light
1169
+ handleBulkExposureDecreaseMax,
1170
+ handleBulkExposureDecrease,
1171
+ handleBulkExposureIncrease,
1172
+ handleBulkExposureIncreaseMax,
1173
+ handleBulkContrastDecreaseMax,
1174
+ handleBulkContrastDecrease,
1175
+ handleBulkContrastIncrease,
1176
+ handleBulkContrastIncreaseMax,
1177
+ handleBulkHighlightsDecreaseMax,
1178
+ handleBulkHighlightsDecrease,
1179
+ handleBulkHighlightsIncrease,
1180
+ handleBulkHighlightsIncreaseMax,
1181
+ handleBulkShadowsDecreaseMax,
1182
+ handleBulkShadowsDecrease,
1183
+ handleBulkShadowsIncrease,
1184
+ handleBulkShadowsIncreaseMax,
1185
+ handleBulkWhitesDecreaseMax,
1186
+ handleBulkWhitesDecrease,
1187
+ handleBulkWhitesIncrease,
1188
+ handleBulkWhitesIncreaseMax,
1189
+ handleBulkBlacksDecreaseMax,
1190
+ handleBulkBlacksDecrease,
1191
+ handleBulkBlacksIncrease,
1192
+ handleBulkBlacksIncreaseMax,
1193
+ // Adjustment Details
1194
+ handleBulkClarityDecreaseMax,
1195
+ handleBulkClarityDecrease,
1196
+ handleBulkClarityIncrease,
1197
+ handleBulkClarityIncreaseMax,
1198
+ handleBulkSharpnessDecreaseMax,
1199
+ handleBulkSharpnessDecrease,
1200
+ handleBulkSharpnessIncrease,
1201
+ handleBulkSharpnessIncreaseMax,
1202
+ };
1203
+ }