@yogiswara/honcho-editor-ui 3.3.4 → 3.4.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 (34) hide show
  1. package/dist/components/editor/GalleryAlbum/AlbumImageGallery.d.ts +14 -7
  2. package/dist/components/editor/GalleryAlbum/AlbumImageGallery.js +207 -5
  3. package/dist/components/editor/GalleryAlbum/ImageItemComponents.d.ts +25 -0
  4. package/dist/components/editor/GalleryAlbum/ImageItemComponents.js +179 -0
  5. package/dist/components/editor/GalleryAlbum/colorsGallery.d.ts +9 -0
  6. package/dist/components/editor/GalleryAlbum/colorsGallery.js +9 -0
  7. package/dist/components/editor/GalleryAlbum/svg/Tick.d.ts +2 -0
  8. package/dist/components/editor/GalleryAlbum/svg/Tick.js +6 -0
  9. package/dist/components/editor/HBulkAccordionColorAdjustment.js +1 -2
  10. package/dist/components/editor/HBulkAccordionColorAdjustmentColors.js +1 -1
  11. package/dist/components/editor/HBulkPresetMobile.d.ts +2 -2
  12. package/dist/components/editor/HBulkPresetMobile.js +2 -2
  13. package/dist/components/editor/HImageEditorBulkMobile.d.ts +2 -2
  14. package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +0 -3
  15. package/dist/hooks/demo/HonchoEditorBulkDemo.js +770 -411
  16. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +0 -3
  17. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +882 -354
  18. package/dist/hooks/demo/index.d.ts +0 -2
  19. package/dist/hooks/demo/index.js +3 -2
  20. package/dist/hooks/editor/type.d.ts +15 -13
  21. package/dist/hooks/editor/useHonchoEditorBulk.d.ts +47 -5
  22. package/dist/hooks/editor/useHonchoEditorBulk.js +252 -133
  23. package/dist/hooks/useAdjustmentHistory.js +12 -12
  24. package/dist/hooks/useAdjustmentHistoryBatch.d.ts +33 -31
  25. package/dist/hooks/useAdjustmentHistoryBatch.js +703 -170
  26. package/dist/hooks/usePreset.js +12 -12
  27. package/dist/index.d.ts +5 -7
  28. package/dist/index.js +5 -4
  29. package/dist/services/type.d.ts +14 -0
  30. package/dist/utils/adjustment.d.ts +1 -1
  31. package/dist/utils/adjustment.js +15 -14
  32. package/dist/utils/logger.d.ts +3 -0
  33. package/dist/utils/logger.js +11 -0
  34. package/package.json +4 -2
