@yogiswara/honcho-editor-ui 2.5.10 → 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 +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
@@ -1,4 +1,4 @@
1
- import { Controller } from "./editor/useHonchoEditor";
1
+ import { Controller } from "./editor/type";
2
2
  import { Gallery } from "./editor/type";
3
3
  /**
4
4
  * Return type for the useGallerySwipe hook
@@ -1,5 +1,5 @@
1
1
  import { Gallery } from './editor/type';
2
- import { Controller } from "./editor/useHonchoEditor";
2
+ import { Controller } from "./editor/type";
3
3
  /**
4
4
  * Configuration options for the paging hook
5
5
  */
@@ -72,7 +72,7 @@ export function usePaging(controller, firebaseUid, eventId, options = {}) {
72
72
  }, [memoizedOptions.devWarnings]);
73
73
  // Helper function to handle errors
74
74
  const handleError = useCallback((operation, error) => {
75
- const errorMessage = `Failed to ${operation}: ${error?.message || error}`;
75
+ const errorMessage = `Failed to ${operation}: ${error instanceof Error ? error.message : String(error)}`;
76
76
  setError(errorMessage);
77
77
  debugLog(`Error in ${operation}`, error);
78
78
  }, [debugLog]);
@@ -1,4 +1,4 @@
1
- import { Controller, Preset, AdjustmentState } from './editor/useHonchoEditor';
1
+ import { Controller, Preset, AdjustmentState } from './editor/type';
2
2
  /**
3
3
  * Configuration options for the preset hook
4
4
  */
