@yogiswara/honcho-editor-ui 2.9.2 → 2.10.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/hooks/editor/useHonchoEditorSingle.d.ts +4 -1
- package/dist/hooks/editor/useHonchoEditorSingle.js +21 -2
- package/dist/hooks/useAdjustmentHistory.d.ts +4 -1
- package/dist/hooks/useAdjustmentHistory.js +90 -10
- package/dist/hooks/useAdjustmentHistoryBatch.js +1 -1
- package/dist/hooks/useEditorHeadlessRaceFree.d.ts +15 -0
- package/dist/hooks/useEditorHeadlessRaceFree.js +88 -0
- package/dist/lib/editor/honcho-editor.d.ts +97 -119
- package/dist/lib/editor/honcho-editor.js +22 -17
- package/dist/lib/hooks/useEditor.d.ts +1 -1
- package/dist/lib/hooks/useEditor.js +1 -1
- package/dist/lib/hooks/useEditorHeadless.d.ts +2 -2
- package/dist/lib/hooks/useEditorHeadless.js +1 -1
- package/dist/lib/hooks/useImageProcessor.d.ts +3 -6
- package/dist/lib/hooks/useImageProcessor.js +33 -70
- package/dist/services/base.d.ts +57 -0
- package/dist/services/base.js +243 -0
- package/dist/services/type.d.ts +191 -0
- package/dist/services/type.js +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Controller, AdjustmentState, Preset } from './type';
|
|
2
2
|
import { Gallery } from './type';
|
|
3
|
-
import { AdjustmentValues } from
|
|
3
|
+
import { AdjustmentValues } from "../../lib/editor/honcho-editor";
|
|
4
|
+
import { ColorAdjustment } from "../../services/type";
|
|
4
5
|
export interface UseHonchoEditorSingleOptions {
|
|
5
6
|
controller: Controller;
|
|
6
7
|
initImageId: string;
|
|
@@ -28,6 +29,8 @@ export interface UseHonchoEditorSingleActions {
|
|
|
28
29
|
setBatchMode: (enabled: boolean) => void;
|
|
29
30
|
startBatchMode: () => void;
|
|
30
31
|
endBatchMode: () => void;
|
|
32
|
+
updateFromSocket: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment) => void;
|
|
33
|
+
pushState: (state: AdjustmentState) => void;
|
|
31
34
|
undo: () => void;
|
|
32
35
|
redo: () => void;
|
|
33
36
|
reset: () => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { useAdjustmentHistory } from '../useAdjustmentHistory';
|
|
4
4
|
import { useGallerySwipe } from '../useGallerySwipe';
|
|
5
5
|
import { usePreset } from '../usePreset';
|
|
@@ -15,11 +15,20 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
15
15
|
const memoizedInitImageId = useMemo(() => initImageId, [initImageId]);
|
|
16
16
|
const memoizedFirebaseUid = useMemo(() => firebaseUid, [firebaseUid]);
|
|
17
17
|
// Initialize business logic hooks only
|
|
18
|
-
const adjustmentHistory = useAdjustmentHistory(initialAdjustments, memoizedController, memoizedFirebaseUid, memoizedInitImageId);
|
|
19
18
|
const gallerySwipe = useGallerySwipe(memoizedFirebaseUid, memoizedInitImageId, memoizedController);
|
|
19
|
+
const adjustmentHistory = useAdjustmentHistory(initialAdjustments, memoizedController, memoizedFirebaseUid, gallerySwipe.currentImageData?.id);
|
|
20
20
|
const presetHook = usePreset(memoizedController, memoizedFirebaseUid, { autoLoad: true });
|
|
21
21
|
// Find active preset based on current adjustments
|
|
22
22
|
const activePreset = presetHook.actions.findByAdjustments(adjustmentHistory.currentState);
|
|
23
|
+
// loop on every 5 seconds to refresh the backend()
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (gallerySwipe.currentImageData?.id) {
|
|
26
|
+
console.log('Syncing adjustment history from backend for image ID:', gallerySwipe.currentImageData.id);
|
|
27
|
+
adjustmentHistory.actions.syncFromBackend()
|
|
28
|
+
.then(() => { console.log('Adjustment history synced from backend'); })
|
|
29
|
+
.catch(console.error);
|
|
30
|
+
}
|
|
31
|
+
}, [gallerySwipe.currentImageData?.id]);
|
|
23
32
|
// Actions - pure business logic, no editor interaction - wrapped in useCallback to prevent re-renders
|
|
24
33
|
const navigateNext = useCallback(() => {
|
|
25
34
|
if (gallerySwipe.isNextAvailable) {
|
|
@@ -47,6 +56,11 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
47
56
|
const endBatchMode = useCallback(() => {
|
|
48
57
|
adjustmentHistory.config.setBatchMode(false);
|
|
49
58
|
}, [adjustmentHistory.config.setBatchMode]);
|
|
59
|
+
const pushState = useCallback(async (state) => {
|
|
60
|
+
await adjustmentHistory.config.setBatchMode(true);
|
|
61
|
+
adjustmentHistory.actions.pushState(state);
|
|
62
|
+
await adjustmentHistory.config.setBatchMode(false, true);
|
|
63
|
+
}, [adjustmentHistory.actions.pushState]);
|
|
50
64
|
const undo = useCallback(() => {
|
|
51
65
|
adjustmentHistory.actions.undo();
|
|
52
66
|
}, [adjustmentHistory.actions.undo]);
|
|
@@ -114,6 +128,9 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
114
128
|
sharpness: adjustments.sharpnessScore,
|
|
115
129
|
};
|
|
116
130
|
}, [adjustmentHistory.currentState]);
|
|
131
|
+
const updateFromSocket = useCallback((imageId, adjustmentTaskId, adjustment) => {
|
|
132
|
+
adjustmentHistory.actions.updateFromBackend(imageId, adjustmentTaskId, adjustment);
|
|
133
|
+
}, [adjustmentHistory.actions.updateFromBackend]);
|
|
117
134
|
const actions = {
|
|
118
135
|
// Navigation
|
|
119
136
|
navigateNext,
|
|
@@ -123,6 +140,8 @@ export function useHonchoEditorSingle({ controller, initImageId, firebaseUid })
|
|
|
123
140
|
setBatchMode,
|
|
124
141
|
startBatchMode,
|
|
125
142
|
endBatchMode,
|
|
143
|
+
updateFromSocket,
|
|
144
|
+
pushState,
|
|
126
145
|
// History
|
|
127
146
|
undo,
|
|
128
147
|
redo,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AdjustmentState, Controller } from '
|
|
1
|
+
import { AdjustmentState, Controller } from '../hooks/editor/type';
|
|
2
|
+
import { ColorAdjustment } from '../hooks/editor/type';
|
|
2
3
|
/**
|
|
3
4
|
* Configuration options for the adjustment history hook
|
|
4
5
|
*/
|
|
@@ -47,6 +48,8 @@ export interface HistoryActions {
|
|
|
47
48
|
trimHistory: (keepLast: number) => void;
|
|
48
49
|
/** Sync history from backend using getEditorHistory */
|
|
49
50
|
syncFromBackend: () => Promise<void>;
|
|
51
|
+
/** Update specific history entry from backend without changing current index */
|
|
52
|
+
updateFromBackend: (imageId: string, adjustmentTaskId: string, adjustment: ColorAdjustment) => void;
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
55
|
* Configuration actions for runtime adjustment
|
|
@@ -102,6 +102,8 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
102
102
|
const [currentState, setCurrentState] = useState(initialState);
|
|
103
103
|
const currentIndexRef = useRef(0);
|
|
104
104
|
const currentStateRef = useRef(initialState);
|
|
105
|
+
const currentFirebaseUIDRef = useRef(firebaseUid);
|
|
106
|
+
const currentImageIdRef = useRef(currentImageId);
|
|
105
107
|
// Batch mode state - ref to avoid triggering effects
|
|
106
108
|
const batchModeRef = useRef(internalOptions.enableBatching);
|
|
107
109
|
const batchStartIndexRef = useRef(null);
|
|
@@ -112,6 +114,8 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
112
114
|
const devWarningsRef = useRef(internalOptions.devWarnings);
|
|
113
115
|
// Computed current index for return value (defined early for dependencies)
|
|
114
116
|
const currentIndex = currentIndexRef.current;
|
|
117
|
+
currentFirebaseUIDRef.current = internalOptions.firebaseUid;
|
|
118
|
+
currentImageIdRef.current = internalOptions.currentImageId;
|
|
115
119
|
// Performance monitoring
|
|
116
120
|
const performanceRef = useRef({
|
|
117
121
|
lastHistorySize: 1,
|
|
@@ -127,9 +131,11 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
127
131
|
// }, [history, currentIndex, initialState]);
|
|
128
132
|
useEffect(() => {
|
|
129
133
|
if (internalOptions.currentImageId) {
|
|
134
|
+
console.log(`🔄 Syncing adjustment history from backend for image ID: ${internalOptions.currentImageId}`);
|
|
130
135
|
syncFromBackend().catch(console.error);
|
|
131
136
|
}
|
|
132
137
|
}, [internalOptions.currentImageId, internalOptions.firebaseUid, internalOptions.controller]);
|
|
138
|
+
console.log(internalOptions);
|
|
133
139
|
const getMemoryUsage = useCallback(() => {
|
|
134
140
|
try {
|
|
135
141
|
const historyString = JSON.stringify(history);
|
|
@@ -442,18 +448,18 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
442
448
|
return newHistory;
|
|
443
449
|
});
|
|
444
450
|
// Call controller to create history in backend - OUTSIDE of setHistory callback
|
|
445
|
-
if (internalOptions.controller &&
|
|
451
|
+
if (internalOptions.controller && currentFirebaseUIDRef.current && currentImageIdRef.current) {
|
|
446
452
|
try {
|
|
447
453
|
console.log('🔄 Creating editor history in backend for batch mode end');
|
|
448
454
|
console.log('📋 Current state for backend:', batchStartIndexRef.current, currentStateRef.current, history.length);
|
|
449
455
|
const createEditorConfigPayload = {
|
|
450
|
-
gallery_id:
|
|
456
|
+
gallery_id: currentImageIdRef.current,
|
|
451
457
|
task_id: taskId,
|
|
452
458
|
color_adjustment: convertAdjustmentStateToColorAdjustment(currentStateRef.current),
|
|
453
459
|
...(replaceFromTaskId && { replace_from: replaceFromTaskId })
|
|
454
460
|
};
|
|
455
461
|
// Create editor config with current adjustments
|
|
456
|
-
internalOptions.controller.createEditorConfig(
|
|
462
|
+
internalOptions.controller.createEditorConfig(currentFirebaseUIDRef.current, createEditorConfigPayload).then(() => {
|
|
457
463
|
console.log('✅ Successfully created editor history in backend');
|
|
458
464
|
}).catch((error) => {
|
|
459
465
|
console.error('❌ Failed to create editor history in backend:', error);
|
|
@@ -464,9 +470,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
464
470
|
}
|
|
465
471
|
}
|
|
466
472
|
else {
|
|
467
|
-
|
|
468
|
-
console.warn('⚠️ Controller, firebaseUid, or currentImageId not provided - skipping backend history creation');
|
|
469
|
-
}
|
|
473
|
+
console.warn('⚠️ Controller, firebaseUid, or currentImageId not provided - skipping backend history creation', { controller, firebaseUid, currentImageId });
|
|
470
474
|
}
|
|
471
475
|
}
|
|
472
476
|
else {
|
|
@@ -481,7 +485,7 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
481
485
|
batchStartStateRef.current = null;
|
|
482
486
|
batchModeProcessingRef.current = false; // Reset processing flag
|
|
483
487
|
}
|
|
484
|
-
}, [internalOptions, history]);
|
|
488
|
+
}, [internalOptions.controller, history]);
|
|
485
489
|
// Sync history from backend using getEditorHistory
|
|
486
490
|
const syncFromBackend = useCallback(async () => {
|
|
487
491
|
if (!internalOptions.controller || !internalOptions.firebaseUid || !internalOptions.currentImageId) {
|
|
@@ -514,7 +518,32 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
514
518
|
}
|
|
515
519
|
// Handle empty history case
|
|
516
520
|
if (backendHistoryEntries.length === 0) {
|
|
517
|
-
console.log('📝 Backend history is empty,
|
|
521
|
+
console.log('📝 Backend history is empty, creating initial adjustment with all values set to 0');
|
|
522
|
+
// Create initial adjustment state with all values set to 0
|
|
523
|
+
const initialAdjustmentState = {
|
|
524
|
+
tempScore: 0,
|
|
525
|
+
tintScore: 0,
|
|
526
|
+
saturationScore: 0,
|
|
527
|
+
vibranceScore: 0,
|
|
528
|
+
exposureScore: 0,
|
|
529
|
+
contrastScore: 0,
|
|
530
|
+
highlightsScore: 0,
|
|
531
|
+
shadowsScore: 0,
|
|
532
|
+
whitesScore: 0,
|
|
533
|
+
blacksScore: 0,
|
|
534
|
+
clarityScore: 0,
|
|
535
|
+
sharpnessScore: 0,
|
|
536
|
+
};
|
|
537
|
+
const initialHistoryEntry = {
|
|
538
|
+
state: initialAdjustmentState,
|
|
539
|
+
taskId: `initial_${Date.now()}`
|
|
540
|
+
};
|
|
541
|
+
// Set history with initial adjustment
|
|
542
|
+
setHistory([initialHistoryEntry]);
|
|
543
|
+
currentIndexRef.current = 0;
|
|
544
|
+
currentStateRef.current = initialAdjustmentState;
|
|
545
|
+
setCurrentState(initialAdjustmentState);
|
|
546
|
+
console.log('✅ Created initial history with all adjustments set to 0');
|
|
518
547
|
return;
|
|
519
548
|
}
|
|
520
549
|
// Exit batch mode if active
|
|
@@ -537,6 +566,56 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
537
566
|
throw error;
|
|
538
567
|
}
|
|
539
568
|
}, [internalOptions]);
|
|
569
|
+
// Update specific history entry from backend without changing current index
|
|
570
|
+
const updateFromBackend = useCallback((imageId, adjustmentTaskId, adjustment) => {
|
|
571
|
+
// Ignore updates if imageID doesn't match current image
|
|
572
|
+
if (imageId !== internalOptions.currentImageId) {
|
|
573
|
+
console.log(`🚫 updateFromBackend: Ignoring update for different image (${imageId} vs ${internalOptions.currentImageId})`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
// Ignore updates during batch mode
|
|
577
|
+
if (batchModeRef.current) {
|
|
578
|
+
console.log(`🚫 updateFromBackend: Ignoring update during batch mode for taskId: ${adjustmentTaskId}`);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
console.log(`🔄 updateFromBackend: Received update for taskId: ${adjustmentTaskId}, imageId: ${imageId}`);
|
|
582
|
+
// Convert ColorAdjustment to AdjustmentState
|
|
583
|
+
const updatedState = convertColorAdjustmentToAdjustmentState(adjustment);
|
|
584
|
+
setHistory(prevHistory => {
|
|
585
|
+
// Find the index of the task to update
|
|
586
|
+
const taskIndex = prevHistory.findIndex(entry => entry.taskId === adjustmentTaskId);
|
|
587
|
+
if (taskIndex === -1) {
|
|
588
|
+
// Task doesn't exist in current history, we need to sync from backend
|
|
589
|
+
// to get the complete history with proper chronological order
|
|
590
|
+
console.log(`📝 updateFromBackend: TaskId ${adjustmentTaskId} not found in current history, triggering sync from backend`);
|
|
591
|
+
// Don't modify history here, instead trigger a sync
|
|
592
|
+
// Use setTimeout to avoid calling async function inside setState
|
|
593
|
+
setTimeout(() => {
|
|
594
|
+
syncFromBackend().catch(error => {
|
|
595
|
+
console.error('❌ Failed to sync history after unknown taskId:', error);
|
|
596
|
+
});
|
|
597
|
+
}, 0);
|
|
598
|
+
// Return unchanged history for now
|
|
599
|
+
return prevHistory;
|
|
600
|
+
}
|
|
601
|
+
// Task exists, check if it's the current index
|
|
602
|
+
const isCurrentIndex = taskIndex === currentIndexRef.current;
|
|
603
|
+
if (isCurrentIndex) {
|
|
604
|
+
console.log(`🔄 updateFromBackend: Updating current index (${taskIndex}) but not changing current state`);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
console.log(`🔄 updateFromBackend: Updating history entry at index ${taskIndex} (far behind current ${currentIndexRef.current})`);
|
|
608
|
+
}
|
|
609
|
+
// Update the specific history entry
|
|
610
|
+
const updatedHistory = [...prevHistory];
|
|
611
|
+
updatedHistory[taskIndex] = {
|
|
612
|
+
state: updatedState,
|
|
613
|
+
taskId: adjustmentTaskId
|
|
614
|
+
};
|
|
615
|
+
return updatedHistory;
|
|
616
|
+
});
|
|
617
|
+
console.log(`✅ updateFromBackend: Successfully updated taskId ${adjustmentTaskId}`);
|
|
618
|
+
}, [internalOptions]);
|
|
540
619
|
// History info object
|
|
541
620
|
const historyInfo = useMemo(() => ({
|
|
542
621
|
canUndo: currentIndex > 0,
|
|
@@ -555,8 +634,9 @@ export function useAdjustmentHistory(initialState, controller, firebaseUid, curr
|
|
|
555
634
|
clearHistory,
|
|
556
635
|
getHistory,
|
|
557
636
|
trimHistory,
|
|
558
|
-
syncFromBackend
|
|
559
|
-
|
|
637
|
+
syncFromBackend,
|
|
638
|
+
updateFromBackend
|
|
639
|
+
}), [pushState, undo, redo, jumpToIndex, clearHistory, getHistory, trimHistory, syncFromBackend, updateFromBackend]);
|
|
560
640
|
// Config object - stabilized with useMemo
|
|
561
641
|
const config = useMemo(() => ({
|
|
562
642
|
setMaxSize,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
|
-
import { mapAdjustmentStateToColorAdjustment, mapColorAdjustmentToAdjustmentState } from
|
|
2
|
+
import { mapAdjustmentStateToColorAdjustment, mapColorAdjustmentToAdjustmentState } from "../utils/adjustment";
|
|
3
3
|
/**
|
|
4
4
|
* Create default adjustment state
|
|
5
5
|
*/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import HonchoEditor from "../lib/editor/honcho-editor";
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
__raceFreeeEditor?: HonchoEditor;
|
|
5
|
+
__raceFreeEditorInitializing?: boolean;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
interface UseEditorHeadlessOptions {
|
|
9
|
+
}
|
|
10
|
+
export declare function useEditorHeadlessRaceFree(options?: UseEditorHeadlessOptions): {
|
|
11
|
+
isReady: boolean;
|
|
12
|
+
error: Error | null;
|
|
13
|
+
editor: HonchoEditor | null;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
export function useEditorHeadlessRaceFree(options = {}) {
|
|
3
|
+
const [isReady, setIsReady] = useState(false);
|
|
4
|
+
const [error, setError] = useState(null);
|
|
5
|
+
const componentId = useRef(`component-${Date.now()}-${Math.random()}`);
|
|
6
|
+
console.log(`[RACE-FREE] Hook called in component: ${componentId.current}`);
|
|
7
|
+
// Get or create shared race-free editor instance
|
|
8
|
+
const getOrCreateSharedEditor = useCallback(async () => {
|
|
9
|
+
// If we already have a global instance, return it
|
|
10
|
+
if (window.__raceFreeeEditor) {
|
|
11
|
+
console.log(`[RACE-FREE] Reusing existing shared editor instance`);
|
|
12
|
+
return window.__raceFreeeEditor;
|
|
13
|
+
}
|
|
14
|
+
// If already initializing, wait for completion
|
|
15
|
+
if (window.__raceFreeEditorInitializing) {
|
|
16
|
+
console.log(`[RACE-FREE] Waiting for shared editor initialization...`);
|
|
17
|
+
let attempts = 0;
|
|
18
|
+
while (window.__raceFreeEditorInitializing && attempts < 100) {
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
+
attempts++;
|
|
21
|
+
}
|
|
22
|
+
if (window.__raceFreeeEditor) {
|
|
23
|
+
return window.__raceFreeeEditor;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Start initialization
|
|
27
|
+
window.__raceFreeEditorInitializing = true;
|
|
28
|
+
console.log(`[RACE-FREE] Creating new shared editor instance`);
|
|
29
|
+
try {
|
|
30
|
+
// Wait for global HonchoEditor to be available (loaded by EditorContext)
|
|
31
|
+
console.log(`[RACE-FREE] Waiting for global HonchoEditor...`);
|
|
32
|
+
let attempts = 0;
|
|
33
|
+
const maxAttempts = 100; // 10 seconds max wait
|
|
34
|
+
while (!window.HonchoEditor && attempts < maxAttempts) {
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
36
|
+
attempts++;
|
|
37
|
+
}
|
|
38
|
+
if (!window.HonchoEditor) {
|
|
39
|
+
throw new Error('[RACE-FREE] Global HonchoEditor not available after waiting');
|
|
40
|
+
}
|
|
41
|
+
console.log(`[RACE-FREE] Global HonchoEditor available, creating shared instance`);
|
|
42
|
+
// Create shared instance using the global constructor
|
|
43
|
+
const editorInstance = new window.HonchoEditor();
|
|
44
|
+
console.log(`[RACE-FREE] Created new shared HonchoEditor instance`);
|
|
45
|
+
// Initialize the editor instance
|
|
46
|
+
await editorInstance.initialize(false);
|
|
47
|
+
console.log(`[RACE-FREE] Shared HonchoEditor instance initialized`);
|
|
48
|
+
// Store globally
|
|
49
|
+
window.__raceFreeeEditor = editorInstance;
|
|
50
|
+
console.log(`[RACE-FREE] Shared editor instance ready`);
|
|
51
|
+
return editorInstance;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.error(`[RACE-FREE] Shared initialization failed:`, err);
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
window.__raceFreeEditorInitializing = false;
|
|
59
|
+
}
|
|
60
|
+
}, []);
|
|
61
|
+
// Initialize shared editor instance
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
console.log(`[RACE-FREE] Component mounted: ${componentId.current}`);
|
|
64
|
+
const initializeSharedEditor = async () => {
|
|
65
|
+
try {
|
|
66
|
+
await getOrCreateSharedEditor();
|
|
67
|
+
setIsReady(true);
|
|
68
|
+
setError(null);
|
|
69
|
+
console.log(`[RACE-FREE] Component ${componentId.current} ready`);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error(`[RACE-FREE] Component ${componentId.current} initialization failed:`, err);
|
|
73
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
74
|
+
setIsReady(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
initializeSharedEditor();
|
|
78
|
+
return () => {
|
|
79
|
+
console.log(`[RACE-FREE] Component unmounting: ${componentId.current}`);
|
|
80
|
+
// Note: We don't cleanup the shared editor instance here since other components might be using it
|
|
81
|
+
};
|
|
82
|
+
}, []); // No dependencies to prevent re-initialization
|
|
83
|
+
return {
|
|
84
|
+
isReady,
|
|
85
|
+
error,
|
|
86
|
+
editor: window.__raceFreeeEditor || null
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -1,4 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Honcho Photo Editor - JavaScript Wrapper
|
|
3
|
+
*
|
|
4
|
+
* This wrapper provides a clean interface to the C++ image processing engine.
|
|
5
|
+
*
|
|
6
|
+
* ARCHITECTURE: Dual-Path Processing
|
|
7
|
+
* ================================
|
|
8
|
+
*
|
|
9
|
+
* FRONTEND (Real-time Preview):
|
|
10
|
+
* - Uses GPU-to-GPU rendering via renderToCanvas()
|
|
11
|
+
* - Zero memory copies for 60fps performance
|
|
12
|
+
* - Optimized for interactive UI sliders
|
|
13
|
+
*
|
|
14
|
+
* BACKEND (Server Processing):
|
|
15
|
+
* - Uses CPU memory path via getProcessedImageData()
|
|
16
|
+
* - Full quality data export for server workflows
|
|
17
|
+
* - Headless processing without WebGL context
|
|
18
|
+
* - Batch processing and API endpoints
|
|
19
|
+
*
|
|
20
|
+
* Both paths use the same C++ processing core with identical results.
|
|
21
|
+
* JavaScript handles all UI and canvas rendering.
|
|
22
|
+
* C++ handles all image processing algorithms.
|
|
23
|
+
*/
|
|
1
24
|
export class HonchoEditor {
|
|
25
|
+
static ADJUSTMENT_RANGES: {
|
|
26
|
+
temperature: {
|
|
27
|
+
min: number;
|
|
28
|
+
max: number;
|
|
29
|
+
default: number;
|
|
30
|
+
step: number;
|
|
31
|
+
};
|
|
32
|
+
tint: {
|
|
33
|
+
min: number;
|
|
34
|
+
max: number;
|
|
35
|
+
default: number;
|
|
36
|
+
step: number;
|
|
37
|
+
};
|
|
38
|
+
saturation: {
|
|
39
|
+
min: number;
|
|
40
|
+
max: number;
|
|
41
|
+
default: number;
|
|
42
|
+
step: number;
|
|
43
|
+
};
|
|
44
|
+
vibrance: {
|
|
45
|
+
min: number;
|
|
46
|
+
max: number;
|
|
47
|
+
default: number;
|
|
48
|
+
step: number;
|
|
49
|
+
};
|
|
50
|
+
exposure: {
|
|
51
|
+
min: number;
|
|
52
|
+
max: number;
|
|
53
|
+
default: number;
|
|
54
|
+
step: number;
|
|
55
|
+
};
|
|
56
|
+
contrast: {
|
|
57
|
+
min: number;
|
|
58
|
+
max: number;
|
|
59
|
+
default: number;
|
|
60
|
+
step: number;
|
|
61
|
+
};
|
|
62
|
+
highlights: {
|
|
63
|
+
min: number;
|
|
64
|
+
max: number;
|
|
65
|
+
default: number;
|
|
66
|
+
step: number;
|
|
67
|
+
};
|
|
68
|
+
shadows: {
|
|
69
|
+
min: number;
|
|
70
|
+
max: number;
|
|
71
|
+
default: number;
|
|
72
|
+
step: number;
|
|
73
|
+
};
|
|
74
|
+
whites: {
|
|
75
|
+
min: number;
|
|
76
|
+
max: number;
|
|
77
|
+
default: number;
|
|
78
|
+
step: number;
|
|
79
|
+
};
|
|
80
|
+
blacks: {
|
|
81
|
+
min: number;
|
|
82
|
+
max: number;
|
|
83
|
+
default: number;
|
|
84
|
+
step: number;
|
|
85
|
+
};
|
|
86
|
+
clarity: {
|
|
87
|
+
min: number;
|
|
88
|
+
max: number;
|
|
89
|
+
default: number;
|
|
90
|
+
step: number;
|
|
91
|
+
};
|
|
92
|
+
sharpness: {
|
|
93
|
+
min: number;
|
|
94
|
+
max: number;
|
|
95
|
+
default: number;
|
|
96
|
+
step: number;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
2
99
|
wasmModule: any;
|
|
3
100
|
isInitialized: boolean;
|
|
4
101
|
currentImageData: any;
|
|
@@ -203,122 +300,3 @@ export namespace HonchoEditorUtils {
|
|
|
203
300
|
*/
|
|
204
301
|
function formatFileSize(bytes: any): string;
|
|
205
302
|
}
|
|
206
|
-
export namespace ADJUSTMENT_RANGES {
|
|
207
|
-
namespace temperature {
|
|
208
|
-
export let min: number;
|
|
209
|
-
export let max: number;
|
|
210
|
-
let _default: number;
|
|
211
|
-
export { _default as default };
|
|
212
|
-
export let step: number;
|
|
213
|
-
}
|
|
214
|
-
namespace tint {
|
|
215
|
-
let min_1: number;
|
|
216
|
-
export { min_1 as min };
|
|
217
|
-
let max_1: number;
|
|
218
|
-
export { max_1 as max };
|
|
219
|
-
let _default_1: number;
|
|
220
|
-
export { _default_1 as default };
|
|
221
|
-
let step_1: number;
|
|
222
|
-
export { step_1 as step };
|
|
223
|
-
}
|
|
224
|
-
namespace saturation {
|
|
225
|
-
let min_2: number;
|
|
226
|
-
export { min_2 as min };
|
|
227
|
-
let max_2: number;
|
|
228
|
-
export { max_2 as max };
|
|
229
|
-
let _default_2: number;
|
|
230
|
-
export { _default_2 as default };
|
|
231
|
-
let step_2: number;
|
|
232
|
-
export { step_2 as step };
|
|
233
|
-
}
|
|
234
|
-
namespace vibrance {
|
|
235
|
-
let min_3: number;
|
|
236
|
-
export { min_3 as min };
|
|
237
|
-
let max_3: number;
|
|
238
|
-
export { max_3 as max };
|
|
239
|
-
let _default_3: number;
|
|
240
|
-
export { _default_3 as default };
|
|
241
|
-
let step_3: number;
|
|
242
|
-
export { step_3 as step };
|
|
243
|
-
}
|
|
244
|
-
namespace exposure {
|
|
245
|
-
let min_4: number;
|
|
246
|
-
export { min_4 as min };
|
|
247
|
-
let max_4: number;
|
|
248
|
-
export { max_4 as max };
|
|
249
|
-
let _default_4: number;
|
|
250
|
-
export { _default_4 as default };
|
|
251
|
-
let step_4: number;
|
|
252
|
-
export { step_4 as step };
|
|
253
|
-
}
|
|
254
|
-
namespace contrast {
|
|
255
|
-
let min_5: number;
|
|
256
|
-
export { min_5 as min };
|
|
257
|
-
let max_5: number;
|
|
258
|
-
export { max_5 as max };
|
|
259
|
-
let _default_5: number;
|
|
260
|
-
export { _default_5 as default };
|
|
261
|
-
let step_5: number;
|
|
262
|
-
export { step_5 as step };
|
|
263
|
-
}
|
|
264
|
-
namespace highlights {
|
|
265
|
-
let min_6: number;
|
|
266
|
-
export { min_6 as min };
|
|
267
|
-
let max_6: number;
|
|
268
|
-
export { max_6 as max };
|
|
269
|
-
let _default_6: number;
|
|
270
|
-
export { _default_6 as default };
|
|
271
|
-
let step_6: number;
|
|
272
|
-
export { step_6 as step };
|
|
273
|
-
}
|
|
274
|
-
namespace shadows {
|
|
275
|
-
let min_7: number;
|
|
276
|
-
export { min_7 as min };
|
|
277
|
-
let max_7: number;
|
|
278
|
-
export { max_7 as max };
|
|
279
|
-
let _default_7: number;
|
|
280
|
-
export { _default_7 as default };
|
|
281
|
-
let step_7: number;
|
|
282
|
-
export { step_7 as step };
|
|
283
|
-
}
|
|
284
|
-
namespace whites {
|
|
285
|
-
let min_8: number;
|
|
286
|
-
export { min_8 as min };
|
|
287
|
-
let max_8: number;
|
|
288
|
-
export { max_8 as max };
|
|
289
|
-
let _default_8: number;
|
|
290
|
-
export { _default_8 as default };
|
|
291
|
-
let step_8: number;
|
|
292
|
-
export { step_8 as step };
|
|
293
|
-
}
|
|
294
|
-
namespace blacks {
|
|
295
|
-
let min_9: number;
|
|
296
|
-
export { min_9 as min };
|
|
297
|
-
let max_9: number;
|
|
298
|
-
export { max_9 as max };
|
|
299
|
-
let _default_9: number;
|
|
300
|
-
export { _default_9 as default };
|
|
301
|
-
let step_9: number;
|
|
302
|
-
export { step_9 as step };
|
|
303
|
-
}
|
|
304
|
-
namespace clarity {
|
|
305
|
-
let min_10: number;
|
|
306
|
-
export { min_10 as min };
|
|
307
|
-
let max_10: number;
|
|
308
|
-
export { max_10 as max };
|
|
309
|
-
let _default_10: number;
|
|
310
|
-
export { _default_10 as default };
|
|
311
|
-
let step_10: number;
|
|
312
|
-
export { step_10 as step };
|
|
313
|
-
}
|
|
314
|
-
namespace sharpness {
|
|
315
|
-
let min_11: number;
|
|
316
|
-
export { min_11 as min };
|
|
317
|
-
let max_11: number;
|
|
318
|
-
export { max_11 as max };
|
|
319
|
-
let _default_11: number;
|
|
320
|
-
export { _default_11 as default };
|
|
321
|
-
let step_11: number;
|
|
322
|
-
export { step_11 as step };
|
|
323
|
-
}
|
|
324
|
-
}
|
|
@@ -23,20 +23,7 @@
|
|
|
23
23
|
* C++ handles all image processing algorithms.
|
|
24
24
|
*/
|
|
25
25
|
// Adjustment ranges for UI components
|
|
26
|
-
|
|
27
|
-
temperature: { min: -100, max: 100, default: 0, step: 1 },
|
|
28
|
-
tint: { min: -100, max: 100, default: 0, step: 1 },
|
|
29
|
-
saturation: { min: -100, max: 100, default: 0, step: 1 },
|
|
30
|
-
vibrance: { min: -100, max: 100, default: 0, step: 1 },
|
|
31
|
-
exposure: { min: -100, max: 100, default: 0, step: 1 },
|
|
32
|
-
contrast: { min: -100, max: 100, default: 0, step: 1 },
|
|
33
|
-
highlights: { min: -100, max: 100, default: 0, step: 1 },
|
|
34
|
-
shadows: { min: -100, max: 100, default: 0, step: 1 },
|
|
35
|
-
whites: { min: -100, max: 100, default: 0, step: 1 },
|
|
36
|
-
blacks: { min: -100, max: 100, default: 0, step: 1 },
|
|
37
|
-
clarity: { min: -100, max: 100, default: 0, step: 1 },
|
|
38
|
-
sharpness: { min: -100, max: 100, default: 0, step: 1 }
|
|
39
|
-
};
|
|
26
|
+
// Moved inside HonchoEditor class as static property
|
|
40
27
|
class HonchoEditor {
|
|
41
28
|
constructor() {
|
|
42
29
|
this.wasmModule = null;
|
|
@@ -554,6 +541,9 @@ class HonchoEditor {
|
|
|
554
541
|
}
|
|
555
542
|
console.log('✅ Frame applied successfully');
|
|
556
543
|
}
|
|
544
|
+
else {
|
|
545
|
+
this.clearFrame();
|
|
546
|
+
}
|
|
557
547
|
// Step 3: Apply all adjustments
|
|
558
548
|
console.log('🎛️ Applying adjustments:', adjustments);
|
|
559
549
|
const validAdjustments = [
|
|
@@ -718,7 +708,7 @@ class HonchoEditor {
|
|
|
718
708
|
* Validate adjustment value against range
|
|
719
709
|
*/
|
|
720
710
|
validateAdjustment(name, value) {
|
|
721
|
-
const range = ADJUSTMENT_RANGES[name];
|
|
711
|
+
const range = HonchoEditor.ADJUSTMENT_RANGES[name];
|
|
722
712
|
if (!range) {
|
|
723
713
|
throw new Error(`Unknown adjustment: ${name}`);
|
|
724
714
|
}
|
|
@@ -742,6 +732,21 @@ class HonchoEditor {
|
|
|
742
732
|
this.canvas = null;
|
|
743
733
|
}
|
|
744
734
|
}
|
|
735
|
+
// Static adjustment ranges for UI components
|
|
736
|
+
HonchoEditor.ADJUSTMENT_RANGES = {
|
|
737
|
+
temperature: { min: -100, max: 100, default: 0, step: 1 },
|
|
738
|
+
tint: { min: -100, max: 100, default: 0, step: 1 },
|
|
739
|
+
saturation: { min: -100, max: 100, default: 0, step: 1 },
|
|
740
|
+
vibrance: { min: -100, max: 100, default: 0, step: 1 },
|
|
741
|
+
exposure: { min: -100, max: 100, default: 0, step: 1 },
|
|
742
|
+
contrast: { min: -100, max: 100, default: 0, step: 1 },
|
|
743
|
+
highlights: { min: -100, max: 100, default: 0, step: 1 },
|
|
744
|
+
shadows: { min: -100, max: 100, default: 0, step: 1 },
|
|
745
|
+
whites: { min: -100, max: 100, default: 0, step: 1 },
|
|
746
|
+
blacks: { min: -100, max: 100, default: 0, step: 1 },
|
|
747
|
+
clarity: { min: -100, max: 100, default: 0, step: 1 },
|
|
748
|
+
sharpness: { min: -100, max: 100, default: 0, step: 1 }
|
|
749
|
+
};
|
|
745
750
|
// Export for convenience
|
|
746
751
|
// export default HonchoEditor; // Disabled for browser compatibility
|
|
747
752
|
// Helper functions for common operations
|
|
@@ -816,10 +821,10 @@ const HonchoEditorUtils = {
|
|
|
816
821
|
};
|
|
817
822
|
// Export for both Node.js and browser
|
|
818
823
|
if (typeof module !== 'undefined' && module.exports) {
|
|
819
|
-
module.exports = { HonchoEditor, HonchoEditorUtils
|
|
824
|
+
module.exports = { HonchoEditor, HonchoEditorUtils };
|
|
820
825
|
}
|
|
821
826
|
else {
|
|
822
827
|
window.HonchoEditor = HonchoEditor;
|
|
823
828
|
window.HonchoEditorUtils = HonchoEditorUtils;
|
|
824
|
-
window.ADJUSTMENT_RANGES = ADJUSTMENT_RANGES;
|
|
829
|
+
//window.ADJUSTMENT_RANGES = ADJUSTMENT_RANGES;
|
|
825
830
|
}
|