@@ -1,354 +1,882 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useRef, useEffect, useCallback } from 'react';
3
- import { Box, Container, Typography, Button, Card, CardMedia, CardContent, Alert, CircularProgress, Paper, Divider, TextField, Stack, Slider, Grid, IconButton, Tooltip, } from '@mui/material';
4
- import { NavigateBefore, NavigateNext, Undo, Redo, RestartAlt, ZoomIn, ZoomOut, FitScreen, Home, Palette } from '@mui/icons-material';
5
- import { useHonchoEditorSingle } from '../editor/useHonchoEditorSingle';
6
- import { useEditorHeadless } from '../../lib/hooks/useEditorHeadless';
7
- // Mock data and controller (same as before)
8
- // Mock data for demonstration
9
- const createMockGallery = (id, adjustments) => ({
10
- id,
11
- uid: 'demo-user',
12
- event_id: 'demo-event',
13
- download: {
14
- key: `${id}-download`,
15
- path: `https://picsum.photos/800/600?random=${id}`,
16
- size: 1024000,
17
- width: 800,
18
- height: 600,
19
- },
20
- download_edited: {
21
- key: `${id}-download-edited`,
22
- path: `https://picsum.photos/800/600?random=${id}`,
23
- size: 1024000,
24
- width: 800,
25
- height: 600,
26
- },
27
- thumbnail: {
28
- key: `${id}-thumb`,
29
- path: `https://picsum.photos/300/200?random=${id}`,
30
- size: 50000,
31
- width: 300,
32
- height: 200,
33
- },
34
- thumbnail_edited: {
35
- key: `${id}-thumb-edited`,
36
- path: `https://picsum.photos/300/200?random=${id}`,
37
- size: 50000,
38
- width: 300,
39
- height: 200,
40
- },
41
- is_original: true,
42
- available: true,
43
- show_gallery: true,
44
- editor_config: {
45
- color_adjustment: {
46
- temperature: adjustments?.tempScore || 0,
47
- tint: adjustments?.tintScore || 0,
48
- vibrance: adjustments?.vibranceScore || 0,
49
- saturation: adjustments?.saturationScore || 0,
50
- exposure: adjustments?.exposureScore || 0,
51
- highlights: adjustments?.highlightsScore || 0,
52
- shadows: adjustments?.shadowsScore || 0,
53
- whites: adjustments?.whitesScore || 0,
54
- blacks: adjustments?.blacksScore || 0,
55
- contrast: adjustments?.contrastScore || 0,
56
- clarity: adjustments?.clarityScore || 0,
57
- sharpness: adjustments?.sharpnessScore || 0,
58
- },
59
- transformation_adjustment: [],
60
- watermarks: [],
61
- },
62
- log: {
63
- created_at: new Date().toISOString(),
64
- updated_at: new Date().toISOString(),
65
- },
66
- });
67
- const createMockController = () => {
68
- console.log('🏭 createMockController() called - Creating new mock controller instance');
69
- const mockImages = [
70
- createMockGallery('1', { tempScore: 5, exposureScore: 2 }),
71
- createMockGallery('2', { contrastScore: -3, clarityScore: 8 }),
72
- createMockGallery('3', { vibranceScore: 10, saturationScore: 5 }),
73
- createMockGallery('4', { tempScore: -8, tintScore: 4 }),
74
- createMockGallery('5', { exposureScore: -5, shadowsScore: 15 }),
75
- ];
76
- return {
77
- onGetImage: async (uid, imageId) => {
78
- console.log(`[Controller] 📷 onGetImage called: uid=${uid}, imageId=${imageId}`);
79
- await new Promise(resolve => setTimeout(resolve, 300));
80
- const image = mockImages.find(img => img.id === imageId);
81
- if (!image)
82
- throw new Error(`Image ${imageId} not found`);
83
- console.log(`[Controller] 📷 onGetImage returning image:`, image.id);
84
- return image;
85
- },
86
- getImageList: async (uid, eventId, page) => {
87
- console.log(`[Controller] 📋 getImageList called: uid=${uid}, eventId=${eventId}, page=${page}`);
88
- await new Promise(resolve => setTimeout(resolve, 500));
89
- const pageSize = 4;
90
- const startIndex = (page - 1) * pageSize;
91
- const endIndex = startIndex + pageSize;
92
- const pageImages = mockImages.slice(startIndex, endIndex);
93
- console.log(`[Controller] 📋 getImageList returning ${pageImages.length} images for page ${page}`);
94
- return {
95
- gallery: pageImages,
96
- limit: pageSize,
97
- current_page: page,
98
- prev_page: page > 1 ? page - 1 : 0,
99
- next_page: endIndex < mockImages.length ? page + 1 : 0,
100
- sum_of_image: pageImages.length,
101
- };
102
- },
103
- syncConfig: async (uid) => {
104
- console.log(`[Controller] 🔄 syncConfig called: uid=${uid}`);
105
- await new Promise(resolve => setTimeout(resolve, 200));
106
- },
107
- handleBack: (uid, imageId) => {
108
- console.log(`[Controller] ⬅️ handleBack called: uid=${uid}, imageId=${imageId}`);
109
- console.log(`Back to gallery from image: ${imageId}`);
110
- },
111
- getPresets: async (uid) => {
112
- console.log(`[Controller] 🎨 getPresets called: uid=${uid}`);
113
- await new Promise(resolve => setTimeout(resolve, 300));
114
- const presets = [
115
- { 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 },
116
- { 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 },
117
- { 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 },
118
- ];
119
- console.log(`🎨 getPresets returning ${presets.length} presets`);
120
- return presets;
121
- },
122
- createPreset: async (uid, name, settings) => {
123
- console.log(`[Controller] createPreset called: uid=${uid}, name=${name}`, settings);
124
- await new Promise(resolve => setTimeout(resolve, 500));
125
- console.log(`Creating preset: ${name}`, settings);
126
- },
127
- deletePreset: async (uid, presetId) => {
128
- console.log(`[Controller] 🗑️ deletePreset called: uid=${uid}, presetId=${presetId}`);
129
- await new Promise(resolve => setTimeout(resolve, 300));
130
- console.log(`Deleting preset: ${presetId}`);
131
- },
132
- updatePreset: async (uid, data) => {
133
- console.log(`[Controller] 🔄 updatePreset called: uid=${uid}`, data);
134
- await new Promise(resolve => setTimeout(resolve, 300));
135
- console.log(`Updating preset:`, data);
136
- },
137
- createEditorConfig: async (uid, payload) => {
138
- console.log(`[Controller] ⚙️ createEditorConfig called: uid=${uid}`, payload);
139
- await new Promise(resolve => setTimeout(resolve, 200));
140
- console.log('Creating editor config:', payload);
141
- },
142
- getEditorHistory: async (uid, imageId) => {
143
- console.log(`[Controller] 📚 getEditorHistory called: uid=${uid}, imageId=${imageId}`);
144
- await new Promise(resolve => setTimeout(resolve, 200));
145
- return { current_task_id: "", history: [] };
146
- },
147
- getGalleryUpdateTimestamp: async (uid, eventId) => {
148
- console.log(`[Controller] getGalleryUpdateTimestamp called: uid=${uid}, eventId=${eventId}`);
149
- await new Promise(resolve => setTimeout(resolve, 100));
150
- return { gallery: [] };
151
- },
152
- setHistoryIndex: async (uid, imageId, taskId) => {
153
- console.log(`[Controller] 📍 setHistoryIndex called: uid=${uid}, imageId=${imageId}, taskId=${taskId}`);
154
- await new Promise(resolve => setTimeout(resolve, 100));
155
- console.log(`Setting history index for image ${imageId} to task ${taskId}`);
156
- },
157
- };
158
- };
159
- // Dumb Adjustment Slider Component
160
- const AdjustmentSlider = ({ label, value, field, onValueChange, onDragStart, onDragEnd, isBatchMode, min = -100, max = 100, step = 1 }) => {
161
- return (_jsxs(Box, { sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "body2", gutterBottom: true, children: [label, ": ", value, isBatchMode && (_jsx(Box, { component: "span", sx: {
162
- ml: 1,
163
- px: 1,
164
- py: 0.25,
165
- backgroundColor: 'warning.main',
166
- color: 'warning.contrastText',
167
- borderRadius: 1,
168
- fontSize: '0.75rem',
169
- fontWeight: 'bold'
170
- }, children: "LIVE EDIT" }))] }), _jsx(Slider, { value: value, onChange: (_, newValue) => {
171
- const numValue = newValue;
172
- onValueChange(field, numValue);
173
- }, onMouseDown: () => {
174
- if (!isBatchMode) {
175
- onDragStart();
176
- }
177
- }, onMouseUp: () => {
178
- onDragEnd();
179
- }, onTouchStart: () => {
180
- if (!isBatchMode) {
181
- onDragStart();
182
- }
183
- }, onTouchEnd: () => {
184
- onDragEnd();
185
- }, min: min, max: max, step: step, valueLabelDisplay: "auto", size: "small" })] }));
186
- };
187
- // Dumb Preset Card Component
188
- const PresetCard = ({ preset, onApply, onDelete, isActive }) => (_jsx(Card, { sx: {
189
- border: isActive ? 2 : 1,
190
- borderColor: isActive ? 'primary.main' : 'divider',
191
- cursor: 'pointer',
192
- transition: 'all 0.2s',
193
- '&:hover': {
194
- boxShadow: 2,
195
- transform: 'translateY(-1px)',
196
- }
197
- }, onClick: () => onApply(preset), children: _jsxs(CardContent, { sx: { p: 2, '&:last-child': { pb: 2 } }, children: [_jsxs(Typography, { variant: "subtitle2", gutterBottom: true, children: [preset.name, isActive && ' ✓'] }), _jsxs(Typography, { variant: "caption", color: "text.secondary", display: "block", sx: { mb: 1 }, children: ["Temp: ", preset.temperature > 0 ? '+' : '', preset.temperature, ", Exp: ", preset.exposure > 0 ? '+' : '', preset.exposure, ", Con: ", preset.contrast > 0 ? '+' : '', preset.contrast] }), _jsxs(Box, { sx: { mt: 1, display: 'flex', gap: 1 }, children: [_jsx(Button, { size: "small", variant: "outlined", onClick: (e) => {
198
- e.stopPropagation();
199
- onApply(preset);
200
- }, children: "Apply" }), _jsx(Button, { size: "small", variant: "outlined", color: "error", onClick: (e) => {
201
- e.stopPropagation();
202
- onDelete(preset.id);
203
- }, children: "Delete" })] })] }) }));
204
- export const HonchoEditorSingleCleanDemo = () => {
205
- // Initialize mock controller
206
- const [controller] = useState(() => createMockController());
207
- // UI state (only UI-specific state here)
208
- const [showAdjustments, setShowAdjustments] = useState(true);
209
- const [newPresetName, setNewPresetName] = useState('');
210
- const [isImageLoaded, setIsImageLoaded] = useState(false);
211
- // Business logic hook - handles adjustments, presets, navigation
212
- const { state, actions } = useHonchoEditorSingle({
213
- controller,
214
- initImageId: '1',
215
- firebaseUid: 'demo-user'
216
- });
217
- // Editor hook - handles editor operations separately
218
- const editorHeadless = useEditorHeadless({
219
- scriptUrl: '/honcho-photo-editor.js',
220
- wasmUrl: '/honcho-photo-editor.wasm'
221
- });
222
- // Refs for canvas rendering
223
- const canvasRef = useRef(null);
224
- // Load image when gallery data changes
225
- useEffect(() => {
226
- if (state.currentImageData && editorHeadless.isReady && editorHeadless.loadImageFromUrl) {
227
- console.log('Loading image to editor:', state.currentImageData.id);
228
- setIsImageLoaded(false);
229
- const imageUrl = state.currentImageData.raw_edited?.path || state.currentImageData.download.path;
230
- editorHeadless.loadImageFromUrl(imageUrl)
231
- .then((size) => {
232
- console.log('Image loaded successfully:', size);
233
- setIsImageLoaded(true);
234
- })
235
- .catch((error) => {
236
- console.error('Error loading image:', error);
237
- setIsImageLoaded(false);
238
- });
239
- }
240
- }, [state.currentImageData, editorHeadless.isReady, editorHeadless.loadImageFromUrl]);
241
- // Apply adjustments to editor when they change
242
- useEffect(() => {
243
- if (editorHeadless.editor && isImageLoaded && canvasRef.current) {
244
- console.log('Applying adjustments to editor:', state.currentAdjustments);
245
- // Get converted adjustments from business logic hook
246
- const editorAdjustments = actions.getEditorAdjustments();
247
- try {
248
- editorHeadless.editor.setAdjustments(editorAdjustments);
249
- editorHeadless.editor.processImage();
250
- editorHeadless.editor.renderToCanvas(canvasRef.current);
251
- console.log('Rendered to canvas successfully');
252
- }
253
- catch (error) {
254
- console.error('Error rendering to canvas:', error);
255
- }
256
- }
257
- }, [state.currentAdjustments, editorHeadless.editor, isImageLoaded, actions]);
258
- // Helper functions that only handle UI logic
259
- const handleCreatePreset = async () => {
260
- if (!newPresetName.trim())
261
- return;
262
- try {
263
- const preset = await actions.createPreset(newPresetName, state.currentAdjustments);
264
- if (preset) {
265
- setNewPresetName('');
266
- console.log('Preset created successfully:', preset);
267
- }
268
- else {
269
- console.error('Failed to create preset');
270
- }
271
- }
272
- catch (error) {
273
- console.error('Error creating preset:', error);
274
- }
275
- };
276
- const navigationNext = useCallback(() => {
277
- // Must be set to avoid adjust without image
278
- setIsImageLoaded(false);
279
- actions.navigateNext();
280
- }, [actions]);
281
- const navigationPrev = useCallback(() => {
282
- // Must be set to avoid adjust without image
283
- setIsImageLoaded(false);
284
- actions.navigatePrev();
285
- }, [actions]);
286
- const getAdjustmentSummary = (adjustments) => {
287
- const summary = [];
288
- if (adjustments.tempScore !== 0)
289
- summary.push(`Temp: ${adjustments.tempScore > 0 ? '+' : ''}${adjustments.tempScore}`);
290
- if (adjustments.exposureScore !== 0)
291
- summary.push(`Exp: ${adjustments.exposureScore > 0 ? '+' : ''}${adjustments.exposureScore}`);
292
- if (adjustments.contrastScore !== 0)
293
- summary.push(`Con: ${adjustments.contrastScore > 0 ? '+' : ''}${adjustments.contrastScore}`);
294
- if (adjustments.clarityScore !== 0)
295
- summary.push(`Cla: ${adjustments.clarityScore > 0 ? '+' : ''}${adjustments.clarityScore}`);
296
- return summary.join(', ') || 'No adjustments';
297
- };
298
- return (_jsxs(Container, { maxWidth: "xl", sx: { py: 4 }, children: [_jsx(Typography, { variant: "h3", gutterBottom: true, align: "center", children: "Clean Honcho Editor Single Image Demo" }), _jsx(Typography, { variant: "subtitle1", align: "center", color: "text.secondary", gutterBottom: true, children: "This is a \"dumb\" view-only component that consumes state from useHonchoEditorSingle" }), (state.galleryError || state.presetsError) && (_jsx(Alert, { severity: "error", sx: { mb: 3 }, children: state.galleryError || state.presetsError })), _jsxs(Grid, { container: true, spacing: 3, children: [_jsx(Grid, { item: true, xs: 12, md: 8, children: _jsxs(Paper, { sx: { p: 2, mb: 2 }, children: [_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "h6", children: ["Image ", state.currentImageData?.id, state.activePreset && ` - ${state.activePreset.name}`] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Tooltip, { title: "Previous Image", children: _jsx("span", { children: _jsx(IconButton, { onClick: navigationPrev, disabled: !state.isPrevAvailable || state.isGalleryLoading, children: _jsx(NavigateBefore, {}) }) }) }), _jsx(Tooltip, { title: "Next Image", children: _jsx("span", { children: _jsx(IconButton, { onClick: navigationNext, disabled: !state.isNextAvailable || state.isGalleryLoading, children: _jsx(NavigateNext, {}) }) }) }), _jsx(Tooltip, { title: "Back to Gallery", children: _jsx(IconButton, { onClick: () => console.log('Back to gallery'), children: _jsx(Home, {}) }) }), _jsx(Divider, { orientation: "vertical", flexItem: true }), _jsx(Tooltip, { title: "Zoom In", children: _jsx(IconButton, { onClick: () => console.log('Zoom in'), children: _jsx(ZoomIn, {}) }) }), _jsx(Tooltip, { title: "Zoom Out", children: _jsx(IconButton, { onClick: () => console.log('Zoom out'), children: _jsx(ZoomOut, {}) }) }), _jsx(Tooltip, { title: "Fit to Screen", children: _jsx(IconButton, { onClick: () => console.log('Fit to screen'), children: _jsx(FitScreen, {}) }) }), _jsx(Typography, { variant: "body2", sx: { alignSelf: 'center', minWidth: '60px' }, children: "100%" })] })] }), _jsxs(Box, { sx: {
299
- position: 'relative',
300
- width: '100%',
301
- height: '500px',
302
- bgcolor: '#f5f5f5',
303
- border: 1,
304
- borderColor: 'divider',
305
- borderRadius: 1,
306
- overflow: 'hidden',
307
- display: 'flex',
308
- alignItems: 'center',
309
- justifyContent: 'center'
310
- }, children: [editorHeadless.isReady && (_jsx(Box, { sx: {
311
- position: 'absolute',
312
- top: 8,
313
- right: 8,
314
- zIndex: 3,
315
- px: 1,
316
- py: 0.5,
317
- backgroundColor: 'success.main',
318
- color: 'success.contrastText',
319
- borderRadius: 1,
320
- fontSize: '0.75rem',
321
- fontWeight: 'bold'
322
- }, children: "EDITOR ACTIVE" })), state.currentImageData ? (_jsx(CardMedia, { component: "img", image: state.currentImageData.download.path, alt: `Image ${state.currentImageData.id}`, sx: {
323
- maxWidth: '100%',
324
- maxHeight: '100%',
325
- objectFit: 'contain',
326
- transition: 'transform 0.1s ease-out',
327
- opacity: editorHeadless.isReady ? 0.3 : 1,
328
- zIndex: 1
329
- } })) : state.isGalleryLoading ? (_jsx(CircularProgress, {})) : (_jsx(Typography, { color: "text.secondary", children: "No image loaded" })), _jsx("canvas", { ref: canvasRef, style: {
330
- position: 'absolute',
331
- top: 0,
332
- left: 0,
333
- width: '100%',
334
- height: '100%',
335
- objectFit: 'contain',
336
- pointerEvents: 'none',
337
- zIndex: state.currentImageData && editorHeadless.isReady ? 2 : 0,
338
- opacity: state.currentImageData && editorHeadless.isReady ? 1 : 0,
339
- } })] }), _jsxs(Stack, { direction: "row", justifyContent: "center", spacing: 1, sx: { mt: 2 }, children: [_jsx(Tooltip, { title: "Undo", children: _jsx("span", { children: _jsx(Button, { variant: "outlined", size: "small", onClick: actions.undo, disabled: !state.canUndo, startIcon: _jsx(Undo, {}), children: "Undo" }) }) }), _jsx(Tooltip, { title: "Redo", children: _jsx("span", { children: _jsx(Button, { variant: "outlined", size: "small", onClick: actions.redo, disabled: !state.canRedo, startIcon: _jsx(Redo, {}), children: "Redo" }) }) }), _jsx(Tooltip, { title: "Reset All", children: _jsx(Button, { variant: "outlined", size: "small", onClick: actions.reset, color: "warning", startIcon: _jsx(RestartAlt, {}), children: "Reset" }) })] }), _jsx(Box, { sx: { mt: 2, p: 1, bgcolor: 'grey.50', borderRadius: 1 }, children: _jsxs(Typography, { variant: "caption", color: "text.secondary", children: ["Current Adjustments: ", getAdjustmentSummary(state.currentAdjustments), state.isBatchMode && (_jsx(Box, { component: "span", sx: {
340
- ml: 1,
341
- px: 1,
342
- py: 0.25,
343
- backgroundColor: 'warning.main',
344
- color: 'warning.contrastText',
345
- borderRadius: 1,
346
- fontSize: '0.625rem',
347
- fontWeight: 'bold'
348
- }, children: "BATCH MODE" }))] }) })] }) }), _jsxs(Grid, { item: true, xs: 12, md: 4, children: [_jsxs(Paper, { sx: { p: 2, mb: 2 }, children: [_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [_jsx(Typography, { variant: "h6", children: "Controls" }), _jsxs(Button, { variant: "outlined", size: "small", onClick: () => setShowAdjustments(!showAdjustments), startIcon: _jsx(Palette, {}), children: [showAdjustments ? 'Hide' : 'Show', " Adjustments"] })] }), showAdjustments && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "subtitle2", gutterBottom: true, sx: { mt: 2 }, children: "Color" }), _jsx(AdjustmentSlider, { label: "Temperature", value: state.currentAdjustments.tempScore, field: "tempScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Tint", value: state.currentAdjustments.tintScore, field: "tintScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Vibrance", value: state.currentAdjustments.vibranceScore, field: "vibranceScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Saturation", value: state.currentAdjustments.saturationScore, field: "saturationScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(Divider, { sx: { my: 2 } }), _jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Light" }), _jsx(AdjustmentSlider, { label: "Exposure", value: state.currentAdjustments.exposureScore, field: "exposureScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Contrast", value: state.currentAdjustments.contrastScore, field: "contrastScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Highlights", value: state.currentAdjustments.highlightsScore, field: "highlightsScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Shadows", value: state.currentAdjustments.shadowsScore, field: "shadowsScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Whites", value: state.currentAdjustments.whitesScore, field: "whitesScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Blacks", value: state.currentAdjustments.blacksScore, field: "blacksScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(Divider, { sx: { my: 2 } }), _jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Details" }), _jsx(AdjustmentSlider, { label: "Clarity", value: state.currentAdjustments.clarityScore, field: "clarityScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode }), _jsx(AdjustmentSlider, { label: "Sharpness", value: state.currentAdjustments.sharpnessScore, field: "sharpnessScore", onValueChange: actions.updateAdjustment, onDragStart: actions.startBatchMode, onDragEnd: actions.endBatchMode, isBatchMode: state.isBatchMode })] }))] }), _jsxs(Paper, { sx: { p: 2 }, children: [_jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [_jsxs(Typography, { variant: "h6", children: ["Presets (", state.presets.length, ")"] }), _jsx(Button, { variant: "outlined", size: "small", onClick: actions.loadPresets, disabled: state.presetsLoading, children: "Refresh" })] }), state.presetsError && (_jsx(Alert, { severity: "error", sx: { mb: 2 }, children: state.presetsError })), state.presetsLoading && (_jsx(Box, { display: "flex", justifyContent: "center", py: 2, children: _jsx(CircularProgress, { size: 20 }) })), _jsxs(Box, { sx: { mb: 2 }, children: [_jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Quick Actions" }), _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "wrap", useFlexGap: true, children: [_jsx(Button, { size: "small", variant: !state.activePreset ? "contained" : "outlined", onClick: actions.reset, children: "Reset All" }), state.activePreset && (_jsxs(Button, { size: "small", variant: "outlined", color: "info", disabled: true, children: ["Active: ", state.activePreset.name] }))] })] }), _jsx(Divider, { sx: { my: 2 } }), _jsx(Stack, { spacing: 1, sx: { maxHeight: '300px', overflowY: 'auto' }, children: state.presets.length === 0 && !state.presetsLoading ? (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { textAlign: 'center', py: 2 }, children: "No presets available. Create your first preset below!" })) : (state.presets.map((preset) => (_jsx(PresetCard, { preset: preset, onApply: actions.applyPreset, onDelete: actions.deletePreset, isActive: state.activePreset?.id === preset.id }, preset.id)))) }), _jsx(Divider, { sx: { my: 2 } }), _jsx(Typography, { variant: "subtitle2", gutterBottom: true, children: "Create New Preset" }), _jsx(Typography, { variant: "caption", color: "text.secondary", display: "block", sx: { mb: 1 }, children: "Save current adjustments as a new preset" }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(TextField, { placeholder: "Enter preset name...", value: newPresetName, onChange: (e) => setNewPresetName(e.target.value), size: "small", fullWidth: true, onKeyPress: (e) => {
349
- if (e.key === 'Enter') {
350
- handleCreatePreset();
351
- }
352
- } }), _jsx(Button, { variant: "contained", size: "small", onClick: handleCreatePreset, disabled: !newPresetName.trim() || state.presetsLoading, children: "Create" })] })] })] })] }), _jsxs(Box, { mt: 4, children: [_jsx(Divider, {}), _jsx(Typography, { variant: "body2", color: "text.secondary", align: "center", sx: { mt: 2 }, children: "Clean Architecture Demo: This component is \"dumb\" and only renders UI. All logic is handled by useHonchoEditorSingle hook." }), _jsx(Paper, { sx: { p: 2, mt: 2, bgcolor: 'grey.50' }, children: _jsxs(Typography, { variant: "caption", display: "block", children: [_jsx("strong", { children: "Debug Info:" }), _jsx("br", {}), "Current Image: ", state.currentImageData?.id, " | Gallery Loading: ", state.isGalleryLoading ? 'Yes' : 'No', " | Image Loaded: ", isImageLoaded ? 'Yes' : 'No', " | Next Available: ", state.isNextAvailable ? 'Yes' : 'No', " | Prev Available: ", state.isPrevAvailable ? 'Yes' : 'No', " | Active Preset: ", state.activePreset?.name || 'None', " | Can Undo: ", state.canUndo ? 'Yes' : 'No', " | Can Redo: ", state.canRedo ? 'Yes' : 'No', " | Batch Mode: ", state.isBatchMode ? 'Yes' : 'No', " | Editor Ready: ", editorHeadless.isReady ? 'Yes' : 'No'] }) })] })] }));
353
- };
354
- export default HonchoEditorSingleCleanDemo;
1
+ "use strict";
2
+ // import React, { useState, useRef, useEffect, useCallback } from 'react';
3
+ // import {
4
+ // Box,
5
+ // Container,
6
+ // Typography,
7
+ // Button,
8
+ // Card,
9
+ // CardMedia,
10
+ // CardContent,
11
+ // Alert,
12
+ // CircularProgress,
13
+ // Paper,
14
+ // Divider,
15
+ // TextField,
16
+ // Stack,
17
+ // Slider,
18
+ // Grid,
19
+ // IconButton,
20
+ // Tooltip,
21
+ // } from '@mui/material';
22
+ // import {
23
+ // NavigateBefore,
24
+ // NavigateNext,
25
+ // Undo,
26
+ // Redo,
27
+ // RestartAlt,
28
+ // ZoomIn,
29
+ // ZoomOut,
30
+ // FitScreen,
31
+ // Home,
32
+ // Palette
33
+ // } from '@mui/icons-material';
34
+ // import { useHonchoEditorSingle } from '../editor/useHonchoEditorSingle';
35
+ // import { useEditorHeadless } from '../../lib/hooks/useEditorHeadless';
36
+ // import { Controller, AdjustmentState, Preset } from '../editor/type';
37
+ // import { Gallery, CreateEditorTaskRequest } from '../editor/type';
38
+ // import { ImageSize } from '../../lib/editor/honcho-editor';
39
+ // // Mock data and controller (same as before)
40
+ // // Mock data for demonstration
41
+ // const createMockGallery = (id: string, adjustments?: Partial<AdjustmentState>): Gallery => ({
42
+ // id,
43
+ // uid: 'demo-user',
44
+ // event_id: 'demo-event',
45
+ // download: {
46
+ // key: `${id}-download`,
47
+ // path: `https://picsum.photos/800/600?random=${id}`,
48
+ // size: 1024000,
49
+ // width: 800,
50
+ // height: 600,
51
+ // },
52
+ // download_edited: {
53
+ // key: `${id}-download-edited`,
54
+ // path: `https://picsum.photos/800/600?random=${id}`,
55
+ // size: 1024000,
56
+ // width: 800,
57
+ // height: 600,
58
+ // },
59
+ // thumbnail: {
60
+ // key: `${id}-thumb`,
61
+ // path: `https://picsum.photos/300/200?random=${id}`,
62
+ // size: 50000,
63
+ // width: 300,
64
+ // height: 200,
65
+ // },
66
+ // thumbnail_edited: {
67
+ // key: `${id}-thumb-edited`,
68
+ // path: `https://picsum.photos/300/200?random=${id}`,
69
+ // size: 50000,
70
+ // width: 300,
71
+ // height: 200,
72
+ // },
73
+ // is_original: true,
74
+ // available: true,
75
+ // show_gallery: true,
76
+ // editor_config: {
77
+ // color_adjustment: {
78
+ // temperature: adjustments?.tempScore || 0,
79
+ // tint: adjustments?.tintScore || 0,
80
+ // vibrance: adjustments?.vibranceScore || 0,
81
+ // saturation: adjustments?.saturationScore || 0,
82
+ // exposure: adjustments?.exposureScore || 0,
83
+ // highlights: adjustments?.highlightsScore || 0,
84
+ // shadows: adjustments?.shadowsScore || 0,
85
+ // whites: adjustments?.whitesScore || 0,
86
+ // blacks: adjustments?.blacksScore || 0,
87
+ // contrast: adjustments?.contrastScore || 0,
88
+ // clarity: adjustments?.clarityScore || 0,
89
+ // sharpness: adjustments?.sharpnessScore || 0,
90
+ // },
91
+ // transformation_adjustment: [],
92
+ // watermarks: [],
93
+ // },
94
+ // log: {
95
+ // created_at: new Date().toISOString(),
96
+ // updated_at: new Date().toISOString(),
97
+ // },
98
+ // });
99
+ // const createMockController = (): Controller => {
100
+ // console.log('🏭 createMockController() called - Creating new mock controller instance');
101
+ // const mockImages = [
102
+ // createMockGallery('1', { tempScore: 5, exposureScore: 2 }),
103
+ // createMockGallery('2', { contrastScore: -3, clarityScore: 8 }),
104
+ // createMockGallery('3', { vibranceScore: 10, saturationScore: 5 }),
105
+ // createMockGallery('4', { tempScore: -8, tintScore: 4 }),
106
+ // createMockGallery('5', { exposureScore: -5, shadowsScore: 15 }),
107
+ // ];
108
+ // return {
109
+ // onGetImage: async (uid: string, imageId: string) => {
110
+ // console.log(`[Controller] 📷 onGetImage called: uid=${uid}, imageId=${imageId}`);
111
+ // await new Promise(resolve => setTimeout(resolve, 300));
112
+ // const image = mockImages.find(img => img.id === imageId);
113
+ // if (!image) throw new Error(`Image ${imageId} not found`);
114
+ // console.log(`[Controller] 📷 onGetImage returning image:`, image.id);
115
+ // return image;
116
+ // },
117
+ // getImageList: async (uid: string, eventId: string, page: number) => {
118
+ // console.log(`[Controller] 📋 getImageList called: uid=${uid}, eventId=${eventId}, page=${page}`);
119
+ // await new Promise(resolve => setTimeout(resolve, 500));
120
+ // const pageSize = 4;
121
+ // const startIndex = (page - 1) * pageSize;
122
+ // const endIndex = startIndex + pageSize;
123
+ // const pageImages = mockImages.slice(startIndex, endIndex);
124
+ // console.log(`[Controller] 📋 getImageList returning ${pageImages.length} images for page ${page}`);
125
+ // return {
126
+ // gallery: pageImages,
127
+ // limit: pageSize,
128
+ // current_page: page,
129
+ // prev_page: page > 1 ? page - 1 : 0,
130
+ // next_page: endIndex < mockImages.length ? page + 1 : 0,
131
+ // sum_of_image: pageImages.length,
132
+ // };
133
+ // },
134
+ // syncConfig: async (uid: string) => {
135
+ // console.log(`[Controller] 🔄 syncConfig called: uid=${uid}`);
136
+ // await new Promise(resolve => setTimeout(resolve, 200));
137
+ // },
138
+ // handleBack: (uid: string, imageId: string) => {
139
+ // console.log(`[Controller] ⬅️ handleBack called: uid=${uid}, imageId=${imageId}`);
140
+ // console.log(`Back to gallery from image: ${imageId}`);
141
+ // },
142
+ // getPresets: async (uid: string) => {
143
+ // console.log(`[Controller] 🎨 getPresets called: uid=${uid}`);
144
+ // await new Promise(resolve => setTimeout(resolve, 300));
145
+ // const presets = [
146
+ // { 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 },
147
+ // { 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 },
148
+ // { 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 },
149
+ // ];
150
+ // console.log(`🎨 getPresets returning ${presets.length} presets`);
151
+ // return presets;
152
+ // },
153
+ // createPreset: async (uid: string, name: string, settings: AdjustmentState) => {
154
+ // console.log(`[Controller] createPreset called: uid=${uid}, name=${name}`, settings);
155
+ // await new Promise(resolve => setTimeout(resolve, 500));
156
+ // console.log(`Creating preset: ${name}`, settings);
157
+ // },
158
+ // deletePreset: async (uid: string, presetId: string) => {
159
+ // console.log(`[Controller] 🗑️ deletePreset called: uid=${uid}, presetId=${presetId}`);
160
+ // await new Promise(resolve => setTimeout(resolve, 300));
161
+ // console.log(`Deleting preset: ${presetId}`);
162
+ // },
163
+ // updatePreset: async (uid: string, data: Preset) => {
164
+ // console.log(`[Controller] 🔄 updatePreset called: uid=${uid}`, data);
165
+ // await new Promise(resolve => setTimeout(resolve, 300));
166
+ // console.log(`Updating preset:`, data);
167
+ // },
168
+ // createEditorConfig: async (uid: string, payload: CreateEditorTaskRequest) => {
169
+ // console.log(`[Controller] ⚙️ createEditorConfig called: uid=${uid}`, payload);
170
+ // await new Promise(resolve => setTimeout(resolve, 200));
171
+ // console.log('Creating editor config:', payload);
172
+ // },
173
+ // getEditorHistory: async (uid: string, imageId: string) => {
174
+ // console.log(`[Controller] 📚 getEditorHistory called: uid=${uid}, imageId=${imageId}`);
175
+ // await new Promise(resolve => setTimeout(resolve, 200));
176
+ // return { current_task_id: "", history: [] };
177
+ // },
178
+ // getGalleryUpdateTimestamp: async (uid: string, eventId: string) => {
179
+ // console.log(`[Controller] getGalleryUpdateTimestamp called: uid=${uid}, eventId=${eventId}`);
180
+ // await new Promise(resolve => setTimeout(resolve, 100));
181
+ // return { gallery: [] };
182
+ // },
183
+ // setHistoryIndex: async (uid: string, imageId: string, taskId: string) => {
184
+ // console.log(`[Controller] 📍 setHistoryIndex called: uid=${uid}, imageId=${imageId}, taskId=${taskId}`);
185
+ // await new Promise(resolve => setTimeout(resolve, 100));
186
+ // console.log(`Setting history index for image ${imageId} to task ${taskId}`);
187
+ // },
188
+ // };
189
+ // };
190
+ // // Dumb Adjustment Slider Component
191
+ // const AdjustmentSlider: React.FC<{
192
+ // label: string;
193
+ // value: number;
194
+ // field: keyof AdjustmentState;
195
+ // onValueChange: (field: keyof AdjustmentState, value: number) => void;
196
+ // onDragStart: () => void;
197
+ // onDragEnd: () => void;
198
+ // isBatchMode: boolean;
199
+ // min?: number;
200
+ // max?: number;
201
+ // step?: number;
202
+ // }> = ({
203
+ // label,
204
+ // value,
205
+ // field,
206
+ // onValueChange,
207
+ // onDragStart,
208
+ // onDragEnd,
209
+ // isBatchMode,
210
+ // min = -100,
211
+ // max = 100,
212
+ // step = 1
213
+ // }) => {
214
+ // return (
215
+ // <Box sx={{ mb: 2 }}>
216
+ // <Typography variant="body2" gutterBottom>
217
+ // {label}: {value}
218
+ // {isBatchMode && (
219
+ // <Box
220
+ // component="span"
221
+ // sx={{
222
+ // ml: 1,
223
+ // px: 1,
224
+ // py: 0.25,
225
+ // backgroundColor: 'warning.main',
226
+ // color: 'warning.contrastText',
227
+ // borderRadius: 1,
228
+ // fontSize: '0.75rem',
229
+ // fontWeight: 'bold'
230
+ // }}
231
+ // >
232
+ // LIVE EDIT
233
+ // </Box>
234
+ // )}
235
+ // </Typography>
236
+ // <Slider
237
+ // value={value}
238
+ // onChange={(_, newValue) => {
239
+ // const numValue = newValue as number;
240
+ // onValueChange(field, numValue);
241
+ // }}
242
+ // onMouseDown={() => {
243
+ // if (!isBatchMode) {
244
+ // onDragStart();
245
+ // }
246
+ // }}
247
+ // onMouseUp={() => {
248
+ // onDragEnd();
249
+ // }}
250
+ // onTouchStart={() => {
251
+ // if (!isBatchMode) {
252
+ // onDragStart();
253
+ // }
254
+ // }}
255
+ // onTouchEnd={() => {
256
+ // onDragEnd();
257
+ // }}
258
+ // min={min}
259
+ // max={max}
260
+ // step={step}
261
+ // valueLabelDisplay="auto"
262
+ // size="small"
263
+ // />
264
+ // </Box>
265
+ // );
266
+ // };
267
+ // // Dumb Preset Card Component
268
+ // const PresetCard: React.FC<{
269
+ // preset: Preset;
270
+ // onApply: (preset: Preset) => void;
271
+ // onDelete: (presetId: string) => void;
272
+ // isActive: boolean;
273
+ // }> = ({ preset, onApply, onDelete, isActive }) => (
274
+ // <Card
275
+ // sx={{
276
+ // border: isActive ? 2 : 1,
277
+ // borderColor: isActive ? 'primary.main' : 'divider',
278
+ // cursor: 'pointer',
279
+ // transition: 'all 0.2s',
280
+ // '&:hover': {
281
+ // boxShadow: 2,
282
+ // transform: 'translateY(-1px)',
283
+ // }
284
+ // }}
285
+ // onClick={() => onApply(preset)}
286
+ // >
287
+ // <CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
288
+ // <Typography variant="subtitle2" gutterBottom>
289
+ // {preset.name}
290
+ // {isActive && ' ✓'}
291
+ // </Typography>
292
+ // <Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
293
+ // Temp: {preset.temperature > 0 ? '+' : ''}{preset.temperature},
294
+ // Exp: {preset.exposure > 0 ? '+' : ''}{preset.exposure},
295
+ // Con: {preset.contrast > 0 ? '+' : ''}{preset.contrast}
296
+ // </Typography>
297
+ // <Box sx={{ mt: 1, display: 'flex', gap: 1 }}>
298
+ // <Button
299
+ // size="small"
300
+ // variant="outlined"
301
+ // onClick={(e) => {
302
+ // e.stopPropagation();
303
+ // onApply(preset);
304
+ // }}
305
+ // >
306
+ // Apply
307
+ // </Button>
308
+ // <Button
309
+ // size="small"
310
+ // variant="outlined"
311
+ // color="error"
312
+ // onClick={(e) => {
313
+ // e.stopPropagation();
314
+ // onDelete(preset.id);
315
+ // }}
316
+ // >
317
+ // Delete
318
+ // </Button>
319
+ // </Box>
320
+ // </CardContent>
321
+ // </Card>
322
+ // );
323
+ // export const HonchoEditorSingleCleanDemo: React.FC = () => {
324
+ // // Initialize mock controller
325
+ // const [controller] = useState(() => createMockController());
326
+ // // UI state (only UI-specific state here)
327
+ // const [showAdjustments, setShowAdjustments] = useState(true);
328
+ // const [newPresetName, setNewPresetName] = useState('');
329
+ // const [isImageLoaded, setIsImageLoaded] = useState(false);
330
+ // // Business logic hook - handles adjustments, presets, navigation
331
+ // const { state, actions } = useHonchoEditorSingle({
332
+ // controller,
333
+ // initImageId: '1',
334
+ // firebaseUid: 'demo-user'
335
+ // });
336
+ // // Editor hook - handles editor operations separately
337
+ // const editorHeadless = useEditorHeadless({
338
+ // scriptUrl: '/honcho-photo-editor.js',
339
+ // wasmUrl: '/honcho-photo-editor.wasm'
340
+ // });
341
+ // // Refs for canvas rendering
342
+ // const canvasRef = useRef<HTMLCanvasElement>(null);
343
+ // // Load image when gallery data changes
344
+ // useEffect(() => {
345
+ // if (state.currentImageData && editorHeadless.isReady && editorHeadless.loadImageFromUrl) {
346
+ // console.log('Loading image to editor:', state.currentImageData.id);
347
+ // setIsImageLoaded(false);
348
+ // const imageUrl = state.currentImageData.raw_edited?.path || state.currentImageData.download.path;
349
+ // editorHeadless.loadImageFromUrl(imageUrl)
350
+ // .then((size: ImageSize) => {
351
+ // console.log('Image loaded successfully:', size);
352
+ // setIsImageLoaded(true);
353
+ // })
354
+ // .catch((error: Error) => {
355
+ // console.error('Error loading image:', error);
356
+ // setIsImageLoaded(false);
357
+ // });
358
+ // }
359
+ // }, [state.currentImageData, editorHeadless.isReady, editorHeadless.loadImageFromUrl]);
360
+ // // Apply adjustments to editor when they change
361
+ // useEffect(() => {
362
+ // if (editorHeadless.editor && isImageLoaded && canvasRef.current) {
363
+ // console.log('Applying adjustments to editor:', state.currentAdjustments);
364
+ // // Get converted adjustments from business logic hook
365
+ // const editorAdjustments = actions.getEditorAdjustments();
366
+ // try {
367
+ // editorHeadless.editor.setAdjustments(editorAdjustments);
368
+ // editorHeadless.editor.processImage();
369
+ // editorHeadless.editor.renderToCanvas(canvasRef.current);
370
+ // console.log('Rendered to canvas successfully');
371
+ // } catch (error) {
372
+ // console.error('Error rendering to canvas:', error);
373
+ // }
374
+ // }
375
+ // }, [state.currentAdjustments, editorHeadless.editor, isImageLoaded, actions]);
376
+ // // Helper functions that only handle UI logic
377
+ // const handleCreatePreset = async () => {
378
+ // if (!newPresetName.trim()) return;
379
+ // try {
380
+ // const preset = await actions.createPreset(newPresetName, state.currentAdjustments);
381
+ // if (preset) {
382
+ // setNewPresetName('');
383
+ // console.log('Preset created successfully:', preset);
384
+ // } else {
385
+ // console.error('Failed to create preset');
386
+ // }
387
+ // } catch (error) {
388
+ // console.error('Error creating preset:', error);
389
+ // }
390
+ // };
391
+ // const navigationNext = useCallback(() => {
392
+ // // Must be set to avoid adjust without image
393
+ // setIsImageLoaded(false);
394
+ // actions.navigateNext();
395
+ // }, [actions]);
396
+ // const navigationPrev = useCallback(() => {
397
+ // // Must be set to avoid adjust without image
398
+ // setIsImageLoaded(false);
399
+ // actions.navigatePrev();
400
+ // }, [actions]);
401
+ // const getAdjustmentSummary = (adjustments: AdjustmentState) => {
402
+ // const summary = [];
403
+ // if (adjustments.tempScore !== 0) summary.push(`Temp: ${adjustments.tempScore > 0 ? '+' : ''}${adjustments.tempScore}`);
404
+ // if (adjustments.exposureScore !== 0) summary.push(`Exp: ${adjustments.exposureScore > 0 ? '+' : ''}${adjustments.exposureScore}`);
405
+ // if (adjustments.contrastScore !== 0) summary.push(`Con: ${adjustments.contrastScore > 0 ? '+' : ''}${adjustments.contrastScore}`);
406
+ // if (adjustments.clarityScore !== 0) summary.push(`Cla: ${adjustments.clarityScore > 0 ? '+' : ''}${adjustments.clarityScore}`);
407
+ // return summary.join(', ') || 'No adjustments';
408
+ // };
409
+ // return (
410
+ // <Container maxWidth="xl" sx={{ py: 4 }}>
411
+ // <Typography variant="h3" gutterBottom align="center">
412
+ // Clean Honcho Editor Single Image Demo
413
+ // </Typography>
414
+ // <Typography variant="subtitle1" align="center" color="text.secondary" gutterBottom>
415
+ // This is a "dumb" view-only component that consumes state from useHonchoEditorSingle
416
+ // </Typography>
417
+ // {(state.galleryError || state.presetsError) && (
418
+ // <Alert severity="error" sx={{ mb: 3 }}>
419
+ // {state.galleryError || state.presetsError}
420
+ // </Alert>
421
+ // )}
422
+ // <Grid container spacing={3}>
423
+ // {/* Main Image Area */}
424
+ // <Grid item xs={12} md={8}>
425
+ // <Paper sx={{ p: 2, mb: 2 }}>
426
+ // <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
427
+ // <Typography variant="h6">
428
+ // Image {state.currentImageData?.id}
429
+ // {state.activePreset && ` - ${state.activePreset.name}`}
430
+ // </Typography>
431
+ // <Stack direction="row" spacing={1}>
432
+ // <Tooltip title="Previous Image">
433
+ // <span>
434
+ // <IconButton
435
+ // onClick={navigationPrev}
436
+ // disabled={!state.isPrevAvailable || state.isGalleryLoading}
437
+ // >
438
+ // <NavigateBefore />
439
+ // </IconButton>
440
+ // </span>
441
+ // </Tooltip>
442
+ // <Tooltip title="Next Image">
443
+ // <span>
444
+ // <IconButton
445
+ // onClick={navigationNext}
446
+ // disabled={!state.isNextAvailable || state.isGalleryLoading}
447
+ // >
448
+ // <NavigateNext />
449
+ // </IconButton>
450
+ // </span>
451
+ // </Tooltip>
452
+ // <Tooltip title="Back to Gallery">
453
+ // <IconButton onClick={() => console.log('Back to gallery')}>
454
+ // <Home />
455
+ // </IconButton>
456
+ // </Tooltip>
457
+ // <Divider orientation="vertical" flexItem />
458
+ // <Tooltip title="Zoom In">
459
+ // <IconButton onClick={() => console.log('Zoom in')}>
460
+ // <ZoomIn />
461
+ // </IconButton>
462
+ // </Tooltip>
463
+ // <Tooltip title="Zoom Out">
464
+ // <IconButton onClick={() => console.log('Zoom out')}>
465
+ // <ZoomOut />
466
+ // </IconButton>
467
+ // </Tooltip>
468
+ // <Tooltip title="Fit to Screen">
469
+ // <IconButton onClick={() => console.log('Fit to screen')}>
470
+ // <FitScreen />
471
+ // </IconButton>
472
+ // </Tooltip>
473
+ // <Typography variant="body2" sx={{ alignSelf: 'center', minWidth: '60px' }}>
474
+ // 100%
475
+ // </Typography>
476
+ // </Stack>
477
+ // </Stack>
478
+ // {/* Canvas Container */}
479
+ // <Box
480
+ // sx={{
481
+ // position: 'relative',
482
+ // width: '100%',
483
+ // height: '500px',
484
+ // bgcolor: '#f5f5f5',
485
+ // border: 1,
486
+ // borderColor: 'divider',
487
+ // borderRadius: 1,
488
+ // overflow: 'hidden',
489
+ // display: 'flex',
490
+ // alignItems: 'center',
491
+ // justifyContent: 'center'
492
+ // }}
493
+ // >
494
+ // {/* Status indicator */}
495
+ // {editorHeadless.isReady && (
496
+ // <Box
497
+ // sx={{
498
+ // position: 'absolute',
499
+ // top: 8,
500
+ // right: 8,
501
+ // zIndex: 3,
502
+ // px: 1,
503
+ // py: 0.5,
504
+ // backgroundColor: 'success.main',
505
+ // color: 'success.contrastText',
506
+ // borderRadius: 1,
507
+ // fontSize: '0.75rem',
508
+ // fontWeight: 'bold'
509
+ // }}
510
+ // >
511
+ // EDITOR ACTIVE
512
+ // </Box>
513
+ // )}
514
+ // {state.currentImageData ? (
515
+ // <CardMedia
516
+ // component="img"
517
+ // image={state.currentImageData.download.path}
518
+ // alt={`Image ${state.currentImageData.id}`}
519
+ // sx={{
520
+ // maxWidth: '100%',
521
+ // maxHeight: '100%',
522
+ // objectFit: 'contain',
523
+ // transition: 'transform 0.1s ease-out',
524
+ // opacity: editorHeadless.isReady ? 0.3 : 1,
525
+ // zIndex: 1
526
+ // }}
527
+ // />
528
+ // ) : state.isGalleryLoading ? (
529
+ // <CircularProgress />
530
+ // ) : (
531
+ // <Typography color="text.secondary">No image loaded</Typography>
532
+ // )}
533
+ // {/* Editor Canvas */}
534
+ // <canvas
535
+ // ref={canvasRef}
536
+ // style={{
537
+ // position: 'absolute',
538
+ // top: 0,
539
+ // left: 0,
540
+ // width: '100%',
541
+ // height: '100%',
542
+ // objectFit: 'contain',
543
+ // pointerEvents: 'none',
544
+ // zIndex: state.currentImageData && editorHeadless.isReady ? 2 : 0,
545
+ // opacity: state.currentImageData && editorHeadless.isReady ? 1 : 0,
546
+ // }}
547
+ // />
548
+ // </Box>
549
+ // {/* History Controls */}
550
+ // <Stack direction="row" justifyContent="center" spacing={1} sx={{ mt: 2 }}>
551
+ // <Tooltip title="Undo">
552
+ // <span>
553
+ // <Button
554
+ // variant="outlined"
555
+ // size="small"
556
+ // onClick={actions.undo}
557
+ // disabled={!state.canUndo}
558
+ // startIcon={<Undo />}
559
+ // >
560
+ // Undo
561
+ // </Button>
562
+ // </span>
563
+ // </Tooltip>
564
+ // <Tooltip title="Redo">
565
+ // <span>
566
+ // <Button
567
+ // variant="outlined"
568
+ // size="small"
569
+ // onClick={actions.redo}
570
+ // disabled={!state.canRedo}
571
+ // startIcon={<Redo />}
572
+ // >
573
+ // Redo
574
+ // </Button>
575
+ // </span>
576
+ // </Tooltip>
577
+ // <Tooltip title="Reset All">
578
+ // <Button
579
+ // variant="outlined"
580
+ // size="small"
581
+ // onClick={actions.reset}
582
+ // color="warning"
583
+ // startIcon={<RestartAlt />}
584
+ // >
585
+ // Reset
586
+ // </Button>
587
+ // </Tooltip>
588
+ // </Stack>
589
+ // {/* Current Adjustments Summary */}
590
+ // <Box sx={{ mt: 2, p: 1, bgcolor: 'grey.50', borderRadius: 1 }}>
591
+ // <Typography variant="caption" color="text.secondary">
592
+ // Current Adjustments: {getAdjustmentSummary(state.currentAdjustments)}
593
+ // {state.isBatchMode && (
594
+ // <Box
595
+ // component="span"
596
+ // sx={{
597
+ // ml: 1,
598
+ // px: 1,
599
+ // py: 0.25,
600
+ // backgroundColor: 'warning.main',
601
+ // color: 'warning.contrastText',
602
+ // borderRadius: 1,
603
+ // fontSize: '0.625rem',
604
+ // fontWeight: 'bold'
605
+ // }}
606
+ // >
607
+ // BATCH MODE
608
+ // </Box>
609
+ // )}
610
+ // </Typography>
611
+ // </Box>
612
+ // </Paper>
613
+ // </Grid>
614
+ // {/* Controls Panel */}
615
+ // <Grid item xs={12} md={4}>
616
+ // <Paper sx={{ p: 2, mb: 2 }}>
617
+ // <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
618
+ // <Typography variant="h6">Controls</Typography>
619
+ // <Button
620
+ // variant="outlined"
621
+ // size="small"
622
+ // onClick={() => setShowAdjustments(!showAdjustments)}
623
+ // startIcon={<Palette />}
624
+ // >
625
+ // {showAdjustments ? 'Hide' : 'Show'} Adjustments
626
+ // </Button>
627
+ // </Stack>
628
+ // {showAdjustments && (
629
+ // <>
630
+ // {/* Color Adjustments */}
631
+ // <Typography variant="subtitle2" gutterBottom sx={{ mt: 2 }}>
632
+ // Color
633
+ // </Typography>
634
+ // <AdjustmentSlider
635
+ // label="Temperature"
636
+ // value={state.currentAdjustments.tempScore}
637
+ // field="tempScore"
638
+ // onValueChange={actions.updateAdjustment}
639
+ // onDragStart={actions.startBatchMode}
640
+ // onDragEnd={actions.endBatchMode}
641
+ // isBatchMode={state.isBatchMode}
642
+ // />
643
+ // <AdjustmentSlider
644
+ // label="Tint"
645
+ // value={state.currentAdjustments.tintScore}
646
+ // field="tintScore"
647
+ // onValueChange={actions.updateAdjustment}
648
+ // onDragStart={actions.startBatchMode}
649
+ // onDragEnd={actions.endBatchMode}
650
+ // isBatchMode={state.isBatchMode}
651
+ // />
652
+ // <AdjustmentSlider
653
+ // label="Vibrance"
654
+ // value={state.currentAdjustments.vibranceScore}
655
+ // field="vibranceScore"
656
+ // onValueChange={actions.updateAdjustment}
657
+ // onDragStart={actions.startBatchMode}
658
+ // onDragEnd={actions.endBatchMode}
659
+ // isBatchMode={state.isBatchMode}
660
+ // />
661
+ // <AdjustmentSlider
662
+ // label="Saturation"
663
+ // value={state.currentAdjustments.saturationScore}
664
+ // field="saturationScore"
665
+ // onValueChange={actions.updateAdjustment}
666
+ // onDragStart={actions.startBatchMode}
667
+ // onDragEnd={actions.endBatchMode}
668
+ // isBatchMode={state.isBatchMode}
669
+ // />
670
+ // <Divider sx={{ my: 2 }} />
671
+ // {/* Light Adjustments */}
672
+ // <Typography variant="subtitle2" gutterBottom>
673
+ // Light
674
+ // </Typography>
675
+ // <AdjustmentSlider
676
+ // label="Exposure"
677
+ // value={state.currentAdjustments.exposureScore}
678
+ // field="exposureScore"
679
+ // onValueChange={actions.updateAdjustment}
680
+ // onDragStart={actions.startBatchMode}
681
+ // onDragEnd={actions.endBatchMode}
682
+ // isBatchMode={state.isBatchMode}
683
+ // />
684
+ // <AdjustmentSlider
685
+ // label="Contrast"
686
+ // value={state.currentAdjustments.contrastScore}
687
+ // field="contrastScore"
688
+ // onValueChange={actions.updateAdjustment}
689
+ // onDragStart={actions.startBatchMode}
690
+ // onDragEnd={actions.endBatchMode}
691
+ // isBatchMode={state.isBatchMode}
692
+ // />
693
+ // <AdjustmentSlider
694
+ // label="Highlights"
695
+ // value={state.currentAdjustments.highlightsScore}
696
+ // field="highlightsScore"
697
+ // onValueChange={actions.updateAdjustment}
698
+ // onDragStart={actions.startBatchMode}
699
+ // onDragEnd={actions.endBatchMode}
700
+ // isBatchMode={state.isBatchMode}
701
+ // />
702
+ // <AdjustmentSlider
703
+ // label="Shadows"
704
+ // value={state.currentAdjustments.shadowsScore}
705
+ // field="shadowsScore"
706
+ // onValueChange={actions.updateAdjustment}
707
+ // onDragStart={actions.startBatchMode}
708
+ // onDragEnd={actions.endBatchMode}
709
+ // isBatchMode={state.isBatchMode}
710
+ // />
711
+ // <AdjustmentSlider
712
+ // label="Whites"
713
+ // value={state.currentAdjustments.whitesScore}
714
+ // field="whitesScore"
715
+ // onValueChange={actions.updateAdjustment}
716
+ // onDragStart={actions.startBatchMode}
717
+ // onDragEnd={actions.endBatchMode}
718
+ // isBatchMode={state.isBatchMode}
719
+ // />
720
+ // <AdjustmentSlider
721
+ // label="Blacks"
722
+ // value={state.currentAdjustments.blacksScore}
723
+ // field="blacksScore"
724
+ // onValueChange={actions.updateAdjustment}
725
+ // onDragStart={actions.startBatchMode}
726
+ // onDragEnd={actions.endBatchMode}
727
+ // isBatchMode={state.isBatchMode}
728
+ // />
729
+ // <Divider sx={{ my: 2 }} />
730
+ // {/* Detail Adjustments */}
731
+ // <Typography variant="subtitle2" gutterBottom>
732
+ // Details
733
+ // </Typography>
734
+ // <AdjustmentSlider
735
+ // label="Clarity"
736
+ // value={state.currentAdjustments.clarityScore}
737
+ // field="clarityScore"
738
+ // onValueChange={actions.updateAdjustment}
739
+ // onDragStart={actions.startBatchMode}
740
+ // onDragEnd={actions.endBatchMode}
741
+ // isBatchMode={state.isBatchMode}
742
+ // />
743
+ // <AdjustmentSlider
744
+ // label="Sharpness"
745
+ // value={state.currentAdjustments.sharpnessScore}
746
+ // field="sharpnessScore"
747
+ // onValueChange={actions.updateAdjustment}
748
+ // onDragStart={actions.startBatchMode}
749
+ // onDragEnd={actions.endBatchMode}
750
+ // isBatchMode={state.isBatchMode}
751
+ // />
752
+ // </>
753
+ // )}
754
+ // </Paper>
755
+ // {/* Presets Panel */}
756
+ // <Paper sx={{ p: 2 }}>
757
+ // <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 2 }}>
758
+ // <Typography variant="h6">
759
+ // Presets ({state.presets.length})
760
+ // </Typography>
761
+ // <Button
762
+ // variant="outlined"
763
+ // size="small"
764
+ // onClick={actions.loadPresets}
765
+ // disabled={state.presetsLoading}
766
+ // >
767
+ // Refresh
768
+ // </Button>
769
+ // </Stack>
770
+ // {state.presetsError && (
771
+ // <Alert severity="error" sx={{ mb: 2 }}>
772
+ // {state.presetsError}
773
+ // </Alert>
774
+ // )}
775
+ // {state.presetsLoading && (
776
+ // <Box display="flex" justifyContent="center" py={2}>
777
+ // <CircularProgress size={20} />
778
+ // </Box>
779
+ // )}
780
+ // {/* Quick Preset Actions */}
781
+ // <Box sx={{ mb: 2 }}>
782
+ // <Typography variant="subtitle2" gutterBottom>
783
+ // Quick Actions
784
+ // </Typography>
785
+ // <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
786
+ // <Button
787
+ // size="small"
788
+ // variant={!state.activePreset ? "contained" : "outlined"}
789
+ // onClick={actions.reset}
790
+ // >
791
+ // Reset All
792
+ // </Button>
793
+ // {state.activePreset && (
794
+ // <Button
795
+ // size="small"
796
+ // variant="outlined"
797
+ // color="info"
798
+ // disabled
799
+ // >
800
+ // Active: {state.activePreset.name}
801
+ // </Button>
802
+ // )}
803
+ // </Stack>
804
+ // </Box>
805
+ // <Divider sx={{ my: 2 }} />
806
+ // <Stack spacing={1} sx={{ maxHeight: '300px', overflowY: 'auto' }}>
807
+ // {state.presets.length === 0 && !state.presetsLoading ? (
808
+ // <Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
809
+ // No presets available. Create your first preset below!
810
+ // </Typography>
811
+ // ) : (
812
+ // state.presets.map((preset) => (
813
+ // <PresetCard
814
+ // key={preset.id}
815
+ // preset={preset}
816
+ // onApply={actions.applyPreset}
817
+ // onDelete={actions.deletePreset}
818
+ // isActive={state.activePreset?.id === preset.id}
819
+ // />
820
+ // ))
821
+ // )}
822
+ // </Stack>
823
+ // {/* Create New Preset */}
824
+ // <Divider sx={{ my: 2 }} />
825
+ // <Typography variant="subtitle2" gutterBottom>
826
+ // Create New Preset
827
+ // </Typography>
828
+ // <Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
829
+ // Save current adjustments as a new preset
830
+ // </Typography>
831
+ // <Stack direction="row" spacing={1}>
832
+ // <TextField
833
+ // placeholder="Enter preset name..."
834
+ // value={newPresetName}
835
+ // onChange={(e) => setNewPresetName(e.target.value)}
836
+ // size="small"
837
+ // fullWidth
838
+ // onKeyPress={(e) => {
839
+ // if (e.key === 'Enter') {
840
+ // handleCreatePreset();
841
+ // }
842
+ // }}
843
+ // />
844
+ // <Button
845
+ // variant="contained"
846
+ // size="small"
847
+ // onClick={handleCreatePreset}
848
+ // disabled={!newPresetName.trim() || state.presetsLoading}
849
+ // >
850
+ // Create
851
+ // </Button>
852
+ // </Stack>
853
+ // </Paper>
854
+ // </Grid>
855
+ // </Grid>
856
+ // {/* Debug Information */}
857
+ // <Box mt={4}>
858
+ // <Divider />
859
+ // <Typography variant="body2" color="text.secondary" align="center" sx={{ mt: 2 }}>
860
+ // Clean Architecture Demo: This component is "dumb" and only renders UI.
861
+ // All logic is handled by useHonchoEditorSingle hook.
862
+ // </Typography>
863
+ // <Paper sx={{ p: 2, mt: 2, bgcolor: 'grey.50' }}>
864
+ // <Typography variant="caption" display="block">
865
+ // <strong>Debug Info:</strong><br />
866
+ // Current Image: {state.currentImageData?.id} |
867
+ // Gallery Loading: {state.isGalleryLoading ? 'Yes' : 'No'} |
868
+ // Image Loaded: {isImageLoaded ? 'Yes' : 'No'} |
869
+ // Next Available: {state.isNextAvailable ? 'Yes' : 'No'} |
870
+ // Prev Available: {state.isPrevAvailable ? 'Yes' : 'No'} |
871
+ // Active Preset: {state.activePreset?.name || 'None'} |
872
+ // Can Undo: {state.canUndo ? 'Yes' : 'No'} |
873
+ // Can Redo: {state.canRedo ? 'Yes' : 'No'} |
874
+ // Batch Mode: {state.isBatchMode ? 'Yes' : 'No'} |
875
+ // Editor Ready: {editorHeadless.isReady ? 'Yes' : 'No'}
876
+ // </Typography>
877
+ // </Paper>
878
+ // </Box>
879
+ // </Container>
880
+ // );
881
+ // };
882
+ // export default HonchoEditorSingleCleanDemo;