178 lines
6.4 KiB
TypeScript
178 lines
6.4 KiB
TypeScript
|
|
/**
|
|||
|
|
* 컴포넌트 ID로 해당 컴포넌트의 ConfigPanel을 동적으로 로드하는 유틸리티
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import React from "react";
|
|||
|
|
|
|||
|
|
// 컴포넌트별 ConfigPanel 동적 import 맵
|
|||
|
|
const CONFIG_PANEL_MAP: Record<string, () => Promise<any>> = {
|
|||
|
|
"text-input": () => import("@/lib/registry/components/text-input/TextInputConfigPanel"),
|
|||
|
|
"number-input": () => import("@/lib/registry/components/number-input/NumberInputConfigPanel"),
|
|||
|
|
"date-input": () => import("@/lib/registry/components/date-input/DateInputConfigPanel"),
|
|||
|
|
"textarea-basic": () => import("@/lib/registry/components/textarea-basic/TextareaBasicConfigPanel"),
|
|||
|
|
"select-basic": () => import("@/lib/registry/components/select-basic/SelectBasicConfigPanel"),
|
|||
|
|
"checkbox-basic": () => import("@/lib/registry/components/checkbox-basic/CheckboxBasicConfigPanel"),
|
|||
|
|
"radio-basic": () => import("@/lib/registry/components/radio-basic/RadioBasicConfigPanel"),
|
|||
|
|
"toggle-switch": () => import("@/lib/registry/components/toggle-switch/ToggleSwitchConfigPanel"),
|
|||
|
|
"file-upload": () => import("@/lib/registry/components/file-upload/FileUploadConfigPanel"),
|
|||
|
|
"button-primary": () => import("@/lib/registry/components/button-primary/ButtonPrimaryConfigPanel"),
|
|||
|
|
"text-display": () => import("@/lib/registry/components/text-display/TextDisplayConfigPanel"),
|
|||
|
|
"slider-basic": () => import("@/lib/registry/components/slider-basic/SliderBasicConfigPanel"),
|
|||
|
|
"image-display": () => import("@/lib/registry/components/image-display/ImageDisplayConfigPanel"),
|
|||
|
|
"divider-line": () => import("@/lib/registry/components/divider-line/DividerLineConfigPanel"),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ConfigPanel 컴포넌트 캐시
|
|||
|
|
const configPanelCache = new Map<string, React.ComponentType<any>>();
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 컴포넌트 ID로 ConfigPanel 컴포넌트를 동적으로 로드
|
|||
|
|
*/
|
|||
|
|
export async function getComponentConfigPanel(componentId: string): Promise<React.ComponentType<any> | null> {
|
|||
|
|
// 캐시에서 먼저 확인
|
|||
|
|
if (configPanelCache.has(componentId)) {
|
|||
|
|
return configPanelCache.get(componentId)!;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 매핑에서 import 함수 찾기
|
|||
|
|
const importFn = CONFIG_PANEL_MAP[componentId];
|
|||
|
|
if (!importFn) {
|
|||
|
|
console.warn(`컴포넌트 "${componentId}"에 대한 ConfigPanel을 찾을 수 없습니다.`);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
console.log(`🔧 ConfigPanel 로드 중: ${componentId}`);
|
|||
|
|
const module = await importFn();
|
|||
|
|
|
|||
|
|
// 모듈에서 ConfigPanel 컴포넌트 추출
|
|||
|
|
const ConfigPanelComponent = module[`${toPascalCase(componentId)}ConfigPanel`] || module.default;
|
|||
|
|
|
|||
|
|
if (!ConfigPanelComponent) {
|
|||
|
|
console.error(`컴포넌트 "${componentId}"의 ConfigPanel을 모듈에서 찾을 수 없습니다.`);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 캐시에 저장
|
|||
|
|
configPanelCache.set(componentId, ConfigPanelComponent);
|
|||
|
|
console.log(`✅ ConfigPanel 로드 완료: ${componentId}`);
|
|||
|
|
|
|||
|
|
return ConfigPanelComponent;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`컴포넌트 "${componentId}"의 ConfigPanel 로드 실패:`, error);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 컴포넌트 ID가 ConfigPanel을 지원하는지 확인
|
|||
|
|
*/
|
|||
|
|
export function hasComponentConfigPanel(componentId: string): boolean {
|
|||
|
|
return componentId in CONFIG_PANEL_MAP;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 지원되는 모든 컴포넌트 ID 목록 조회
|
|||
|
|
*/
|
|||
|
|
export function getSupportedConfigPanelComponents(): string[] {
|
|||
|
|
return Object.keys(CONFIG_PANEL_MAP);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* kebab-case를 PascalCase로 변환
|
|||
|
|
* text-input → TextInput
|
|||
|
|
*/
|
|||
|
|
function toPascalCase(str: string): string {
|
|||
|
|
return str
|
|||
|
|
.split('-')
|
|||
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|||
|
|
.join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 컴포넌트 설정 패널을 렌더링하는 React 컴포넌트
|
|||
|
|
*/
|
|||
|
|
export interface ComponentConfigPanelProps {
|
|||
|
|
componentId: string;
|
|||
|
|
config: Record<string, any>;
|
|||
|
|
onChange: (config: Record<string, any>) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const DynamicComponentConfigPanel: React.FC<ComponentConfigPanelProps> = ({
|
|||
|
|
componentId,
|
|||
|
|
config,
|
|||
|
|
onChange,
|
|||
|
|
}) => {
|
|||
|
|
const [ConfigPanelComponent, setConfigPanelComponent] = React.useState<React.ComponentType<any> | null>(null);
|
|||
|
|
const [loading, setLoading] = React.useState(true);
|
|||
|
|
const [error, setError] = React.useState<string | null>(null);
|
|||
|
|
|
|||
|
|
React.useEffect(() => {
|
|||
|
|
let mounted = true;
|
|||
|
|
|
|||
|
|
async function loadConfigPanel() {
|
|||
|
|
try {
|
|||
|
|
console.log(`🔧 DynamicComponentConfigPanel: ${componentId} 로드 시작`);
|
|||
|
|
setLoading(true);
|
|||
|
|
setError(null);
|
|||
|
|
|
|||
|
|
const component = await getComponentConfigPanel(componentId);
|
|||
|
|
console.log(`🔧 DynamicComponentConfigPanel: ${componentId} 로드 결과:`, component);
|
|||
|
|
|
|||
|
|
if (mounted) {
|
|||
|
|
setConfigPanelComponent(() => component);
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error(`❌ DynamicComponentConfigPanel: ${componentId} 로드 실패:`, err);
|
|||
|
|
if (mounted) {
|
|||
|
|
setError(err instanceof Error ? err.message : String(err));
|
|||
|
|
setLoading(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
loadConfigPanel();
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
mounted = false;
|
|||
|
|
};
|
|||
|
|
}, [componentId]);
|
|||
|
|
|
|||
|
|
if (loading) {
|
|||
|
|
return (
|
|||
|
|
<div className="rounded-md border border-dashed border-gray-300 bg-gray-50 p-4">
|
|||
|
|
<div className="flex items-center gap-2 text-gray-600">
|
|||
|
|
<span className="text-sm font-medium">⏳ 로딩 중...</span>
|
|||
|
|
</div>
|
|||
|
|
<p className="mt-1 text-xs text-gray-500">설정 패널을 불러오는 중입니다.</p>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
return (
|
|||
|
|
<div className="rounded-md border border-dashed border-red-300 bg-red-50 p-4">
|
|||
|
|
<div className="flex items-center gap-2 text-red-600">
|
|||
|
|
<span className="text-sm font-medium">⚠️ 로드 실패</span>
|
|||
|
|
</div>
|
|||
|
|
<p className="mt-1 text-xs text-red-500">설정 패널을 불러올 수 없습니다: {error}</p>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!ConfigPanelComponent) {
|
|||
|
|
console.warn(`⚠️ DynamicComponentConfigPanel: ${componentId} ConfigPanelComponent가 null`);
|
|||
|
|
return (
|
|||
|
|
<div className="rounded-md border border-dashed border-yellow-300 bg-yellow-50 p-4">
|
|||
|
|
<div className="flex items-center gap-2 text-yellow-600">
|
|||
|
|
<span className="text-sm font-medium">⚠️ 설정 패널 없음</span>
|
|||
|
|
</div>
|
|||
|
|
<p className="mt-1 text-xs text-yellow-500">컴포넌트 "{componentId}"에 대한 설정 패널이 없습니다.</p>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <ConfigPanelComponent config={config} onChange={onChange} />;
|
|||
|
|
};
|