@yogiswara/honcho-editor-ui 2.5.9 → 2.6.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.
- package/dist/components/editor/HBulkPreset.js +12 -2
- package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +3 -0
- package/dist/hooks/demo/HonchoEditorBulkDemo.js +228 -0
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +3 -0
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +354 -0
- package/dist/hooks/demo/index.d.ts +2 -0
- package/dist/hooks/demo/index.js +2 -0
- package/dist/hooks/editor/type.d.ts +71 -0
- package/dist/hooks/editor/useHonchoEditorBulk.d.ts +12 -12
- package/dist/hooks/editor/useHonchoEditorBulk.js +155 -42
- package/dist/hooks/editor/useHonchoEditorSingle.d.ts +43 -0
- package/dist/hooks/editor/useHonchoEditorSingle.js +158 -0
- package/dist/hooks/useAdjustmentHistory.d.ts +9 -5
- package/dist/hooks/useAdjustmentHistory.js +187 -31
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +18 -1
- package/dist/hooks/useAdjustmentHistoryBatch.js +627 -201
- package/dist/hooks/useGallerySwipe.d.ts +1 -1
- package/dist/hooks/usePaging.d.ts +89 -0
- package/dist/hooks/usePaging.js +211 -0
- package/dist/hooks/usePreset.d.ts +1 -1
- package/dist/hooks/usePreset.js +35 -35
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -1
- package/dist/lib/context/EditorContext.d.ts +10 -0
- package/dist/lib/context/EditorContext.js +4 -2
- package/dist/lib/hooks/useEditorHeadless.d.ts +18 -2
- package/dist/lib/hooks/useEditorHeadless.js +142 -63
- package/dist/utils/adjustment.d.ts +2 -1
- package/dist/utils/adjustment.js +16 -0
- package/dist/utils/imageLoader.d.ts +11 -0
- package/dist/utils/imageLoader.js +53 -0
- package/package.json +1 -1
- package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.d.ts +0 -17
- package/dist/components/editor/GalleryAlbum/SimplifiedAlbumGallery.js +0 -14
- package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.d.ts +0 -8
- package/dist/components/editor/GalleryAlbum/SimplifiedImageItem.js +0 -30
- package/dist/components/editor/HImageEditorPage.d.ts +0 -1
- package/dist/components/editor/HImageEditorPage.js +0 -187
- package/dist/hooks/__tests__/useGallerySwipe.test.d.ts +0 -0
- package/dist/hooks/__tests__/useGallerySwipe.test.js +0 -619
- package/dist/hooks/editor/useHonchoEditor.d.ts +0 -203
- package/dist/hooks/editor/useHonchoEditor.js +0 -716
- package/dist/hooks/useAdjustmentHistory.demo.d.ts +0 -8
- package/dist/hooks/useAdjustmentHistory.demo.js +0 -106
- package/dist/hooks/useAdjustmentHistory.example.d.ts +0 -38
- package/dist/hooks/useAdjustmentHistory.example.js +0 -182
- package/dist/hooks/useAdjustmentHistory.syncDemo.d.ts +0 -8
- package/dist/hooks/useAdjustmentHistory.syncDemo.js +0 -180
- package/dist/hooks/useGallerySwipe.example.d.ts +0 -24
- 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;
|
|
@@ -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 {
|
|
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,9 +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;
|
|
32
|
-
|
|
23
|
+
activePreset: Preset | null;
|
|
33
24
|
handleSelectBulkPreset: (event: SelectChangeEvent<string>) => void;
|
|
25
|
+
handleOpenPresetModal: () => void;
|
|
26
|
+
presetActions: import("../usePreset").PresetActions;
|
|
27
|
+
handleToggleImageSelection: (imageId: string) => void;
|
|
28
|
+
handleLoadMore: () => Promise<void>;
|
|
29
|
+
handleRefresh: () => Promise<void>;
|
|
34
30
|
handleBulkTempDecreaseMax: () => void;
|
|
35
31
|
handleBulkTempDecrease: () => void;
|
|
36
32
|
handleBulkTempIncrease: () => void;
|
|
@@ -79,4 +75,8 @@ export declare function useHonchoEditorBulk(controller: Controller, eventID: str
|
|
|
79
75
|
handleBulkSharpnessDecrease: () => void;
|
|
80
76
|
handleBulkSharpnessIncrease: () => void;
|
|
81
77
|
handleBulkSharpnessIncreaseMax: () => void;
|
|
78
|
+
handleUndo: () => void;
|
|
79
|
+
handleRedo: () => void;
|
|
80
|
+
handleReset: (imageIds?: string[]) => void;
|
|
81
|
+
historyInfo: import("../useAdjustmentHistoryBatch").BatchHistoryInfo;
|
|
82
82
|
};
|