@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.
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 +12 -12
  10. package/dist/hooks/editor/useHonchoEditorBulk.js +155 -42
  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 +89 -0
  19. package/dist/hooks/usePaging.js +211 -0
  20. package/dist/hooks/usePreset.d.ts +1 -1
  21. package/dist/hooks/usePreset.js +35 -35
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +3 -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
@@ -1,41 +1,110 @@
1
1
  'use client';
2
2
  import { useState, useEffect, useRef, useCallback } from 'react';
3
- import { HonchoEditor, HonchoEditorUtils } from '../editor/honcho-editor';
4
- export function useEditorHeadless() {
3
+ import { loadImageAsFile } from '../../utils/imageLoader';
4
+ export function useEditorHeadless(options = {}) {
5
+ const { scriptUrl = '/honcho-photo-editor.js', wasmUrl = '/honcho-photo-editor.wasm' } = options;
5
6
  const editorRef = useRef(null);
6
7
  const [isReady, setIsReady] = useState(false);
7
8
  const [error, setError] = useState(null);
8
9
  const [isScriptLoaded, setIsScriptLoaded] = useState(false);
9
10
  // Load script dynamically without component
10
11
  useEffect(() => {
11
- const loadScript = () => {
12
- // Check if script is already loaded
13
- if (document.querySelector('script[src="/honcho-photo-editor.js"]')) {
12
+ const loadScripts = async () => {
13
+ // Check if HonchoEditor is already available
14
+ if (window.HonchoEditor) {
15
+ console.debug('HonchoEditor already available globally');
14
16
  setIsScriptLoaded(true);
15
17
  return;
16
18
  }
17
- const script = document.createElement('script');
18
- script.src = '/honcho-photo-editor.js';
19
- script.async = true;
20
- script.onload = () => {
21
- console.debug('Honcho photo editor script loaded');
22
- setIsScriptLoaded(true);
19
+ // Load WASM module first
20
+ const loadWasmModule = () => {
21
+ return new Promise((resolve, reject) => {
22
+ // Check if WASM module is already loaded
23
+ if (document.querySelector(`script[src="${scriptUrl}"]`)) {
24
+ console.debug('WASM module script already exists');
25
+ resolve();
26
+ return;
27
+ }
28
+ console.debug(`Loading WASM module from: ${scriptUrl}`);
29
+ const wasmScript = document.createElement('script');
30
+ wasmScript.src = scriptUrl;
31
+ wasmScript.async = true;
32
+ wasmScript.onload = () => {
33
+ console.debug('WASM module script loaded');
34
+ resolve();
35
+ };
36
+ wasmScript.onerror = () => {
37
+ console.error(`Failed to load WASM module from: ${scriptUrl}`);
38
+ reject(new Error('Failed to load WASM module'));
39
+ };
40
+ document.head.appendChild(wasmScript);
41
+ });
23
42
  };
24
- script.onerror = () => {
25
- console.error('Failed to load honcho photo editor script');
26
- setError(new Error('Failed to load editor script'));
43
+ // Load wrapper script that creates HonchoEditor constructor
44
+ const loadWrapperScript = () => {
45
+ return new Promise((resolve, reject) => {
46
+ const wrapperUrl = '/honcho-editor.js';
47
+ // Check if wrapper script is already loaded
48
+ if (document.querySelector(`script[src="${wrapperUrl}"]`)) {
49
+ console.debug('Wrapper script already exists');
50
+ resolve();
51
+ return;
52
+ }
53
+ console.debug(`Loading HonchoEditor wrapper from: ${wrapperUrl}`);
54
+ const wrapperScript = document.createElement('script');
55
+ wrapperScript.src = wrapperUrl;
56
+ wrapperScript.async = true;
57
+ wrapperScript.onload = () => {
58
+ console.debug('Wrapper script loaded');
59
+ resolve();
60
+ };
61
+ wrapperScript.onerror = () => {
62
+ console.error(`Failed to load wrapper script from: ${wrapperUrl}`);
63
+ reject(new Error('Failed to load wrapper script'));
64
+ };
65
+ document.head.appendChild(wrapperScript);
66
+ });
27
67
  };
28
- document.head.appendChild(script);
68
+ try {
69
+ // Load WASM module first
70
+ await loadWasmModule();
71
+ // Then load the wrapper that creates the constructor
72
+ await loadWrapperScript();
73
+ // Wait for HonchoEditor constructor to be available
74
+ const waitForConstructor = () => {
75
+ return new Promise((resolve, reject) => {
76
+ const checkInterval = setInterval(() => {
77
+ if (window.HonchoEditor && typeof window.HonchoEditor === 'function') {
78
+ console.debug('HonchoEditor constructor now available');
79
+ clearInterval(checkInterval);
80
+ resolve();
81
+ }
82
+ }, 100);
83
+ // Timeout after 10 seconds
84
+ setTimeout(() => {
85
+ clearInterval(checkInterval);
86
+ if (!window.HonchoEditor) {
87
+ console.error('Timeout waiting for HonchoEditor constructor');
88
+ reject(new Error('Timeout waiting for HonchoEditor constructor'));
89
+ }
90
+ }, 10000);
91
+ });
92
+ };
93
+ await waitForConstructor();
94
+ setIsScriptLoaded(true);
95
+ }
96
+ catch (error) {
97
+ console.error('Failed to load HonchoEditor scripts:', error);
98
+ setError(error instanceof Error ? error : new Error(String(error)));
99
+ }
29
100
  };
30
- loadScript();
31
- // Cleanup script on unmount
101
+ loadScripts();
102
+ // Cleanup scripts on unmount (but be careful not to remove if other components need it)
32
103
  return () => {
33
- const script = document.querySelector('script[src="/honcho-photo-editor.js"]');
34
- if (script) {
35
- script.remove();
36
- }
104
+ // Don't remove the scripts on unmount as other components might need them
105
+ // The scripts should stay loaded for the lifetime of the app
37
106
  };
38
- }, []);
107
+ }, [scriptUrl]);
39
108
  // Initialize editor when script is loaded
40
109
  useEffect(() => {
41
110
  if (!isScriptLoaded)
@@ -43,10 +112,21 @@ export function useEditorHeadless() {
43
112
  const initialize = async () => {
44
113
  try {
45
114
  console.debug('Script loaded, initializing editor...');
115
+ // Double-check that HonchoEditor is available
116
+ if (!window.HonchoEditor) {
117
+ throw new Error('window.HonchoEditor is not available');
118
+ }
119
+ if (typeof window.HonchoEditor !== 'function') {
120
+ throw new Error(`window.HonchoEditor is not a constructor (type: ${typeof window.HonchoEditor})`);
121
+ }
46
122
  if (!editorRef.current) {
47
- editorRef.current = new HonchoEditor();
123
+ console.debug('Creating new HonchoEditor instance...');
124
+ editorRef.current = new window.HonchoEditor();
125
+ console.debug('HonchoEditor instance created successfully');
48
126
  }
127
+ console.debug('Initializing HonchoEditor...');
49
128
  await editorRef.current.initialize(false);
129
+ console.debug('HonchoEditor initialized successfully');
50
130
  setIsReady(true);
51
131
  }
52
132
  catch (e) {
@@ -56,39 +136,12 @@ export function useEditorHeadless() {
56
136
  };
57
137
  initialize();
58
138
  return () => {
59
- editorRef.current?.cleanup();
139
+ if (editorRef.current) {
140
+ console.debug('Cleaning up HonchoEditor...');
141
+ editorRef.current?.cleanup();
142
+ }
60
143
  };
61
144
  }, [isScriptLoaded]);
62
- // Helper function to load image as blob with fallback
63
- const loadImageAsBlob = async (url) => {
64
- try {
65
- // Try direct fetch first (faster, no server load)
66
- const response = await fetch(url, {
67
- mode: 'cors',
68
- credentials: 'omit'
69
- });
70
- if (!response.ok) {
71
- throw new Error(`Direct fetch failed: ${response.statusText}`);
72
- }
73
- return response.blob();
74
- }
75
- catch (error) {
76
- console.warn(`Direct fetch failed for ${url}, trying proxy fallback:`, error);
77
- // Fallback to proxy API if CORS or other fetch issues
78
- try {
79
- const proxyUrl = `/api/image?imageUrl=${encodeURIComponent(url)}`;
80
- const response = await fetch(proxyUrl);
81
- if (!response.ok) {
82
- throw new Error(`Proxy fetch failed: ${response.statusText}`);
83
- }
84
- return response.blob();
85
- }
86
- catch (proxyError) {
87
- console.error(`Both direct and proxy fetch failed for ${url}:`, proxyError);
88
- throw new Error(`Failed to load image: ${proxyError instanceof Error ? proxyError.message : 'Unknown error'}`);
89
- }
90
- }
91
- };
92
145
  // Process single image task
93
146
  const processImage = useCallback(async (task) => {
94
147
  if (!editorRef.current || !isReady) {
@@ -96,20 +149,17 @@ export function useEditorHeadless() {
96
149
  }
97
150
  try {
98
151
  console.debug(`Processing image: ${task.id}`);
99
- // Load original image as blob first
100
- const imageBlob = await loadImageAsBlob(task.path);
101
- // Convert blob to File for HonchoEditor
102
- const imageFile = new File([imageBlob], `image-${task.id}`, { type: imageBlob.type });
152
+ // Load original image as File using the new utility
153
+ const imageFile = await loadImageAsFile(task.path);
103
154
  // Load frame if provided
104
155
  let frameFile = null;
105
156
  if (task.frame) {
106
- const frameBlob = await loadImageAsBlob(task.frame);
107
- frameFile = new File([frameBlob], `frame-${task.id}`, { type: frameBlob.type });
157
+ frameFile = await loadImageAsFile(task.frame);
108
158
  }
109
159
  // Process image using HonchoEditor's one-shot method
110
160
  const processedImageData = await editorRef.current.processImageOneShot(imageFile, task.adjustments, frameFile);
111
161
  // Convert ImageData to Blob
112
- const processedBlob = await HonchoEditorUtils.imageDataToBlob(processedImageData);
162
+ const processedBlob = await window.HonchoEditorUtils.imageDataToBlob(processedImageData);
113
163
  // Create blob URL for processed result
114
164
  const blobUrl = URL.createObjectURL(processedBlob);
115
165
  return { id: task.id, path: blobUrl };
@@ -118,11 +168,40 @@ export function useEditorHeadless() {
118
168
  console.error(`Failed to process image ${task.id}:`, error);
119
169
  throw new Error(`Failed to process image: ${error instanceof Error ? error.message : 'Unknown error'}`);
120
170
  }
121
- }, [isReady, loadImageAsBlob]);
171
+ }, [isReady]);
172
+ // Helper function to load image from URL with CORS handling
173
+ const loadImageFromUrl = useCallback(async (url) => {
174
+ if (!editorRef.current || !isReady) {
175
+ throw new Error('Editor not ready');
176
+ }
177
+ try {
178
+ console.debug(`Loading image from URL: ${url}`);
179
+ // First try direct load with CORS
180
+ try {
181
+ const size = await editorRef.current.loadImageFromUrl(url);
182
+ console.debug('Image loaded successfully via direct URL');
183
+ return size;
184
+ }
185
+ catch (directError) {
186
+ console.warn('Direct URL load failed, trying blob approach:', directError);
187
+ // Fallback: Load as File using the new utility
188
+ const imageFile = await loadImageAsFile(url);
189
+ // Load via file method
190
+ const size = await editorRef.current.loadImageFromFile(imageFile);
191
+ console.debug('Image loaded successfully via file fallback');
192
+ return size;
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error(`Failed to load image from URL ${url}:`, error);
197
+ throw new Error(`Failed to load image: ${error instanceof Error ? error.message : 'Unknown error'}`);
198
+ }
199
+ }, [isReady]);
122
200
  return {
123
201
  editor: editorRef.current,
124
202
  isReady,
125
203
  error,
126
- processImage
204
+ processImage,
205
+ loadImageFromUrl
127
206
  };
128
207
  }
@@ -1,5 +1,6 @@
1
1
  import { ColorAdjustment } from "../hooks/editor/type";
2
- import { AdjustmentState } from "../hooks/editor/useHonchoEditor";
2
+ import { AdjustmentState } from "../hooks/editor/type";
3
3
  import { AdjustmentValues } from "../lib/editor/honcho-editor";
4
4
  export declare function mapAdjustmentStateToAdjustmentEditor(state: AdjustmentState): AdjustmentValues;
5
5
  export declare function mapColorAdjustmentToAdjustmentState(colors: ColorAdjustment): AdjustmentState;
6
+ export declare function mapAdjustmentStateToColorAdjustment(state: AdjustmentState): ColorAdjustment;
@@ -30,3 +30,19 @@ export function mapColorAdjustmentToAdjustmentState(colors) {
30
30
  sharpnessScore: colors.sharpness
31
31
  };
32
32
  }
33
+ export function mapAdjustmentStateToColorAdjustment(state) {
34
+ return {
35
+ temperature: state.tempScore,
36
+ tint: state.tintScore,
37
+ vibrance: state.vibranceScore,
38
+ saturation: state.saturationScore,
39
+ exposure: state.exposureScore,
40
+ contrast: state.contrastScore,
41
+ highlights: state.highlightsScore,
42
+ shadows: state.shadowsScore,
43
+ whites: state.whitesScore,
44
+ blacks: state.blacksScore,
45
+ clarity: state.clarityScore,
46
+ sharpness: state.sharpnessScore
47
+ };
48
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Image loading utilities with CORS handling and fallback mechanisms
3
+ */
4
+ /**
5
+ * Load image as blob with CORS handling and proxy fallback
6
+ */
7
+ export declare function loadImageAsBlob(url: string): Promise<Blob>;
8
+ /**
9
+ * Load image from URL and convert to File object with CORS handling and fallback mechanisms
10
+ */
11
+ export declare function loadImageAsFile(url: string): Promise<File>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Image loading utilities with CORS handling and fallback mechanisms
3
+ */
4
+ /**
5
+ * Load image as blob with CORS handling and proxy fallback
6
+ */
7
+ export async function loadImageAsBlob(url) {
8
+ try {
9
+ // Try direct fetch first (faster, no server load)
10
+ const response = await fetch(url, {
11
+ mode: 'cors',
12
+ credentials: 'omit'
13
+ });
14
+ if (!response.ok) {
15
+ throw new Error(`Direct fetch failed: ${response.statusText}`);
16
+ }
17
+ return response.blob();
18
+ }
19
+ catch (error) {
20
+ console.warn(`Direct fetch failed for ${url}, trying proxy fallback:`, error);
21
+ // Fallback to proxy API if CORS or other fetch issues
22
+ try {
23
+ const proxyUrl = `/api/image?imageUrl=${encodeURIComponent(url)}`;
24
+ const response = await fetch(proxyUrl);
25
+ if (!response.ok) {
26
+ throw new Error(`Proxy fetch failed: ${response.statusText}`);
27
+ }
28
+ return response.blob();
29
+ }
30
+ catch (proxyError) {
31
+ console.error(`Both direct and proxy fetch failed for ${url}:`, proxyError);
32
+ throw new Error(`Failed to load image: ${proxyError instanceof Error ? proxyError.message : 'Unknown error'}`);
33
+ }
34
+ }
35
+ }
36
+ /**
37
+ * Load image from URL and convert to File object with CORS handling and fallback mechanisms
38
+ */
39
+ export async function loadImageAsFile(url) {
40
+ try {
41
+ console.debug(`Loading image from URL: ${url}`);
42
+ // Load image as blob with CORS handling
43
+ const imageBlob = await loadImageAsBlob(url);
44
+ // Convert blob to File for HonchoEditor
45
+ const imageFile = new File([imageBlob], 'image', { type: imageBlob.type });
46
+ console.debug('Image loaded and converted to File successfully');
47
+ return imageFile;
48
+ }
49
+ catch (error) {
50
+ console.error(`Failed to load image from URL ${url}:`, error);
51
+ throw new Error(`Failed to load image: ${error instanceof Error ? error.message : 'Unknown error'}`);
52
+ }
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yogiswara/honcho-editor-ui",
3
- "version": "2.5.9",
3
+ "version": "2.6.0",
4
4
  "description": "A complete UI component library for the Honcho photo editor.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,17 +0,0 @@
1
- import type { Gallery } from "../../../hooks/editor/type";
2
- export interface PhotoData {
3
- key: string;
4
- src: string;
5
- width: number;
6
- height: number;
7
- alt: string;
8
- isSelected: boolean;
9
- originalData: Gallery;
10
- }
11
- interface SimplifiedAlbumGalleryProps {
12
- imageCollection: PhotoData[];
13
- onToggleSelect: (photo: PhotoData) => void;
14
- onPreview: (photo: PhotoData) => void;
15
- }
16
- export default function SimplifiedAlbumGallery({ imageCollection, onToggleSelect, onPreview }: SimplifiedAlbumGalleryProps): import("react/jsx-runtime").JSX.Element;
17
- export {};
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Box } from '@mui/material';
3
- import SimplifiedImageItem from './SimplifiedImageItem'; // Import the child component
4
- export default function SimplifiedAlbumGallery({ imageCollection, onToggleSelect, onPreview }) {
5
- return (_jsx(Box, { sx: {
6
- display: 'grid',
7
- gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
8
- gap: '16px',
9
- width: '100%',
10
- height: '100%',
11
- overflowY: 'auto',
12
- p: 1
13
- }, children: imageCollection.map(photo => (_jsx(SimplifiedImageItem, { photo: photo, onToggleSelect: onToggleSelect, onPreview: onPreview }, photo.key))) }));
14
- }
@@ -1,8 +0,0 @@
1
- import type { PhotoData } from './SimplifiedAlbumGallery';
2
- interface SimplifiedImageItemProps {
3
- photo: PhotoData;
4
- onToggleSelect: (photo: PhotoData) => void;
5
- onPreview: (photo: PhotoData) => void;
6
- }
7
- export default function SimplifiedImageItem({ photo, onToggleSelect, onPreview }: SimplifiedImageItemProps): import("react/jsx-runtime").JSX.Element;
8
- export {};
@@ -1,30 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { Box } from '@mui/material';
4
- import CheckCircleIcon from '@mui/icons-material/CheckCircle';
5
- import RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';
6
- export default function SimplifiedImageItem({ photo, onToggleSelect, onPreview }) {
7
- const [isHovered, setIsHovered] = useState(false);
8
- return (_jsxs(Box, { onClick: () => onPreview(photo), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), sx: {
9
- position: 'relative',
10
- cursor: 'pointer',
11
- overflow: 'hidden',
12
- borderRadius: '8px',
13
- '& img': {
14
- display: 'block',
15
- width: '100%',
16
- transition: 'transform 0.2s ease-in-out',
17
- transform: isHovered ? 'scale(1.05)' : 'scale(1)',
18
- },
19
- }, children: [_jsx("img", { src: photo.src, alt: photo.alt, style: { aspectRatio: `${photo.width} / ${photo.height}` } }), _jsx(Box, { onClick: (e) => {
20
- e.stopPropagation(); // Prevent onPreview from firing when clicking checkbox
21
- onToggleSelect(photo);
22
- }, sx: {
23
- position: 'absolute',
24
- top: '8px',
25
- left: '8px',
26
- color: 'white',
27
- opacity: photo.isSelected || isHovered ? 1 : 0,
28
- transition: 'opacity 0.2s ease-in-out',
29
- }, children: photo.isSelected ? (_jsx(CheckCircleIcon, { sx: { fontSize: '28px', background: 'rgba(0,0,0,0.3)', borderRadius: '50%' } })) : (_jsx(RadioButtonUncheckedIcon, { sx: { fontSize: '28px', background: 'rgba(0,0,0,0.3)', borderRadius: '50%' } })) })] }));
30
- }
@@ -1 +0,0 @@
1
- export default function HImageEditorPage(): import("react/jsx-runtime").JSX.Element;