웹타입 컴포넌트 분리작업
This commit is contained in:
112
frontend/lib/registry/DynamicConfigPanel.tsx
Normal file
112
frontend/lib/registry/DynamicConfigPanel.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
import { WebTypeConfigPanelProps } from "./types";
|
||||
|
||||
/**
|
||||
* 동적 설정 패널 렌더러 컴포넌트
|
||||
* 레지스트리에서 웹타입을 조회하여 해당 설정 패널을 동적으로 렌더링합니다.
|
||||
*/
|
||||
export const DynamicConfigPanel: React.FC<
|
||||
WebTypeConfigPanelProps & {
|
||||
webType: string;
|
||||
}
|
||||
> = ({ webType, component, onUpdateComponent, onUpdateProperty }) => {
|
||||
// 레지스트리에서 웹타입 정의 조회
|
||||
const webTypeDefinition = useMemo(() => {
|
||||
return WebTypeRegistry.getWebType(webType);
|
||||
}, [webType]);
|
||||
|
||||
// 웹타입이 등록되지 않은 경우
|
||||
if (!webTypeDefinition) {
|
||||
console.warn(`웹타입 "${webType}"이 레지스트리에 등록되지 않았습니다.`);
|
||||
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">웹타입 "{webType}"의 설정 패널을 찾을 수 없습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 설정 패널 컴포넌트가 없는 경우
|
||||
if (!webTypeDefinition.configPanel) {
|
||||
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">웹타입 "{webType}"에 대한 설정 패널이 구현되지 않았습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ConfigPanelComponent = webTypeDefinition.configPanel;
|
||||
|
||||
// 설정 패널 props 구성
|
||||
const configPanelProps: WebTypeConfigPanelProps = {
|
||||
component,
|
||||
onUpdateComponent,
|
||||
onUpdateProperty,
|
||||
};
|
||||
|
||||
try {
|
||||
return <ConfigPanelComponent {...configPanelProps} />;
|
||||
} catch (error) {
|
||||
console.error(`웹타입 "${webType}" 설정 패널 렌더링 중 오류 발생:`, 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">웹타입 "{webType}" 설정 패널 렌더링 중 오류가 발생했습니다.</p>
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<pre className="mt-2 overflow-auto text-xs text-red-400">
|
||||
{error instanceof Error ? error.stack : String(error)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DynamicConfigPanel.displayName = "DynamicConfigPanel";
|
||||
|
||||
/**
|
||||
* 웹타입별 설정 패널을 렌더링하는 헬퍼 함수
|
||||
*/
|
||||
export function renderConfigPanel(
|
||||
webType: string,
|
||||
component: any,
|
||||
onUpdateComponent: (component: any) => void,
|
||||
onUpdateProperty: (property: string, value: any) => void,
|
||||
): React.ReactElement | null {
|
||||
return (
|
||||
<DynamicConfigPanel
|
||||
webType={webType}
|
||||
component={component}
|
||||
onUpdateComponent={onUpdateComponent}
|
||||
onUpdateProperty={onUpdateProperty}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입이 설정 패널을 지원하는지 확인하는 헬퍼 함수
|
||||
*/
|
||||
export function hasConfigPanel(webType: string): boolean {
|
||||
const webTypeDefinition = WebTypeRegistry.getWebType(webType);
|
||||
return !!(webTypeDefinition && webTypeDefinition.configPanel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입의 기본 설정을 가져오는 헬퍼 함수
|
||||
*/
|
||||
export function getDefaultConfig(webType: string): Record<string, any> | null {
|
||||
const webTypeDefinition = WebTypeRegistry.getWebType(webType);
|
||||
return webTypeDefinition ? webTypeDefinition.defaultConfig : null;
|
||||
}
|
||||
|
||||
|
||||
148
frontend/lib/registry/DynamicWebTypeRenderer.tsx
Normal file
148
frontend/lib/registry/DynamicWebTypeRenderer.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
"use client";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
import { DynamicComponentProps } from "./types";
|
||||
import { getWidgetComponentByWebType, getWidgetComponentByName } from "@/components/screen/widgets/types";
|
||||
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
||||
|
||||
/**
|
||||
* 동적 웹타입 렌더러 컴포넌트
|
||||
* 레지스트리에서 웹타입을 조회하여 동적으로 렌더링합니다.
|
||||
*/
|
||||
export const DynamicWebTypeRenderer: React.FC<DynamicComponentProps> = ({
|
||||
webType,
|
||||
props = {},
|
||||
config = {},
|
||||
onEvent,
|
||||
}) => {
|
||||
// 모든 hooks를 먼저 호출 (조건부 return 이전에)
|
||||
const { webTypes } = useWebTypes({ active: "Y" });
|
||||
|
||||
const webTypeDefinition = useMemo(() => {
|
||||
return WebTypeRegistry.getWebType(webType);
|
||||
}, [webType]);
|
||||
|
||||
// 데이터베이스에서 웹타입 정보 조회
|
||||
const dbWebType = useMemo(() => {
|
||||
return webTypes.find((wt) => wt.web_type === webType);
|
||||
}, [webTypes, webType]);
|
||||
|
||||
// 기본 설정과 전달받은 설정을 병합 (조건부로 사용되지만 항상 계산)
|
||||
const mergedConfig = useMemo(() => {
|
||||
if (!webTypeDefinition) return config;
|
||||
return {
|
||||
...webTypeDefinition.defaultConfig,
|
||||
...config,
|
||||
};
|
||||
}, [webTypeDefinition?.defaultConfig, config]);
|
||||
|
||||
// 최종 props 구성 (조건부로 사용되지만 항상 계산)
|
||||
const finalProps = useMemo(() => {
|
||||
return {
|
||||
...props,
|
||||
webTypeConfig: mergedConfig,
|
||||
webType: webType,
|
||||
onEvent: onEvent,
|
||||
};
|
||||
}, [props, mergedConfig, webType, onEvent]);
|
||||
|
||||
// 1순위: DB에서 지정된 컴포넌트 사용 (항상 우선)
|
||||
if (dbWebType?.component_name) {
|
||||
try {
|
||||
console.log(`웹타입 "${webType}" → DB 지정 컴포넌트 "${dbWebType.component_name}" 사용`);
|
||||
console.log(`DB 웹타입 정보:`, dbWebType);
|
||||
console.log(`웹타입 데이터 배열:`, webTypes);
|
||||
const ComponentByName = getWidgetComponentByName(dbWebType.component_name);
|
||||
console.log(`컴포넌트 "${dbWebType.component_name}" 성공적으로 로드됨:`, ComponentByName);
|
||||
return <ComponentByName {...props} />;
|
||||
} catch (error) {
|
||||
console.error(`DB 지정 컴포넌트 "${dbWebType.component_name}" 렌더링 실패:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 2순위: 레지스트리에 등록된 웹타입 사용
|
||||
if (webTypeDefinition) {
|
||||
console.log(`웹타입 "${webType}" → 레지스트리 컴포넌트 사용`);
|
||||
|
||||
// 웹타입이 비활성화된 경우
|
||||
if (!webTypeDefinition.isActive) {
|
||||
console.warn(`웹타입 "${webType}"이 비활성화되어 있습니다.`);
|
||||
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">웹타입 "{webType}"이 비활성화되어 있습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Component = webTypeDefinition.component;
|
||||
try {
|
||||
return <Component {...finalProps} />;
|
||||
} catch (error) {
|
||||
console.error(`웹타입 "${webType}" 레지스트리 컴포넌트 렌더링 실패:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 3순위: 웹타입명으로 자동 매핑 (폴백)
|
||||
try {
|
||||
console.warn(`웹타입 "${webType}" → 자동 매핑 폴백 사용`);
|
||||
const FallbackComponent = getWidgetComponentByWebType(webType);
|
||||
return <FallbackComponent {...props} />;
|
||||
} catch (error) {
|
||||
console.error(`웹타입 "${webType}" 폴백 컴포넌트 렌더링 실패:`, 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">웹타입 "{webType}"을 렌더링할 수 없습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DynamicWebTypeRenderer.displayName = "DynamicWebTypeRenderer";
|
||||
|
||||
/**
|
||||
* 웹타입 미리보기 렌더러
|
||||
* 관리 페이지에서 웹타입을 미리보기할 때 사용
|
||||
*/
|
||||
export const WebTypePreviewRenderer: React.FC<{
|
||||
webType: string;
|
||||
config?: Record<string, any>;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}> = ({ webType, config = {}, size = "md" }) => {
|
||||
const webTypeDefinition = WebTypeRegistry.getWebType(webType);
|
||||
|
||||
if (!webTypeDefinition) {
|
||||
return (
|
||||
<div className="rounded border border-dashed border-gray-300 bg-gray-50 p-2 text-center">
|
||||
<span className="text-xs text-gray-500">웹타입 없음</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const sizeClasses = {
|
||||
sm: "text-xs p-1",
|
||||
md: "text-sm p-2",
|
||||
lg: "text-base p-3",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`rounded-md border ${sizeClasses[size]}`}>
|
||||
<DynamicWebTypeRenderer
|
||||
webType={webType}
|
||||
config={config}
|
||||
props={{
|
||||
placeholder: `${webTypeDefinition.name} 미리보기`,
|
||||
disabled: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WebTypePreviewRenderer.displayName = "WebTypePreviewRenderer";
|
||||
283
frontend/lib/registry/WebTypeRegistry.ts
Normal file
283
frontend/lib/registry/WebTypeRegistry.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
WebTypeDefinition,
|
||||
ButtonActionDefinition,
|
||||
RegistryEvent,
|
||||
WebTypeFilterOptions,
|
||||
ButtonActionFilterOptions,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* 웹타입 레지스트리 클래스
|
||||
* 동적으로 웹타입 컴포넌트를 등록, 관리, 조회할 수 있는 중앙 레지스트리
|
||||
*/
|
||||
export class WebTypeRegistry {
|
||||
private static webTypes = new Map<string, WebTypeDefinition>();
|
||||
private static buttonActions = new Map<string, ButtonActionDefinition>();
|
||||
private static eventListeners: Array<(event: RegistryEvent) => void> = [];
|
||||
|
||||
/**
|
||||
* 웹타입 등록
|
||||
*/
|
||||
static registerWebType(definition: WebTypeDefinition): void {
|
||||
this.webTypes.set(definition.id, definition);
|
||||
this.emitEvent({
|
||||
type: "webtype_registered",
|
||||
data: definition,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
console.log(`✅ 웹타입 등록: ${definition.id} (${definition.name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 등록 해제
|
||||
*/
|
||||
static unregisterWebType(id: string): void {
|
||||
const definition = this.webTypes.get(id);
|
||||
if (definition) {
|
||||
this.webTypes.delete(id);
|
||||
this.emitEvent({
|
||||
type: "webtype_unregistered",
|
||||
data: definition,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
console.log(`❌ 웹타입 등록 해제: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 조회
|
||||
*/
|
||||
static getWebType(id: string): WebTypeDefinition | undefined {
|
||||
return this.webTypes.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 웹타입 조회
|
||||
*/
|
||||
static getAllWebTypes(): WebTypeDefinition[] {
|
||||
return Array.from(this.webTypes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 필터링
|
||||
*/
|
||||
static getWebTypes(options: WebTypeFilterOptions = {}): WebTypeDefinition[] {
|
||||
let webTypes = this.getAllWebTypes();
|
||||
|
||||
// 활성화 상태 필터
|
||||
if (options.isActive !== undefined) {
|
||||
webTypes = webTypes.filter((wt) => wt.isActive === options.isActive);
|
||||
}
|
||||
|
||||
// 카테고리 필터
|
||||
if (options.category) {
|
||||
webTypes = webTypes.filter((wt) => wt.category === options.category);
|
||||
}
|
||||
|
||||
// 검색어 필터
|
||||
if (options.search) {
|
||||
const searchLower = options.search.toLowerCase();
|
||||
webTypes = webTypes.filter(
|
||||
(wt) =>
|
||||
wt.name.toLowerCase().includes(searchLower) ||
|
||||
wt.description.toLowerCase().includes(searchLower) ||
|
||||
wt.id.toLowerCase().includes(searchLower),
|
||||
);
|
||||
}
|
||||
|
||||
// 태그 필터
|
||||
if (options.tags && options.tags.length > 0) {
|
||||
webTypes = webTypes.filter((wt) => wt.tags && wt.tags.some((tag) => options.tags!.includes(tag)));
|
||||
}
|
||||
|
||||
return webTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 카테고리별 웹타입 그룹화
|
||||
*/
|
||||
static getWebTypesByCategory(): Record<string, WebTypeDefinition[]> {
|
||||
const webTypes = this.getWebTypes({ isActive: true });
|
||||
const grouped: Record<string, WebTypeDefinition[]> = {};
|
||||
|
||||
webTypes.forEach((webType) => {
|
||||
if (!grouped[webType.category]) {
|
||||
grouped[webType.category] = [];
|
||||
}
|
||||
grouped[webType.category].push(webType);
|
||||
});
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 존재 여부 확인
|
||||
*/
|
||||
static hasWebType(id: string): boolean {
|
||||
return this.webTypes.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 등록
|
||||
*/
|
||||
static registerButtonAction(definition: ButtonActionDefinition): void {
|
||||
this.buttonActions.set(definition.id, definition);
|
||||
this.emitEvent({
|
||||
type: "buttonaction_registered",
|
||||
data: definition,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
console.log(`✅ 버튼 액션 등록: ${definition.id} (${definition.name})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 등록 해제
|
||||
*/
|
||||
static unregisterButtonAction(id: string): void {
|
||||
const definition = this.buttonActions.get(id);
|
||||
if (definition) {
|
||||
this.buttonActions.delete(id);
|
||||
this.emitEvent({
|
||||
type: "buttonaction_unregistered",
|
||||
data: definition,
|
||||
timestamp: new Date(),
|
||||
});
|
||||
console.log(`❌ 버튼 액션 등록 해제: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 조회
|
||||
*/
|
||||
static getButtonAction(id: string): ButtonActionDefinition | undefined {
|
||||
return this.buttonActions.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 버튼 액션 조회
|
||||
*/
|
||||
static getAllButtonActions(): ButtonActionDefinition[] {
|
||||
return Array.from(this.buttonActions.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 필터링
|
||||
*/
|
||||
static getButtonActions(options: ButtonActionFilterOptions = {}): ButtonActionDefinition[] {
|
||||
let buttonActions = this.getAllButtonActions();
|
||||
|
||||
// 활성화 상태 필터
|
||||
if (options.isActive !== undefined) {
|
||||
buttonActions = buttonActions.filter((ba) => ba.isActive === options.isActive);
|
||||
}
|
||||
|
||||
// 카테고리 필터
|
||||
if (options.category) {
|
||||
buttonActions = buttonActions.filter((ba) => ba.category === options.category);
|
||||
}
|
||||
|
||||
// 검색어 필터
|
||||
if (options.search) {
|
||||
const searchLower = options.search.toLowerCase();
|
||||
buttonActions = buttonActions.filter(
|
||||
(ba) =>
|
||||
ba.name.toLowerCase().includes(searchLower) ||
|
||||
ba.description.toLowerCase().includes(searchLower) ||
|
||||
ba.id.toLowerCase().includes(searchLower),
|
||||
);
|
||||
}
|
||||
|
||||
// 확인 필요 여부 필터
|
||||
if (options.requiresConfirmation !== undefined) {
|
||||
buttonActions = buttonActions.filter((ba) => ba.requiresConfirmation === options.requiresConfirmation);
|
||||
}
|
||||
|
||||
return buttonActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 리스너 등록
|
||||
*/
|
||||
static subscribe(callback: (event: RegistryEvent) => void): () => void {
|
||||
this.eventListeners.push(callback);
|
||||
|
||||
// 구독 해제 함수 반환
|
||||
return () => {
|
||||
const index = this.eventListeners.indexOf(callback);
|
||||
if (index > -1) {
|
||||
this.eventListeners.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 이벤트 발생
|
||||
*/
|
||||
private static emitEvent(event: RegistryEvent): void {
|
||||
this.eventListeners.forEach((listener) => {
|
||||
try {
|
||||
listener(event);
|
||||
} catch (error) {
|
||||
console.error("레지스트리 이벤트 리스너 오류:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 상태 정보
|
||||
*/
|
||||
static getRegistryInfo() {
|
||||
return {
|
||||
webTypesCount: this.webTypes.size,
|
||||
buttonActionsCount: this.buttonActions.size,
|
||||
activeWebTypesCount: this.getWebTypes({ isActive: true }).length,
|
||||
activeButtonActionsCount: this.getButtonActions({ isActive: true }).length,
|
||||
categories: [...new Set(this.getAllWebTypes().map((wt) => wt.category))],
|
||||
lastUpdated: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 초기화 (개발/테스트용)
|
||||
*/
|
||||
static clear(): void {
|
||||
this.webTypes.clear();
|
||||
this.buttonActions.clear();
|
||||
this.eventListeners.length = 0;
|
||||
console.log("🧹 레지스트리 초기화됨");
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 내용을 JSON으로 내보내기
|
||||
*/
|
||||
static exportToJSON() {
|
||||
return {
|
||||
webTypes: Object.fromEntries(
|
||||
Array.from(this.webTypes.entries()).map(([id, def]) => [
|
||||
id,
|
||||
{
|
||||
...def,
|
||||
// 함수/컴포넌트는 제외하고 메타데이터만 내보내기
|
||||
component: def.component.name || "Unknown",
|
||||
configPanel: def.configPanel.name || "Unknown",
|
||||
},
|
||||
]),
|
||||
),
|
||||
buttonActions: Object.fromEntries(
|
||||
Array.from(this.buttonActions.entries()).map(([id, def]) => [
|
||||
id,
|
||||
{
|
||||
...def,
|
||||
// 함수는 제외하고 메타데이터만 내보내기
|
||||
handler: def.handler.name || "Unknown",
|
||||
},
|
||||
]),
|
||||
),
|
||||
exportedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
frontend/lib/registry/index.ts
Normal file
37
frontend/lib/registry/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Registry system exports
|
||||
export { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
export { DynamicWebTypeRenderer, WebTypePreviewRenderer } from "./DynamicWebTypeRenderer";
|
||||
export { DynamicConfigPanel, renderConfigPanel, hasConfigPanel, getDefaultConfig } from "./DynamicConfigPanel";
|
||||
|
||||
// Registry hooks
|
||||
export {
|
||||
useRegistry,
|
||||
useWebTypes,
|
||||
useButtonActions,
|
||||
useWebTypesByCategory,
|
||||
useRegistryInfo,
|
||||
useWebTypeExists,
|
||||
} from "./useRegistry";
|
||||
|
||||
// Initialization
|
||||
export { initializeRegistries, initializeWebTypeRegistry } from "./init";
|
||||
|
||||
// Type definitions
|
||||
export type {
|
||||
WebTypeDefinition,
|
||||
ButtonActionDefinition,
|
||||
WebTypeComponentProps,
|
||||
WebTypeConfigPanelProps,
|
||||
ButtonActionContext,
|
||||
DynamicComponentProps,
|
||||
RegistryEvent,
|
||||
RegistryEventType,
|
||||
UseRegistryReturn,
|
||||
WebTypeFilterOptions,
|
||||
ButtonActionFilterOptions,
|
||||
WebTypeCategory,
|
||||
ButtonActionCategory,
|
||||
} from "./types";
|
||||
|
||||
// Component registry types
|
||||
export type { WidgetComponent } from "@/types/screen";
|
||||
401
frontend/lib/registry/init.ts
Normal file
401
frontend/lib/registry/init.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
"use client";
|
||||
|
||||
import { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
|
||||
// 개별적으로 위젯 컴포넌트들을 import
|
||||
import { TextWidget } from "@/components/screen/widgets/types/TextWidget";
|
||||
import { NumberWidget } from "@/components/screen/widgets/types/NumberWidget";
|
||||
import { DateWidget } from "@/components/screen/widgets/types/DateWidget";
|
||||
import { SelectWidget } from "@/components/screen/widgets/types/SelectWidget";
|
||||
import { TextareaWidget } from "@/components/screen/widgets/types/TextareaWidget";
|
||||
import { CheckboxWidget } from "@/components/screen/widgets/types/CheckboxWidget";
|
||||
import { RadioWidget } from "@/components/screen/widgets/types/RadioWidget";
|
||||
import { FileWidget } from "@/components/screen/widgets/types/FileWidget";
|
||||
import { CodeWidget } from "@/components/screen/widgets/types/CodeWidget";
|
||||
import { EntityWidget } from "@/components/screen/widgets/types/EntityWidget";
|
||||
import { ButtonWidget } from "@/components/screen/widgets/types/ButtonWidget";
|
||||
|
||||
// 개별적으로 설정 패널들을 import
|
||||
import { TextConfigPanel } from "@/components/screen/config-panels/TextConfigPanel";
|
||||
import { NumberConfigPanel } from "@/components/screen/config-panels/NumberConfigPanel";
|
||||
import { DateConfigPanel } from "@/components/screen/config-panels/DateConfigPanel";
|
||||
import { SelectConfigPanel } from "@/components/screen/config-panels/SelectConfigPanel";
|
||||
import { TextareaConfigPanel } from "@/components/screen/config-panels/TextareaConfigPanel";
|
||||
import { CheckboxConfigPanel } from "@/components/screen/config-panels/CheckboxConfigPanel";
|
||||
import { RadioConfigPanel } from "@/components/screen/config-panels/RadioConfigPanel";
|
||||
import { FileConfigPanel } from "@/components/screen/config-panels/FileConfigPanel";
|
||||
import { CodeConfigPanel } from "@/components/screen/config-panels/CodeConfigPanel";
|
||||
import { EntityConfigPanel } from "@/components/screen/config-panels/EntityConfigPanel";
|
||||
import { ButtonConfigPanel } from "@/components/screen/config-panels/ButtonConfigPanel";
|
||||
|
||||
/**
|
||||
* 웹타입 레지스트리 초기화
|
||||
* 모든 기본 웹타입 컴포넌트와 설정 패널을 등록합니다.
|
||||
*/
|
||||
export function initializeWebTypeRegistry() {
|
||||
// Text-based types
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "text",
|
||||
name: "텍스트",
|
||||
category: "input",
|
||||
description: "단일 라인 텍스트 입력 필드",
|
||||
component: TextWidget,
|
||||
configPanel: TextConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "텍스트를 입력하세요",
|
||||
maxLength: 255,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "email",
|
||||
name: "이메일",
|
||||
category: "input",
|
||||
description: "이메일 주소 입력 필드",
|
||||
component: TextWidget,
|
||||
configPanel: TextConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "이메일을 입력하세요",
|
||||
inputType: "email",
|
||||
validation: "email",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "password",
|
||||
name: "비밀번호",
|
||||
category: "input",
|
||||
description: "비밀번호 입력 필드",
|
||||
component: TextWidget,
|
||||
configPanel: TextConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "비밀번호를 입력하세요",
|
||||
inputType: "password",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "tel",
|
||||
name: "전화번호",
|
||||
category: "input",
|
||||
description: "전화번호 입력 필드",
|
||||
component: TextWidget,
|
||||
configPanel: TextConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "전화번호를 입력하세요",
|
||||
inputType: "tel",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Number types
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "number",
|
||||
name: "숫자",
|
||||
category: "input",
|
||||
description: "정수 입력 필드",
|
||||
component: NumberWidget,
|
||||
configPanel: NumberConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "숫자를 입력하세요",
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
step: 1,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "decimal",
|
||||
name: "소수",
|
||||
category: "input",
|
||||
description: "소수점 숫자 입력 필드",
|
||||
component: NumberWidget,
|
||||
configPanel: NumberConfigPanel,
|
||||
defaultConfig: {
|
||||
placeholder: "소수를 입력하세요",
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
step: 0.01,
|
||||
decimalPlaces: 2,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Date types
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "date",
|
||||
name: "날짜",
|
||||
category: "input",
|
||||
description: "날짜 선택 필드",
|
||||
component: DateWidget,
|
||||
configPanel: DateConfigPanel,
|
||||
defaultConfig: {
|
||||
format: "YYYY-MM-DD",
|
||||
showTime: false,
|
||||
placeholder: "날짜를 선택하세요",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "datetime",
|
||||
name: "날짜시간",
|
||||
category: "input",
|
||||
description: "날짜와 시간 선택 필드",
|
||||
component: DateWidget,
|
||||
configPanel: DateConfigPanel,
|
||||
defaultConfig: {
|
||||
format: "YYYY-MM-DD HH:mm",
|
||||
showTime: true,
|
||||
placeholder: "날짜와 시간을 선택하세요",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Selection types
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "select",
|
||||
name: "선택박스",
|
||||
category: "input",
|
||||
description: "드롭다운 선택 필드",
|
||||
component: SelectWidget,
|
||||
configPanel: SelectConfigPanel,
|
||||
defaultConfig: {
|
||||
options: [
|
||||
{ label: "옵션 1", value: "option1" },
|
||||
{ label: "옵션 2", value: "option2" },
|
||||
],
|
||||
multiple: false,
|
||||
searchable: false,
|
||||
placeholder: "선택하세요",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "dropdown",
|
||||
name: "드롭다운",
|
||||
category: "input",
|
||||
description: "검색 가능한 드롭다운 필드",
|
||||
component: SelectWidget,
|
||||
configPanel: SelectConfigPanel,
|
||||
defaultConfig: {
|
||||
options: [
|
||||
{ label: "옵션 1", value: "option1" },
|
||||
{ label: "옵션 2", value: "option2" },
|
||||
],
|
||||
multiple: false,
|
||||
searchable: true,
|
||||
placeholder: "검색하여 선택하세요",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Text area
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "textarea",
|
||||
name: "텍스트영역",
|
||||
category: "input",
|
||||
description: "여러 줄 텍스트 입력 필드",
|
||||
component: TextareaWidget,
|
||||
configPanel: TextareaConfigPanel,
|
||||
defaultConfig: {
|
||||
rows: 4,
|
||||
placeholder: "내용을 입력하세요",
|
||||
resizable: true,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "text_area",
|
||||
name: "텍스트 영역",
|
||||
category: "input",
|
||||
description: "여러 줄 텍스트 입력 필드 (언더스코어 형식)",
|
||||
component: TextareaWidget,
|
||||
configPanel: TextareaConfigPanel,
|
||||
defaultConfig: {
|
||||
rows: 4,
|
||||
placeholder: "내용을 입력하세요",
|
||||
resizable: true,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Boolean/Checkbox types
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "boolean",
|
||||
name: "불린",
|
||||
category: "input",
|
||||
description: "참/거짓 선택 체크박스",
|
||||
component: CheckboxWidget,
|
||||
configPanel: CheckboxConfigPanel,
|
||||
defaultConfig: {
|
||||
label: "선택",
|
||||
checkedValue: true,
|
||||
uncheckedValue: false,
|
||||
defaultChecked: false,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "checkbox",
|
||||
name: "체크박스",
|
||||
category: "input",
|
||||
description: "체크박스 입력 필드",
|
||||
component: CheckboxWidget,
|
||||
configPanel: CheckboxConfigPanel,
|
||||
defaultConfig: {
|
||||
label: "체크박스",
|
||||
checkedValue: "Y",
|
||||
uncheckedValue: "N",
|
||||
defaultChecked: false,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Radio button
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "radio",
|
||||
name: "라디오버튼",
|
||||
category: "input",
|
||||
description: "라디오버튼 그룹 선택 필드",
|
||||
component: RadioWidget,
|
||||
configPanel: RadioConfigPanel,
|
||||
defaultConfig: {
|
||||
options: [
|
||||
{ label: "옵션 1", value: "option1" },
|
||||
{ label: "옵션 2", value: "option2" },
|
||||
],
|
||||
inline: true,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// File upload
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "file",
|
||||
name: "파일 업로드",
|
||||
category: "input",
|
||||
description: "파일 업로드 필드",
|
||||
component: FileWidget,
|
||||
configPanel: FileConfigPanel,
|
||||
defaultConfig: {
|
||||
multiple: false,
|
||||
maxFileSize: 10, // MB
|
||||
acceptedTypes: [],
|
||||
showPreview: true,
|
||||
dragAndDrop: true,
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Code editor
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "code",
|
||||
name: "코드 에디터",
|
||||
category: "input",
|
||||
description: "코드 편집 필드",
|
||||
component: CodeWidget,
|
||||
configPanel: CodeConfigPanel,
|
||||
defaultConfig: {
|
||||
language: "javascript",
|
||||
theme: "light",
|
||||
showLineNumbers: true,
|
||||
height: 300,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Entity selection
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "entity",
|
||||
name: "엔티티 선택",
|
||||
category: "input",
|
||||
description: "데이터베이스 엔티티 선택 필드",
|
||||
component: EntityWidget,
|
||||
configPanel: EntityConfigPanel,
|
||||
defaultConfig: {
|
||||
entityType: "",
|
||||
valueField: "id",
|
||||
labelField: "name",
|
||||
multiple: false,
|
||||
searchable: true,
|
||||
placeholder: "엔티티를 선택하세요",
|
||||
required: false,
|
||||
readonly: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
// Button
|
||||
WebTypeRegistry.registerWebType({
|
||||
id: "button",
|
||||
name: "버튼",
|
||||
category: "action",
|
||||
description: "클릭 가능한 버튼 컴포넌트",
|
||||
component: ButtonWidget,
|
||||
configPanel: ButtonConfigPanel,
|
||||
defaultConfig: {
|
||||
label: "버튼",
|
||||
text: "",
|
||||
tooltip: "",
|
||||
variant: "primary",
|
||||
size: "medium",
|
||||
disabled: false,
|
||||
fullWidth: false,
|
||||
},
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
console.log("웹타입 레지스트리 초기화 완료:", WebTypeRegistry.getAllWebTypes().length, "개 웹타입 등록됨");
|
||||
}
|
||||
|
||||
/**
|
||||
* 애플리케이션 시작 시 호출되어야 하는 초기화 함수
|
||||
*/
|
||||
export function initializeRegistries() {
|
||||
initializeWebTypeRegistry();
|
||||
|
||||
// 필요한 경우 버튼 액션 레지스트리도 여기서 초기화
|
||||
// initializeButtonActionRegistry();
|
||||
}
|
||||
198
frontend/lib/registry/types.ts
Normal file
198
frontend/lib/registry/types.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* 웹타입 정의 인터페이스
|
||||
*/
|
||||
export interface WebTypeDefinition {
|
||||
/** 고유 식별자 */
|
||||
id: string;
|
||||
/** 표시 이름 */
|
||||
name: string;
|
||||
/** 카테고리 (input, display, layout 등) */
|
||||
category: string;
|
||||
/** 설명 */
|
||||
description: string;
|
||||
/** 렌더링 컴포넌트 */
|
||||
component: React.ComponentType<any>;
|
||||
/** 설정 패널 컴포넌트 */
|
||||
configPanel: React.ComponentType<WebTypeConfigPanelProps>;
|
||||
/** 기본 설정값 */
|
||||
defaultConfig: Record<string, any>;
|
||||
/** 활성화 상태 */
|
||||
isActive: boolean;
|
||||
/** 아이콘 (선택사항) */
|
||||
icon?: React.ComponentType<any>;
|
||||
/** 태그 (선택사항) */
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 정의 인터페이스
|
||||
*/
|
||||
export interface ButtonActionDefinition {
|
||||
/** 고유 식별자 */
|
||||
id: string;
|
||||
/** 표시 이름 */
|
||||
name: string;
|
||||
/** 카테고리 (save, delete, navigate 등) */
|
||||
category: string;
|
||||
/** 설명 */
|
||||
description: string;
|
||||
/** 핸들러 함수 */
|
||||
handler: (context: ButtonActionContext) => void | Promise<void>;
|
||||
/** 기본 설정값 */
|
||||
defaultConfig: Record<string, any>;
|
||||
/** 활성화 상태 */
|
||||
isActive: boolean;
|
||||
/** 아이콘 (선택사항) */
|
||||
icon?: React.ComponentType<any>;
|
||||
/** 확인 메시지 필요 여부 */
|
||||
requiresConfirmation?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 컴포넌트 Props
|
||||
*/
|
||||
export interface WebTypeComponentProps {
|
||||
/** 컴포넌트 객체 */
|
||||
component: any;
|
||||
/** 현재 값 */
|
||||
value?: any;
|
||||
/** 값 변경 핸들러 */
|
||||
onChange?: (value: any) => void;
|
||||
/** 읽기 전용 모드 */
|
||||
readonly?: boolean;
|
||||
/** 추가 속성들 */
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 설정 패널 Props
|
||||
*/
|
||||
export interface WebTypeConfigPanelProps {
|
||||
/** 컴포넌트 객체 */
|
||||
component: any;
|
||||
/** 컴포넌트 업데이트 핸들러 */
|
||||
onUpdateComponent: (component: any) => void;
|
||||
/** 속성 업데이트 핸들러 */
|
||||
onUpdateProperty: (property: string, value: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 실행 컨텍스트
|
||||
*/
|
||||
export interface ButtonActionContext {
|
||||
/** 현재 화면 데이터 */
|
||||
screenData: Record<string, any>;
|
||||
/** 선택된 데이터 */
|
||||
selectedData?: Record<string, any>;
|
||||
/** 화면 설정 */
|
||||
screenConfig: Record<string, any>;
|
||||
/** 사용자 정보 */
|
||||
user: any;
|
||||
/** 네비게이션 함수 */
|
||||
navigate: (path: string) => void;
|
||||
/** 메시지 표시 함수 */
|
||||
showMessage: (message: string, type?: "success" | "error" | "warning" | "info") => void;
|
||||
/** API 호출 함수 */
|
||||
api: {
|
||||
get: (url: string, params?: any) => Promise<any>;
|
||||
post: (url: string, data?: any) => Promise<any>;
|
||||
put: (url: string, data?: any) => Promise<any>;
|
||||
delete: (url: string) => Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 이벤트 타입
|
||||
*/
|
||||
export type RegistryEventType =
|
||||
| "webtype_registered"
|
||||
| "webtype_unregistered"
|
||||
| "buttonaction_registered"
|
||||
| "buttonaction_unregistered";
|
||||
|
||||
/**
|
||||
* 레지스트리 이벤트 인터페이스
|
||||
*/
|
||||
export interface RegistryEvent {
|
||||
type: RegistryEventType;
|
||||
data: WebTypeDefinition | ButtonActionDefinition;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 카테고리
|
||||
*/
|
||||
export type WebTypeCategory = "input" | "display" | "layout" | "media" | "data" | "action";
|
||||
|
||||
/**
|
||||
* 버튼 액션 카테고리
|
||||
*/
|
||||
export type ButtonActionCategory = "save" | "delete" | "navigate" | "export" | "import" | "custom";
|
||||
|
||||
/**
|
||||
* 동적 컴포넌트 렌더러 Props
|
||||
*/
|
||||
export interface DynamicComponentProps {
|
||||
/** 웹타입 ID */
|
||||
webType: string;
|
||||
/** 컴포넌트 속성 */
|
||||
props: Record<string, any>;
|
||||
/** 컴포넌트 설정 */
|
||||
config?: Record<string, any>;
|
||||
/** 이벤트 핸들러 */
|
||||
onEvent?: (event: string, data: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 훅 반환 타입
|
||||
*/
|
||||
export interface UseRegistryReturn {
|
||||
/** 등록된 웹타입 목록 */
|
||||
webTypes: WebTypeDefinition[];
|
||||
/** 등록된 버튼 액션 목록 */
|
||||
buttonActions: ButtonActionDefinition[];
|
||||
/** 웹타입 등록 */
|
||||
registerWebType: (definition: WebTypeDefinition) => void;
|
||||
/** 웹타입 등록 해제 */
|
||||
unregisterWebType: (id: string) => void;
|
||||
/** 버튼 액션 등록 */
|
||||
registerButtonAction: (definition: ButtonActionDefinition) => void;
|
||||
/** 버튼 액션 등록 해제 */
|
||||
unregisterButtonAction: (id: string) => void;
|
||||
/** 웹타입 조회 */
|
||||
getWebType: (id: string) => WebTypeDefinition | undefined;
|
||||
/** 버튼 액션 조회 */
|
||||
getButtonAction: (id: string) => ButtonActionDefinition | undefined;
|
||||
/** 이벤트 구독 */
|
||||
subscribe: (callback: (event: RegistryEvent) => void) => () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입 필터 옵션
|
||||
*/
|
||||
export interface WebTypeFilterOptions {
|
||||
/** 카테고리 필터 */
|
||||
category?: WebTypeCategory;
|
||||
/** 활성화 상태 필터 */
|
||||
isActive?: boolean;
|
||||
/** 검색어 */
|
||||
search?: string;
|
||||
/** 태그 필터 */
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 필터 옵션
|
||||
*/
|
||||
export interface ButtonActionFilterOptions {
|
||||
/** 카테고리 필터 */
|
||||
category?: ButtonActionCategory;
|
||||
/** 활성화 상태 필터 */
|
||||
isActive?: boolean;
|
||||
/** 검색어 */
|
||||
search?: string;
|
||||
/** 확인 필요 여부 필터 */
|
||||
requiresConfirmation?: boolean;
|
||||
}
|
||||
221
frontend/lib/registry/useRegistry.ts
Normal file
221
frontend/lib/registry/useRegistry.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { WebTypeRegistry } from "./WebTypeRegistry";
|
||||
import {
|
||||
WebTypeDefinition,
|
||||
ButtonActionDefinition,
|
||||
UseRegistryReturn,
|
||||
WebTypeFilterOptions,
|
||||
ButtonActionFilterOptions,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* 레지스트리 관리를 위한 React 훅
|
||||
*/
|
||||
export function useRegistry(): UseRegistryReturn {
|
||||
const [webTypes, setWebTypes] = useState<WebTypeDefinition[]>([]);
|
||||
const [buttonActions, setButtonActions] = useState<ButtonActionDefinition[]>([]);
|
||||
|
||||
// 웹타입 목록 업데이트
|
||||
const updateWebTypes = useCallback(() => {
|
||||
setWebTypes(WebTypeRegistry.getAllWebTypes());
|
||||
}, []);
|
||||
|
||||
// 버튼 액션 목록 업데이트
|
||||
const updateButtonActions = useCallback(() => {
|
||||
setButtonActions(WebTypeRegistry.getAllButtonActions());
|
||||
}, []);
|
||||
|
||||
// 레지스트리 이벤트 구독
|
||||
useEffect(() => {
|
||||
// 초기 데이터 로드
|
||||
updateWebTypes();
|
||||
updateButtonActions();
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
const unsubscribe = WebTypeRegistry.subscribe((event) => {
|
||||
if (event.type === "webtype_registered" || event.type === "webtype_unregistered") {
|
||||
updateWebTypes();
|
||||
} else if (event.type === "buttonaction_registered" || event.type === "buttonaction_unregistered") {
|
||||
updateButtonActions();
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [updateWebTypes, updateButtonActions]);
|
||||
|
||||
// 웹타입 등록
|
||||
const registerWebType = useCallback((definition: WebTypeDefinition) => {
|
||||
WebTypeRegistry.registerWebType(definition);
|
||||
}, []);
|
||||
|
||||
// 웹타입 등록 해제
|
||||
const unregisterWebType = useCallback((id: string) => {
|
||||
WebTypeRegistry.unregisterWebType(id);
|
||||
}, []);
|
||||
|
||||
// 버튼 액션 등록
|
||||
const registerButtonAction = useCallback((definition: ButtonActionDefinition) => {
|
||||
WebTypeRegistry.registerButtonAction(definition);
|
||||
}, []);
|
||||
|
||||
// 버튼 액션 등록 해제
|
||||
const unregisterButtonAction = useCallback((id: string) => {
|
||||
WebTypeRegistry.unregisterButtonAction(id);
|
||||
}, []);
|
||||
|
||||
// 웹타입 조회
|
||||
const getWebType = useCallback((id: string) => {
|
||||
return WebTypeRegistry.getWebType(id);
|
||||
}, []);
|
||||
|
||||
// 버튼 액션 조회
|
||||
const getButtonAction = useCallback((id: string) => {
|
||||
return WebTypeRegistry.getButtonAction(id);
|
||||
}, []);
|
||||
|
||||
// 이벤트 구독
|
||||
const subscribe = useCallback((callback: (event: any) => void) => {
|
||||
return WebTypeRegistry.subscribe(callback);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
webTypes,
|
||||
buttonActions,
|
||||
registerWebType,
|
||||
unregisterWebType,
|
||||
registerButtonAction,
|
||||
unregisterButtonAction,
|
||||
getWebType,
|
||||
getButtonAction,
|
||||
subscribe,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 필터링된 웹타입을 가져오는 훅
|
||||
*/
|
||||
export function useWebTypes(options: WebTypeFilterOptions = {}) {
|
||||
const [webTypes, setWebTypes] = useState<WebTypeDefinition[]>([]);
|
||||
|
||||
const filteredWebTypes = useMemo(() => {
|
||||
return WebTypeRegistry.getWebTypes(options);
|
||||
}, [options]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentWebTypes = WebTypeRegistry.getWebTypes(options);
|
||||
setWebTypes(currentWebTypes);
|
||||
|
||||
// 레지스트리 변경 감지
|
||||
const unsubscribe = WebTypeRegistry.subscribe((event) => {
|
||||
if (event.type === "webtype_registered" || event.type === "webtype_unregistered") {
|
||||
setWebTypes(WebTypeRegistry.getWebTypes(options));
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [options]);
|
||||
|
||||
return webTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 필터링된 버튼 액션을 가져오는 훅
|
||||
*/
|
||||
export function useButtonActions(options: ButtonActionFilterOptions = {}) {
|
||||
const [buttonActions, setButtonActions] = useState<ButtonActionDefinition[]>([]);
|
||||
|
||||
const filteredButtonActions = useMemo(() => {
|
||||
return WebTypeRegistry.getButtonActions(options);
|
||||
}, [options]);
|
||||
|
||||
useEffect(() => {
|
||||
setButtonActions(filteredButtonActions);
|
||||
|
||||
// 레지스트리 변경 감지
|
||||
const unsubscribe = WebTypeRegistry.subscribe((event) => {
|
||||
if (event.type === "buttonaction_registered" || event.type === "buttonaction_unregistered") {
|
||||
setButtonActions(WebTypeRegistry.getButtonActions(options));
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [filteredButtonActions, options]);
|
||||
|
||||
return buttonActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 웹타입별로 그룹화된 데이터를 가져오는 훅
|
||||
*/
|
||||
export function useWebTypesByCategory() {
|
||||
const [groupedWebTypes, setGroupedWebTypes] = useState<Record<string, WebTypeDefinition[]>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const updateGroupedWebTypes = () => {
|
||||
setGroupedWebTypes(WebTypeRegistry.getWebTypesByCategory());
|
||||
};
|
||||
|
||||
updateGroupedWebTypes();
|
||||
|
||||
// 레지스트리 변경 감지
|
||||
const unsubscribe = WebTypeRegistry.subscribe((event) => {
|
||||
if (event.type === "webtype_registered" || event.type === "webtype_unregistered") {
|
||||
updateGroupedWebTypes();
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
return groupedWebTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레지스트리 상태 정보를 가져오는 훅
|
||||
*/
|
||||
export function useRegistryInfo() {
|
||||
const [registryInfo, setRegistryInfo] = useState(WebTypeRegistry.getRegistryInfo());
|
||||
|
||||
useEffect(() => {
|
||||
const updateRegistryInfo = () => {
|
||||
setRegistryInfo(WebTypeRegistry.getRegistryInfo());
|
||||
};
|
||||
|
||||
// 레지스트리 변경 감지
|
||||
const unsubscribe = WebTypeRegistry.subscribe(() => {
|
||||
updateRegistryInfo();
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, []);
|
||||
|
||||
return registryInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 웹타입의 존재 여부를 확인하는 훅
|
||||
*/
|
||||
export function useWebTypeExists(webTypeId: string) {
|
||||
const [exists, setExists] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkExists = () => {
|
||||
setExists(WebTypeRegistry.hasWebType(webTypeId));
|
||||
};
|
||||
|
||||
checkExists();
|
||||
|
||||
// 레지스트리 변경 감지
|
||||
const unsubscribe = WebTypeRegistry.subscribe((event) => {
|
||||
if (event.type === "webtype_registered" || event.type === "webtype_unregistered") {
|
||||
checkExists();
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [webTypeId]);
|
||||
|
||||
return exists;
|
||||
}
|
||||
Reference in New Issue
Block a user