@yogiswara/honcho-editor-ui 3.8.2 â 3.8.5
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/hooks/editor/useHonchoEditorSingle.d.ts +1 -1
- package/dist/hooks/editor/useHonchoEditorSingle.js +2 -2
- package/dist/hooks/useAdjustmentHistory.d.ts +1 -1
- package/dist/hooks/useAdjustmentHistory.js +12 -11
- package/dist/lib/worker/editor.worker.d.ts +7 -0
- package/dist/lib/worker/editor.worker.js +156 -0
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ export interface UseHonchoEditorSingleActions {
|
|
|
47
47
|
setBatchMode: (enabled: boolean) => void;
|
|
48
48
|
startBatchMode: () => void;
|
|
49
49
|
endBatchMode: () => void;
|
|
50
|
-
updateFromSocket: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment) => void;
|
|
50
|
+
updateFromSocket: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment, preset_id?: string) => void;
|
|
51
51
|
pushState: (state: AdjustmentState) => void;
|
|
52
52
|
undo: () => void;
|
|
53
53
|
redo: () => void;
|
|
@@ -166,8 +166,8 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
166
166
|
sharpness: adjustments.sharpnessScore,
|
|
167
167
|
};
|
|
168
168
|
}, [adjustmentHistory.currentState]);
|
|
169
|
-
const updateFromSocket = useCallback((imageId, adjustmentTaskId, adjustment) => {
|
|
170
|
-
adjustmentHistory.actions.updateFromBackend(imageId, adjustmentTaskId, adjustment);
|
|
169
|
+
const updateFromSocket = useCallback((imageId, adjustmentTaskId, adjustment, preset_id) => {
|
|
170
|
+
adjustmentHistory.actions.updateFromBackend(imageId, adjustmentTaskId, adjustment, preset_id);
|
|
171
171
|
}, [adjustmentHistory.actions.updateFromBackend]);
|
|
172
172
|
const actions = {
|
|
173
173
|
// Navigation
|
|
@@ -53,7 +53,7 @@ export interface HistoryActions {
|
|
|
53
53
|
/** Sync history from backend using getEditorHistory */
|
|
54
54
|
syncFromBackend: () => Promise<void>;
|
|
55
55
|
/** Update specific history entry from backend without changing current index */
|
|
56
|
-
updateFromBackend: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment) => void;
|
|
56
|
+
updateFromBackend: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment, preset_id?: string) => void;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Configuration actions for runtime adjustment
|
|
@@ -22,7 +22,7 @@ const convertAdjustmentStateToColorAdjustment = (adjustmentState) => {
|
|
|
22
22
|
/**
|
|
23
23
|
* Convert ColorAdjustment from backend to AdjustmentState format
|
|
24
24
|
*/
|
|
25
|
-
const convertColorAdjustmentToAdjustmentState = (colorAdjustment) => {
|
|
25
|
+
const convertColorAdjustmentToAdjustmentState = (colorAdjustment, preset_id) => {
|
|
26
26
|
return {
|
|
27
27
|
tempScore: colorAdjustment.temperature,
|
|
28
28
|
tintScore: colorAdjustment.tint,
|
|
@@ -36,6 +36,7 @@ const convertColorAdjustmentToAdjustmentState = (colorAdjustment) => {
|
|
|
36
36
|
blacksScore: colorAdjustment.blacks,
|
|
37
37
|
clarityScore: colorAdjustment.clarity,
|
|
38
38
|
sharpnessScore: Math.max(0, Math.min(100, colorAdjustment.sharpness)),
|
|
39
|
+
preset_id: preset_id,
|
|
39
40
|
};
|
|
40
41
|
};
|
|
41
42
|
/**
|
|
@@ -579,32 +580,32 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
579
580
|
}
|
|
580
581
|
}, [internalOptions]);
|
|
581
582
|
// Update specific history entry from backend without changing current index
|
|
582
|
-
const updateFromBackend = useCallback((imageId, adjustmentTaskId, adjustment) => {
|
|
583
|
+
const updateFromBackend = useCallback((imageId, adjustmentTaskId, adjustment, preset_id) => {
|
|
583
584
|
// Ignore updates if imageID doesn't match current image
|
|
584
585
|
if (imageId !== internalOptions.currentImageId) {
|
|
585
|
-
|
|
586
|
+
log.debug({ receivedImageId: imageId, currentImageId: internalOptions.currentImageId }, 'đĢ updateFromBackend: Ignoring update for different image');
|
|
586
587
|
return;
|
|
587
588
|
}
|
|
588
589
|
// Ignore updates during batch mode
|
|
589
590
|
if (batchModeRef.current) {
|
|
590
|
-
|
|
591
|
+
log.debug({ adjustmentTaskId }, 'đĢ updateFromBackend: Ignoring update during batch mode for taskId');
|
|
591
592
|
return;
|
|
592
593
|
}
|
|
593
|
-
|
|
594
|
+
log.info({ adjustmentTaskId, imageId, preset_id }, 'đ updateFromBackend: Received update');
|
|
594
595
|
// Convert ColorAdjustment to AdjustmentState
|
|
595
|
-
const updatedState = convertColorAdjustmentToAdjustmentState(adjustment);
|
|
596
|
+
const updatedState = convertColorAdjustmentToAdjustmentState(adjustment, preset_id);
|
|
596
597
|
setHistory(prevHistory => {
|
|
597
598
|
// Find the index of the task to update
|
|
598
599
|
const taskIndex = prevHistory.findIndex(entry => entry.taskId === adjustmentTaskId);
|
|
599
600
|
if (taskIndex === -1) {
|
|
600
601
|
// Task doesn't exist in current history, we need to sync from backend
|
|
601
602
|
// to get the complete history with proper chronological order
|
|
602
|
-
|
|
603
|
+
log.info({ adjustmentTaskId }, 'đ updateFromBackend: TaskId not found in current history, triggering sync from backend');
|
|
603
604
|
// Don't modify history here, instead trigger a sync
|
|
604
605
|
// Use setTimeout to avoid calling async function inside setState
|
|
605
606
|
setTimeout(() => {
|
|
606
607
|
syncFromBackend().catch(error => {
|
|
607
|
-
|
|
608
|
+
log.error({ error, adjustmentTaskId }, 'â Failed to sync history after unknown taskId');
|
|
608
609
|
});
|
|
609
610
|
}, 0);
|
|
610
611
|
// Return unchanged history for now
|
|
@@ -613,10 +614,10 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
613
614
|
// Task exists, check if it's the current index
|
|
614
615
|
const isCurrentIndex = taskIndex === currentIndexRef.current;
|
|
615
616
|
if (isCurrentIndex) {
|
|
616
|
-
|
|
617
|
+
log.debug({ taskIndex }, 'đ updateFromBackend: Updating current index but not changing current state');
|
|
617
618
|
}
|
|
618
619
|
else {
|
|
619
|
-
|
|
620
|
+
log.debug({ taskIndex, currentIndex: currentIndexRef.current }, 'đ updateFromBackend: Updating history entry far behind current');
|
|
620
621
|
}
|
|
621
622
|
// Update the specific history entry
|
|
622
623
|
const updatedHistory = [...prevHistory];
|
|
@@ -626,7 +627,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
626
627
|
};
|
|
627
628
|
return updatedHistory;
|
|
628
629
|
});
|
|
629
|
-
|
|
630
|
+
log.info({ adjustmentTaskId }, 'â
updateFromBackend: Successfully updated taskId');
|
|
630
631
|
}, [internalOptions]);
|
|
631
632
|
// History info object
|
|
632
633
|
const historyInfo = useMemo(() => ({
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STEP 2: Web Worker for Image Processing
|
|
3
|
+
*
|
|
4
|
+
* This worker runs the HonchoEditor in a separate thread to prevent
|
|
5
|
+
* UI freezing on iOS Safari. It uses OffscreenCanvas for processing.
|
|
6
|
+
*/
|
|
7
|
+
// Import the HonchoEditor class
|
|
8
|
+
// @ts-ignore - dynamic import for worker context
|
|
9
|
+
import { HonchoEditor } from '../honcho-editor.js';
|
|
10
|
+
// Create a single editor instance that will be reused
|
|
11
|
+
let editor = null;
|
|
12
|
+
let isInitialized = false;
|
|
13
|
+
let envConfig = {};
|
|
14
|
+
// Helper function to log messages from worker (visible in main thread console)
|
|
15
|
+
const log = (message, ...args) => {
|
|
16
|
+
console.log(`[Worker] ${message}`, ...args);
|
|
17
|
+
};
|
|
18
|
+
// Pre-configure Module BEFORE importing the script
|
|
19
|
+
// This ensures the locateFile function is available when the WASM loads
|
|
20
|
+
self.Module = {
|
|
21
|
+
locateFile: (path) => {
|
|
22
|
+
// Resolve WASM file path relative to the public folder
|
|
23
|
+
if (path.endsWith('.wasm')) {
|
|
24
|
+
log('WASM path resolved to:', '/honcho-photo-editor.wasm');
|
|
25
|
+
return '/honcho-photo-editor.wasm';
|
|
26
|
+
}
|
|
27
|
+
return path;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
log('Worker script loaded and ready');
|
|
31
|
+
// Handle incoming messages from the main thread
|
|
32
|
+
self.onmessage = async (event) => {
|
|
33
|
+
const { type, id, path, framePath, adjustments, env } = event.data;
|
|
34
|
+
log(`đĨ Received message: ${type}`, { id, path, framePath });
|
|
35
|
+
try {
|
|
36
|
+
if (type === 'INIT') {
|
|
37
|
+
log('đ§ Starting initialization...');
|
|
38
|
+
// Store environment variables
|
|
39
|
+
if (env) {
|
|
40
|
+
envConfig = env;
|
|
41
|
+
log('â Environment variables stored:', envConfig);
|
|
42
|
+
}
|
|
43
|
+
// Initialize the editor
|
|
44
|
+
if (!editor) {
|
|
45
|
+
log('đĻ Loading WASM module script...');
|
|
46
|
+
// Load the WASM module script
|
|
47
|
+
// The Module config above will be picked up by the script
|
|
48
|
+
self.importScripts('/honcho-photo-editor.js');
|
|
49
|
+
log('âŗ Waiting for Module to be available...');
|
|
50
|
+
// Wait a moment for Module to be available
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
52
|
+
log('đ¨ Creating HonchoEditor instance...');
|
|
53
|
+
editor = new HonchoEditor();
|
|
54
|
+
log('âī¸ Initializing editor...');
|
|
55
|
+
await editor.initialize(false);
|
|
56
|
+
isInitialized = true;
|
|
57
|
+
log('â
Editor initialized successfully');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
log('âšī¸ Editor already initialized, skipping...');
|
|
61
|
+
}
|
|
62
|
+
// Notify main thread that initialization is complete
|
|
63
|
+
log('đ¤ Sending INIT_SUCCESS to main thread');
|
|
64
|
+
self.postMessage({ type: 'INIT_SUCCESS' });
|
|
65
|
+
}
|
|
66
|
+
else if (type === 'PROCESS') {
|
|
67
|
+
log(`đŧī¸ Processing image: ${id}`);
|
|
68
|
+
// Ensure editor is initialized
|
|
69
|
+
if (!isInitialized || !editor) {
|
|
70
|
+
throw new Error('Editor not initialized - call INIT first');
|
|
71
|
+
}
|
|
72
|
+
// Fetch image blobs in the worker
|
|
73
|
+
log(`đĻ Fetching image from: ${path}`);
|
|
74
|
+
const imageBlob = await fetchImageAsBlob(path);
|
|
75
|
+
log(`â Fetched image blob: ${imageBlob.size} bytes`);
|
|
76
|
+
let frameBlob = null;
|
|
77
|
+
if (framePath) {
|
|
78
|
+
log(`đĻ Fetching frame from: ${framePath}`);
|
|
79
|
+
frameBlob = await fetchImageAsBlob(framePath);
|
|
80
|
+
log(`â Fetched frame blob: ${frameBlob.size} bytes`);
|
|
81
|
+
}
|
|
82
|
+
log('đī¸ Adjustments:', adjustments);
|
|
83
|
+
log('⥠Starting processToBlobFast...');
|
|
84
|
+
const startTime = performance.now();
|
|
85
|
+
// Process the image
|
|
86
|
+
const resultBlob = await editor.processToBlobFast(imageBlob, adjustments || {}, frameBlob);
|
|
87
|
+
const duration = Math.round(performance.now() - startTime);
|
|
88
|
+
log(`â
Processing complete in ${duration}ms, result size: ${resultBlob.size} bytes`);
|
|
89
|
+
// Send the result back to the main thread
|
|
90
|
+
log(`đ¤ Sending PROCESS_SUCCESS to main thread for ${id}`);
|
|
91
|
+
self.postMessage({
|
|
92
|
+
type: 'PROCESS_SUCCESS',
|
|
93
|
+
id,
|
|
94
|
+
blob: resultBlob
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
throw new Error(`Unknown message type: ${type}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
log(`â Error processing message type ${type}:`, error);
|
|
103
|
+
// Send error back to main thread
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
105
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
106
|
+
log(`đ¤ Sending ERROR to main thread for ${id}`, { error: errorMessage });
|
|
107
|
+
self.postMessage({
|
|
108
|
+
type: 'ERROR',
|
|
109
|
+
id,
|
|
110
|
+
error: errorMessage,
|
|
111
|
+
stack: errorStack
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
// Helper function to fetch image as blob in worker
|
|
116
|
+
async function fetchImageAsBlob(url) {
|
|
117
|
+
const BUCKET_HOST = envConfig.bucketHost;
|
|
118
|
+
const CLOUD_FRONT_HOST = envConfig.cloudFrontHost;
|
|
119
|
+
// Try CloudFront first if available
|
|
120
|
+
if (CLOUD_FRONT_HOST && BUCKET_HOST) {
|
|
121
|
+
try {
|
|
122
|
+
const key = url.replace(BUCKET_HOST, "");
|
|
123
|
+
const response = await fetch(`${CLOUD_FRONT_HOST}${key}`);
|
|
124
|
+
if (response.ok) {
|
|
125
|
+
log(`â Fetched from CloudFront: ${key}`);
|
|
126
|
+
return response.blob();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
log(`â ī¸ CloudFront fetch failed for ${url}:`, error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Fallback to direct fetch
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(url, {
|
|
136
|
+
mode: 'cors',
|
|
137
|
+
credentials: 'omit'
|
|
138
|
+
});
|
|
139
|
+
if (response.ok) {
|
|
140
|
+
log(`â Fetched directly: ${url}`);
|
|
141
|
+
return response.blob();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
log(`â ī¸ Direct fetch failed for ${url}:`, error);
|
|
146
|
+
}
|
|
147
|
+
// Final fallback to proxy
|
|
148
|
+
const proxyUrl = `/api/image?imageUrl=${encodeURIComponent(url)}`;
|
|
149
|
+
log(`â ī¸ Using proxy fallback: ${proxyUrl}`);
|
|
150
|
+
const response = await fetch(proxyUrl);
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`Failed to fetch image: ${response.statusText}`);
|
|
153
|
+
}
|
|
154
|
+
return response.blob();
|
|
155
|
+
}
|
|
156
|
+
log('Worker message handler registered');
|