@@ -42,25 +42,27 @@ export function usePreset(controller, firebaseUid, options = {}) {
42
42
  const [isLoading, setIsLoading] = useState(false);
43
43
  const [error, setError] = useState(null);
44
44
  const [isInitialized, setIsInitialized] = useState(false);
45
- // Track controller and firebaseUid changes with stable refs
46
- const controllerRef = useRef(controller);
47
- const firebaseUidRef = useRef(firebaseUid);
48
- // Only update refs when values actually change
49
- if (controllerRef.current !== controller) {
50
- controllerRef.current = controller;
51
- }
52
- if (firebaseUidRef.current !== firebaseUid) {
53
- firebaseUidRef.current = firebaseUid;
54
- }
55
45
  // Helper function to log debug messages - memoized to prevent re-renders
56
46
  const debugLog = useCallback((message, data) => {
57
47
  if (memoizedOptions.devWarnings) {
58
48
  console.log(`[usePreset] ${message}`, data || '');
59
49
  }
60
50
  }, [memoizedOptions.devWarnings]);
51
+ // Stable references for controller and firebaseUid to detect actual changes
52
+ const stableControllerRef = useRef(controller);
53
+ const stableFirebaseUidRef = useRef(firebaseUid);
54
+ const prevStableControllerRef = useRef(controller);
55
+ const prevStableFirebaseUidRef = useRef(firebaseUid);
56
+ // Update refs only when values actually change (by reference)
57
+ if (stableControllerRef.current !== controller) {
58
+ stableControllerRef.current = controller;
59
+ }
60
+ if (stableFirebaseUidRef.current !== firebaseUid) {
61
+ stableFirebaseUidRef.current = firebaseUid;
62
+ }
61
63
  // Helper function to handle errors
62
64
  const handleError = useCallback((operation, error) => {
63
- const errorMessage = `Failed to ${operation}: ${error?.message || error}`;
65
+ const errorMessage = `Failed to ${operation}: ${error instanceof Error ? error.message : String(error)}`;
64
66
  setError(errorMessage);
65
67
  debugLog(`Error in ${operation}`, error);
66
68
  return false;
@@ -68,50 +70,44 @@ export function usePreset(controller, firebaseUid, options = {}) {
68
70
  // Load presets from backend
69
71
  const load = useCallback(async () => {
70
72
  console.log("Load Presets Get Function Called");
71
- if (!controllerRef.current || !firebaseUidRef.current) {
73
+ if (!stableControllerRef.current || !stableFirebaseUidRef.current) {
72
74
  debugLog('Load skipped: missing controller or firebaseUid');
73
75
  return;
74
76
  }
75
77
  setIsLoading(true);
76
78
  setError(null);
77
- console.log('before GOINT to load 2.5. STATE UPDATE: setPresets is being called with:', presets);
78
79
  try {
79
80
  debugLog('Loading presets from backend...');
80
- const loadedPresets = await controllerRef.current.getPresets(firebaseUidRef.current);
81
- console.log('✅ 3. STATE UPDATE: setPresets is being called with:', loadedPresets);
81
+ const loadedPresets = await stableControllerRef.current.getPresets(stableFirebaseUidRef.current);
82
82
  setPresets(loadedPresets);
83
- console.log('✅ 4. STATE UPDATE: setIsInitialized is being called with:', true);
84
- console.log('presets thats called:', presets);
85
83
  setIsInitialized(true);
86
84
  debugLog('Presets loaded successfully', { count: loadedPresets.length });
87
85
  }
88
86
  catch (err) {
89
87
  handleError('load presets', err);
90
- console.log('4. catch ERROR!');
91
88
  setPresets([]); // Clear presets on error
92
89
  }
93
90
  finally {
94
- console.log('5. STATE UPDATE: setIsLoading is being called with:', false);
95
91
  setIsLoading(false);
96
92
  }
97
93
  }, [debugLog, handleError]);
98
94
  // Fire-and-forget version of load for internal use
99
95
  const loadInBackground = useCallback(() => {
100
- if (!controllerRef.current || !firebaseUidRef.current) {
96
+ if (!stableControllerRef.current || !stableFirebaseUidRef.current) {
101
97
  debugLog('Background load skipped: missing controller or firebaseUid');
102
98
  return;
103
99
  }
104
100
  debugLog('Background loading presets...');
105
101
  // Don't set loading state for background operations
106
- controllerRef.current.getPresets(firebaseUidRef.current)
107
- .then(loadedPresets => {
102
+ stableControllerRef.current.getPresets(stableFirebaseUidRef.current)
103
+ .then((loadedPresets) => {
108
104
  setPresets(loadedPresets);
109
105
  if (!isInitialized) {
110
106
  setIsInitialized(true);
111
107
  }
112
108
  debugLog('Background presets loaded successfully', { count: loadedPresets.length });
113
109
  })
114
- .catch(err => {
110
+ .catch((err) => {
115
111
  debugLog('Background load failed:', err);
116
112
  // Don't set error state for background operations
117
113
  });
@@ -119,7 +115,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
119
115
  // Create a new preset
120
116
  const create = useCallback(async (name, settings) => {
121
117
  console.log("Create Preset Get Function Called");
122
- if (!controllerRef.current || !firebaseUidRef.current) {
118
+ if (!stableControllerRef.current || !stableFirebaseUidRef.current) {
123
119
  debugLog('Create skipped: missing controller or firebaseUid');
124
120
  return null;
125
121
  }
@@ -137,7 +133,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
137
133
  try {
138
134
  debugLog('Creating preset...', { name, settings });
139
135
  // Fire the create request but don't wait for preset data in response
140
- await controllerRef.current.createPreset(firebaseUidRef.current, name, settings);
136
+ await stableControllerRef.current.createPreset(stableFirebaseUidRef.current, name, settings);
141
137
  debugLog('Preset creation request completed');
142
138
  const newPreset = {
143
139
  id: `temp-${Date.now()}`, // Use a temporary ID
@@ -178,7 +174,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
178
174
  }, [presets, debugLog, handleError, loadInBackground]);
179
175
  // Rename an existing preset
180
176
  const rename = useCallback(async (presetId, newName) => {
181
- if (!controllerRef.current || !firebaseUidRef.current) {
177
+ if (!stableControllerRef.current || !stableFirebaseUidRef.current) {
182
178
  debugLog('Rename skipped: missing controller or firebaseUid');
183
179
  return false;
184
180
  }
@@ -201,7 +197,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
201
197
  try {
202
198
  debugLog('Renaming preset...', { presetId, oldName: existingPreset.name, newName });
203
199
  const updatedPreset = { ...existingPreset, name: newName };
204
- await controllerRef.current.updatePreset(firebaseUidRef.current, updatedPreset);
200
+ await stableControllerRef.current.updatePreset(stableFirebaseUidRef.current, updatedPreset);
205
201
  // Update local state
206
202
  setPresets(prev => prev.map(p => p.id === presetId ? updatedPreset : p));
207
203
  debugLog('Preset renamed successfully');
@@ -218,7 +214,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
218
214
  // Delete a preset
219
215
  const deletePreset = useCallback(async (presetId) => {
220
216
  console.log("Delete Presets Get Function Called");
221
- if (!controllerRef.current || !firebaseUidRef.current) {
217
+ if (!stableControllerRef.current || !stableFirebaseUidRef.current) {
222
218
  debugLog('Delete skipped: missing controller or firebaseUid');
223
219
  return false;
224
220
  }
@@ -231,7 +227,7 @@ export function usePreset(controller, firebaseUid, options = {}) {
231
227
  setError(null);
232
228
  try {
233
229
  debugLog('Deleting preset...', { presetId, name: existingPreset.name });
234
- await controllerRef.current.deletePreset(firebaseUidRef.current, presetId);
230
+ await stableControllerRef.current.deletePreset(stableFirebaseUidRef.current, presetId);
235
231
  // Remove from local state
236
232
  setPresets(prev => prev.filter(p => p.id !== presetId));
237
233
  debugLog('Preset deleted successfully');
@@ -294,20 +290,24 @@ export function usePreset(controller, firebaseUid, options = {}) {
294
290
  }, [presets, debugLog]);
295
291
  // Auto-load presets on initialization - stable dependencies
296
292
  useEffect(() => {
297
- if (memoizedOptions.autoLoad && controller && firebaseUid && !isInitialized) {
293
+ if (memoizedOptions.autoLoad && stableControllerRef.current && stableFirebaseUidRef.current && !isInitialized) {
298
294
  debugLog('Auto-loading presets...');
299
295
  load();
300
296
  }
301
- }, [memoizedOptions.autoLoad, controller, firebaseUid, isInitialized, load, debugLog]);
302
- // Clear state when controller or firebaseUid changes
297
+ }, [memoizedOptions.autoLoad, isInitialized, load]);
298
+ // Clear state when controller or firebaseUid changes - but only when they actually change
303
299
  useEffect(() => {
304
- if (isInitialized) {
300
+ const controllerChanged = prevStableControllerRef.current !== stableControllerRef.current;
301
+ const firebaseUidChanged = prevStableFirebaseUidRef.current !== stableFirebaseUidRef.current;
302
+ if ((controllerChanged || firebaseUidChanged) && isInitialized) {
305
303
  debugLog('Controller or firebaseUid changed, clearing state');
306
- // setPresets([]);
307
304
  setError(null);
308
305
  setIsInitialized(false);
306
+ // Don't clear presets to avoid flicker - they'll be reloaded
309
307
  }
310
- }, [controller, firebaseUid, isInitialized, debugLog]);
308
+ prevStableControllerRef.current = stableControllerRef.current;
309
+ prevStableFirebaseUidRef.current = stableFirebaseUidRef.current;
310
+ }, [stableControllerRef.current, stableFirebaseUidRef.current, isInitialized]);
311
311
  // Preset info object - memoized to prevent re-renders
312
312
  const info = useMemo(() => ({
313
313
  isLoading,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { useHonchoEditor } from './hooks/editor/useHonchoEditor';
2
- export type { Controller, AdjustmentState, Preset, ImageItem, } from './hooks/editor/useHonchoEditor';
1
+ export { useHonchoEditorSingle } from './hooks/editor/useHonchoEditorSingle';
3
2
  export { useHonchoEditorBulk } from './hooks/editor/useHonchoEditorBulk';
4
- export type { PhotoData, ControllerBulk } from './hooks/editor/useHonchoEditorBulk';
3
+ export type { Controller, AdjustmentState, Preset, ImageItem, } from './hooks/editor/type';
4
+ export type { PhotoData } from './hooks/editor/useHonchoEditorBulk';
5
5
  export type { Gallery, Content } from './hooks/editor/type';
6
6
  export { default as HHeaderEditor } from './components/editor/HHeaderEditor';
7
7
  export { default as HFooter } from './components/editor/HFooter';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { useHonchoEditor } from './hooks/editor/useHonchoEditor';
1
+ export { useHonchoEditorSingle } from './hooks/editor/useHonchoEditorSingle';
2
2
  export { useHonchoEditorBulk } from './hooks/editor/useHonchoEditorBulk';
3
3
  export { default as HHeaderEditor } from './components/editor/HHeaderEditor';
4
4
  export { default as HFooter } from './components/editor/HFooter';
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { EditorProcessingService } from '../context/EditorProcessingService';
3
+ import HonchoEditor from "../editor/honcho-editor";
3
4
  interface EditorContextValue {
4
5
  isReady: boolean;
5
6
  error: Error | null;
@@ -9,9 +10,18 @@ interface EditorContextValue {
9
10
  isProcessing: boolean;
10
11
  hasProcessor: boolean;
11
12
  };
13
+ editor: HonchoEditor | null;
14
+ loadImageFromUrl: ((url: string) => Promise<{
15
+ width: number;
16
+ height: number;
17
+ }>) | null;
12
18
  }
13
19
  interface EditorProviderProps {
14
20
  children: React.ReactNode;
21
+ /** URL to the honcho-photo-editor.js script (defaults to '/honcho-photo-editor.js') */
22
+ scriptUrl?: string;
23
+ /** URL to the honcho-photo-editor.wasm file (defaults to '/honcho-photo-editor.wasm') */
24
+ wasmUrl?: string;
15
25
  }
16
26
  export declare const EditorProvider: React.FC<EditorProviderProps>;
17
27
  export declare const useEditorContext: () => EditorContextValue;
@@ -4,9 +4,9 @@ import { createContext, useContext, useEffect, useState } from 'react';
4
4
  import { useEditorHeadless } from '../hooks/useEditorHeadless';
5
5
  import { EditorProcessingService } from '../context/EditorProcessingService';
6
6
  const EditorContext = createContext(null);
7
- export const EditorProvider = ({ children }) => {
7
+ export const EditorProvider = ({ children, scriptUrl = '/honcho-photo-editor.js', wasmUrl = '/honcho-photo-editor.wasm' }) => {
8
8
  // Single editor instance for the entire app
9
- const { editor, isReady, error, processImage } = useEditorHeadless();
9
+ const { editor, isReady, error, processImage, loadImageFromUrl } = useEditorHeadless({ scriptUrl, wasmUrl });
10
10
  // Single processing service instance
11
11
  const [processingService] = useState(() => new EditorProcessingService());
12
12
  const [queueStatus, setQueueStatus] = useState(processingService.getQueueStatus());
@@ -45,6 +45,8 @@ export const EditorProvider = ({ children }) => {
45
45
  error,
46
46
  processingService,
47
47
  queueStatus,
48
+ editor: editor,
49
+ loadImageFromUrl
48
50
  };
49
51
  return (_jsx(EditorContext.Provider, { value: contextValue, children: children }));
50
52
  };
@@ -1,4 +1,12 @@
1
- import { AdjustmentValues, HonchoEditor } from '../editor/honcho-editor';
1
+ import type { AdjustmentValues, HonchoEditor } from '../editor/honcho-editor';
2
+ declare global {
3
+ interface Window {
4
+ HonchoEditor: new () => any;
5
+ HonchoEditorUtils: {
6
+ imageDataToBlob: (imageData: ImageData, format?: string, quality?: number) => Promise<Blob>;
7
+ };
8
+ }
9
+ }
2
10
  interface EditorTask {
3
11
  id: string;
4
12
  path: string;
@@ -9,10 +17,18 @@ interface EditorResponse {
9
17
  id: string;
10
18
  path: string;
11
19
  }
12
- export declare function useEditorHeadless(): {
20
+ interface UseEditorHeadlessOptions {
21
+ scriptUrl?: string;
22
+ wasmUrl?: string;
23
+ }
24
+ export declare function useEditorHeadless(options?: UseEditorHeadlessOptions): {
13
25
  editor: HonchoEditor | null;
14
26
  isReady: boolean;
15
27
  error: Error | null;
16
28
  processImage: (task: EditorTask) => Promise<EditorResponse>;
29
+ loadImageFromUrl: (url: string) => Promise<{
30
+ width: number;
31
+ height: number;
32
+ }>;
17
33
  };
18
34
  export {};
@@ -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>;