@yogiswara/honcho-editor-ui 2.5.10 → 2.6.1

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 (50) hide show
  1. package/dist/components/editor/HBulkPreset.js +12 -2
  2. package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +3 -0
  3. package/dist/hooks/demo/HonchoEditorBulkDemo.js +228 -0
  4. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +3 -0
  5. package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +354 -0
  6. package/dist/hooks/demo/index.d.ts +2 -0
  7. package/dist/hooks/demo/index.js +2 -0
  8. package/dist/hooks/editor/type.d.ts +71 -0
  9. package/dist/hooks/editor/useHonchoEditorBulk.d.ts +10 -12
  10. package/dist/hooks/editor/useHonchoEditorBulk.js +126 -10
  11. package/dist/hooks/editor/useHonchoEditorSingle.d.ts +43 -0
  12. package/dist/hooks/editor/useHonchoEditorSingle.js +158 -0
  13. package/dist/hooks/useAdjustmentHistory.d.ts +9 -5
  14. package/dist/hooks/useAdjustmentHistory.js +187 -31
  15. package/dist/hooks/useAdjustmentHistoryBatch.d.ts +18 -1
  16. package/dist/hooks/useAdjustmentHistoryBatch.js +627 -201
  17. package/dist/hooks/useGallerySwipe.d.ts +1 -1
  18. package/dist/hooks/usePaging.d.ts +1 -1
  19. package/dist/hooks/usePaging.js +1 -1
  20. package/dist/hooks/usePreset.d.ts +1 -1
  21. package/dist/hooks/usePreset.js +35 -35
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +1 -1
  24. package/dist/lib/context/EditorContext.d.ts +10 -0
  25. package/dist/lib/context/EditorContext.js +4 -2
  26. package/dist/lib/hooks/useEditorHeadless.d.ts +18 -2
  27. package/dist/lib/hooks/useEditorHeadless.js +142 -63
  28. package/dist/utils/adjustment.d.ts +2 -1
  29. package/dist/utils/adjustment.js +16 -0
  30. package/dist/utils/imageLoader.d.ts +11 -0
  31. package/dist/utils/imageLoader.js +53 -0
  32. package/package.json +1 -1
  33. package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.d.ts +0 -17
  34. package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.js +0 -14
  35. package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.d.ts +0 -8
  36. package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.js +0 -30
  37. package/dist/components/editor/HImageEditorPage.d.ts +0 -1
  38. package/dist/components/editor/HImageEditorPage.js +0 -187
  39. package/dist/hooks/__tests__/useGallerySwipe.test.d.ts +0 -0
  40. package/dist/hooks/__tests__/useGallerySwipe.test.js +0 -619
  41. package/dist/hooks/editor/useHonchoEditor.d.ts +0 -203
  42. package/dist/hooks/editor/useHonchoEditor.js +0 -716
  43. package/dist/hooks/useAdjustmentHistory.demo.d.ts +0 -8
  44. package/dist/hooks/useAdjustmentHistory.demo.js +0 -106
  45. package/dist/hooks/useAdjustmentHistory.example.d.ts +0 -38
  46. package/dist/hooks/useAdjustmentHistory.example.js +0 -182
  47. package/dist/hooks/useAdjustmentHistory.syncDemo.d.ts +0 -8
  48. package/dist/hooks/useAdjustmentHistory.syncDemo.js +0 -180
  49. package/dist/hooks/useGallerySwipe.example.d.ts +0 -24
  50. package/dist/hooks/useGallerySwipe.example.js +0 -184
@@ -0,0 +1,354 @@
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);
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;
@@ -0,0 +1,2 @@
1
+ export { HonchoEditorBulkDemo } from './HonchoEditorBulkDemo';
2
+ export { HonchoEditorSingleCleanDemo } from './HonchoEditorSingleCleanDemo';
@@ -0,0 +1,2 @@
1
+ export { HonchoEditorBulkDemo } from './HonchoEditorBulkDemo';
2
+ export { HonchoEditorSingleCleanDemo } from './HonchoEditorSingleCleanDemo';
@@ -100,4 +100,75 @@ export interface ResponseGalleryPaging {
100
100
  next_page: number;
101
101
  sum_of_image?: number;
102
102
  }
