- "app/src/utils/exportService.ts" - "app/src/utils/__tests__/exportService.test.ts" - "app/src/api/engine.ts" - "app/src/api/__tests__/engine.test.ts" - "app/src/types/opentype.d.ts" GSD-Task: S01/T03
117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
import type { PresetConfig, TraceMetadata } from '../types/engine';
|
|
import FileUpload from '../components/FileUpload';
|
|
import PresetSelector from '../components/PresetSelector';
|
|
import ParameterSliders from '../components/ParameterSliders';
|
|
import SvgPreview from '../components/SvgPreview';
|
|
import OutputInfoBar from '../components/OutputInfoBar';
|
|
import { useDebouncedTrace } from '../hooks/useDebouncedTrace';
|
|
import styles from './ImportConvert.module.css';
|
|
|
|
interface ImportConvertProps {
|
|
onUseThis: (svgOutput: string, metadata: TraceMetadata) => void;
|
|
}
|
|
|
|
/**
|
|
* Extract default slider parameters from a preset config.
|
|
*/
|
|
function defaultParamsFromPreset(config: PresetConfig): Record<string, unknown> {
|
|
const mode = config.vectorization.mode;
|
|
const params: Record<string, unknown> = {
|
|
epsilon: config.postprocessing.epsilon ?? 2.5,
|
|
};
|
|
|
|
if (mode === 'potrace') {
|
|
params.turdsize = config.vectorization.potrace?.turdsize ?? 10;
|
|
params.alphamax = config.vectorization.potrace?.alphamax ?? 1.0;
|
|
} else {
|
|
params.filter_speckle = config.vectorization.vtracer?.filter_speckle ?? 20;
|
|
params.corner_threshold = config.vectorization.vtracer?.corner_threshold ?? 60;
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
export default function ImportConvert({ onUseThis }: ImportConvertProps) {
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
const [_isSvgMode, setIsSvgMode] = useState(false);
|
|
const [selectedPreset, setSelectedPreset] = useState('sign');
|
|
const [presetConfig, setPresetConfig] = useState<PresetConfig | null>(null);
|
|
const [currentParams, setCurrentParams] = useState<Record<string, unknown>>({
|
|
epsilon: 2.5,
|
|
turdsize: 10,
|
|
alphamax: 1.0,
|
|
});
|
|
|
|
// Hook handles params stabilization via JSON.stringify internally
|
|
const { svgOutput, metadata, isLoading, error } = useDebouncedTrace(
|
|
selectedFile,
|
|
selectedPreset,
|
|
currentParams,
|
|
300,
|
|
);
|
|
|
|
const handleFileSelect = useCallback(
|
|
(file: File, isSvg: boolean) => {
|
|
setSelectedFile(file);
|
|
setIsSvgMode(isSvg);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const handlePresetSelect = useCallback(
|
|
(name: string, config: PresetConfig) => {
|
|
setSelectedPreset(name);
|
|
setPresetConfig(config);
|
|
setCurrentParams(defaultParamsFromPreset(config));
|
|
},
|
|
[],
|
|
);
|
|
|
|
const handleParamsChange = useCallback(
|
|
(params: Record<string, unknown>) => {
|
|
setCurrentParams(params);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const handleUseThis = useCallback(() => {
|
|
if (svgOutput && metadata) {
|
|
onUseThis(svgOutput, metadata);
|
|
}
|
|
}, [svgOutput, metadata, onUseThis]);
|
|
|
|
return (
|
|
<div className={styles.container}>
|
|
<div className={styles.leftPanel}>
|
|
<FileUpload onFileSelect={handleFileSelect} selectedFile={selectedFile} />
|
|
<PresetSelector
|
|
selectedPreset={selectedPreset}
|
|
onPresetSelect={handlePresetSelect}
|
|
/>
|
|
<ParameterSliders
|
|
presetConfig={presetConfig}
|
|
params={currentParams}
|
|
onChange={handleParamsChange}
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="use-this-btn"
|
|
disabled={!svgOutput || isLoading}
|
|
onClick={handleUseThis}
|
|
>
|
|
Use This →
|
|
</button>
|
|
</div>
|
|
<div className={styles.rightPanel}>
|
|
<SvgPreview
|
|
svgOutput={svgOutput}
|
|
isLoading={isLoading}
|
|
error={error}
|
|
metadata={metadata}
|
|
/>
|
|
<OutputInfoBar metadata={metadata} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|