103
+ export interface EditorHistoryEntry {
104
+ id: string;
105
+ gallery_id: string;
106
+ event_id: string;
107
+ task_id: string;
108
+ editor_config: EditorConfig;
109
+ log: Log;
110
+ }
111
+ export interface CreateEditorTaskRequest {
112
+ gallery_id: string;
113
+ task_id: string;
114
+ color_adjustment: ColorAdjustment;
115
+ replace_from?: string;
116
+ }
117
+ export interface GetHistoryResponse {
118
+ current_task_id: string;
119
+ history: EditorHistoryEntry[];
120
+ }
121
+ export interface GetGalleryUpdateTimestampResponse {
122
+ gallery: string[];
123
+ }
124
+ export interface AdjustmentState {
125
+ tempScore: number;
126
+ tintScore: number;
127
+ vibranceScore: number;
128
+ saturationScore: number;
129
+ exposureScore: number;
130
+ highlightsScore: number;
131
+ shadowsScore: number;
132
+ whitesScore: number;
133
+ blacksScore: number;
134
+ contrastScore: number;
135
+ clarityScore: number;
136
+ sharpnessScore: number;
137
+ }
138
+ export interface Preset {
139
+ id: string;
140
+ name: string;
141
+ is_default: boolean;
142
+ temperature: number;
143
+ tint: number;
144
+ saturation: number;
145
+ vibrance: number;
146
+ exposure: number;
147
+ contrast: number;
148
+ highlights: number;
149
+ shadows: number;
150
+ whites: number;
151
+ blacks: number;
152
+ clarity: number;
153
+ sharpness: number;
154
+ }
155
+ export interface Controller {
156
+ onGetImage(firebaseUid: string, imageID: string): Promise<Gallery>;
157
+ getImageList(firebaseUid: string, eventId: string, page: number): Promise<ResponseGalleryPaging>;
158
+ syncConfig(firebaseUid: string): Promise<void>;
159
+ handleBack(firebaseUid: string, imageID: string): void;
160
+ getPresets(firebaseUid: string): Promise<Preset[]>;
161
+ createPreset(firebaseUid: string, name: string, settings: AdjustmentState): Promise<void>;
162
+ deletePreset(firebaseUid: string, presetId: string): Promise<void>;
163
+ updatePreset(firebaseUid: string, data: Preset): Promise<void>;
164
+ createEditorConfig(firebaseUid: string, payload: CreateEditorTaskRequest): Promise<void>;
165
+ getEditorHistory(firebaseUid: string, imageID: string): Promise<GetHistoryResponse>;
166
+ getGalleryUpdateTimestamp(firebaseUid: string, eventID: string): Promise<GetGalleryUpdateTimestampResponse>;
167
+ setHistoryIndex(firebaseUid: string, imageID: string, taskID: string): Promise<void>;
168
+ }
169
+ export interface ImageItem {
170
+ id: string;
171
+ url: string;
172
+ file: File;
173
+ }
103
174
  export {};
@@ -1,6 +1,5 @@
1
1
  import { SelectChangeEvent } from "@mui/material";
2
- import { AdjustmentState, Controller, Preset } from './useHonchoEditor';
3
- import { Gallery, ResponseGalleryPaging } from '../../hooks/editor/type';
2
+ import { Controller, Preset } from './type';
4
3
  import { AdjustmentValues } from "../../lib/editor/honcho-editor";
5
4
  export interface PhotoData {
6
5
  key: string;
@@ -12,15 +11,6 @@ export interface PhotoData {
12
11
  isSelected: boolean;
13
12
  adjustments?: Partial<AdjustmentValues>;
14
13
  }
15
- export interface ControllerBulk {
16
- onGetImage(firebaseUid: string, imageID: string): Promise<Gallery>;
17
- getImageList(firebaseUid: string, eventID: string, page: number): Promise<ResponseGalleryPaging>;
18
- syncConfig(firebaseUid: string): Promise<void>;
19
- handleBack(firebaseUid: string, lastImageID: string): void;
20
- getPresets(firebaseUid: string): Promise<Preset[]>;
21
- createPreset(firebaseUid: string, name: string, settings: AdjustmentState): Promise<Preset>;
22
- deletePreset(firebaseUid: string, presetId: string): Promise<void>;
23
- }
24
14
  export declare function useHonchoEditorBulk(controller: Controller, eventID: string, firebaseUid: string): {
25
15
  imageData: PhotoData[];
26
16
  isLoading: boolean;
@@ -28,11 +18,15 @@ export declare function useHonchoEditorBulk(controller: Controller, eventID: str
28
18
  selectedIds: string[];
29
19
  hasMore: boolean;
30
20
  handleBackCallbackBulk: () => void;
21
+ presets: Preset[];
31
22
  selectedBulkPreset: string;
23
+ activePreset: Preset | null;
24
+ handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
25
+ handleOpenPresetModal: () => void;
26
+ presetActions: import("../usePreset").PresetActions;
32
27
  handleToggleImageSelection: (imageId: string) => void;
33
28
  handleLoadMore: () => Promise<void>;
34
29
  handleRefresh: () => Promise<void>;
35
- handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
36
30
  handleBulkTempDecreaseMax: () => void;
37
31
  handleBulkTempDecrease: () => void;
38
32
  handleBulkTempIncrease: () => void;
@@ -81,4 +75,8 @@ export declare function useHonchoEditorBulk(controller: Controller, eventID: str
81
75
  handleBulkSharpnessDecrease: () => void;
82
76
  handleBulkSharpnessIncrease: () => void;
83
77
  handleBulkSharpnessIncreaseMax: () => void;
78
+ handleUndo: () => void;
79
+ handleRedo: () => void;
80
+ handleReset: (imageIds?: string[]) => void;
81
+ historyInfo: import("../useAdjustmentHistoryBatch").BatchHistoryInfo;
84
82
  };