325 lines
12 KiB
TypeScript
325 lines
12 KiB
TypeScript
/**
|
|
* V2 레이아웃 변환 유틸리티
|
|
*
|
|
* 기존 LayoutData ↔ V2 LayoutData 변환
|
|
*/
|
|
|
|
import {
|
|
ComponentV2,
|
|
LayoutV2,
|
|
getComponentUrl,
|
|
getComponentTypeFromUrl,
|
|
getDefaultsByUrl,
|
|
mergeComponentConfig,
|
|
extractCustomConfig,
|
|
} from "@/lib/schemas/componentConfig";
|
|
|
|
// 기존 ComponentData 타입 (간략화)
|
|
interface LegacyComponentData {
|
|
id: string;
|
|
componentType?: string;
|
|
widgetType?: string;
|
|
type?: string;
|
|
position?: { x: number; y: number };
|
|
size?: { width: number; height: number };
|
|
componentConfig?: Record<string, any>;
|
|
[key: string]: any;
|
|
}
|
|
|
|
interface LegacyLayoutData {
|
|
components: LegacyComponentData[];
|
|
gridSettings?: any;
|
|
screenResolution?: any;
|
|
metadata?: any;
|
|
}
|
|
|
|
// ============================================
|
|
// 중첩 컴포넌트 기본값 적용 헬퍼 함수 (재귀적)
|
|
// ============================================
|
|
function applyDefaultsToNestedComponents(components: any[]): any[] {
|
|
if (!Array.isArray(components)) return components;
|
|
|
|
return components.map((nestedComp: any) => {
|
|
if (!nestedComp) return nestedComp;
|
|
|
|
// 중첩 컴포넌트의 타입 확인 (componentType 또는 url에서 추출)
|
|
let nestedComponentType = nestedComp.componentType;
|
|
if (!nestedComponentType && nestedComp.url) {
|
|
nestedComponentType = getComponentTypeFromUrl(nestedComp.url);
|
|
}
|
|
|
|
// 결과 객체 초기화 (원본 복사)
|
|
let result = { ...nestedComp };
|
|
|
|
// 🆕 탭 위젯인 경우 재귀적으로 탭 내부 컴포넌트도 처리
|
|
if (nestedComponentType === "v2-tabs-widget") {
|
|
const config = result.componentConfig || {};
|
|
if (config.tabs && Array.isArray(config.tabs)) {
|
|
result.componentConfig = {
|
|
...config,
|
|
tabs: config.tabs.map((tab: any) => {
|
|
if (tab?.components && Array.isArray(tab.components)) {
|
|
return {
|
|
...tab,
|
|
components: applyDefaultsToNestedComponents(tab.components),
|
|
};
|
|
}
|
|
return tab;
|
|
}),
|
|
};
|
|
}
|
|
}
|
|
|
|
// 🆕 분할 패널인 경우 재귀적으로 내부 컴포넌트도 처리
|
|
if (nestedComponentType === "v2-split-panel-layout") {
|
|
const config = result.componentConfig || {};
|
|
result.componentConfig = {
|
|
...config,
|
|
leftPanel: config.leftPanel ? {
|
|
...config.leftPanel,
|
|
components: applyDefaultsToNestedComponents(config.leftPanel.components || []),
|
|
} : config.leftPanel,
|
|
rightPanel: config.rightPanel ? {
|
|
...config.rightPanel,
|
|
components: applyDefaultsToNestedComponents(config.rightPanel.components || []),
|
|
} : config.rightPanel,
|
|
};
|
|
}
|
|
|
|
// 컴포넌트 타입이 없으면 그대로 반환
|
|
if (!nestedComponentType) {
|
|
return result;
|
|
}
|
|
|
|
// 중첩 컴포넌트의 기본값 가져오기
|
|
const nestedDefaults = getDefaultsByUrl(`registry://${nestedComponentType}`);
|
|
|
|
// componentConfig가 있으면 기본값과 병합
|
|
if (result.componentConfig && Object.keys(nestedDefaults).length > 0) {
|
|
const mergedNestedConfig = mergeComponentConfig(nestedDefaults, result.componentConfig);
|
|
return {
|
|
...result,
|
|
componentConfig: mergedNestedConfig,
|
|
};
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|
|
|
|
// ============================================
|
|
// 분할 패널 내부 컴포넌트 기본값 적용
|
|
// ============================================
|
|
function applyDefaultsToSplitPanelComponents(mergedConfig: Record<string, any>): Record<string, any> {
|
|
const result = { ...mergedConfig };
|
|
|
|
// leftPanel.components 처리
|
|
if (result.leftPanel?.components) {
|
|
result.leftPanel = {
|
|
...result.leftPanel,
|
|
components: applyDefaultsToNestedComponents(result.leftPanel.components),
|
|
};
|
|
}
|
|
|
|
// rightPanel.components 처리
|
|
if (result.rightPanel?.components) {
|
|
result.rightPanel = {
|
|
...result.rightPanel,
|
|
components: applyDefaultsToNestedComponents(result.rightPanel.components),
|
|
};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ============================================
|
|
// V2 → Legacy 변환 (로드 시)
|
|
// ============================================
|
|
export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData | null {
|
|
if (!v2Layout || !v2Layout.components) {
|
|
return null;
|
|
}
|
|
|
|
const components: LegacyComponentData[] = v2Layout.components.map((comp) => {
|
|
const componentType = getComponentTypeFromUrl(comp.url);
|
|
const defaults = getDefaultsByUrl(comp.url);
|
|
let mergedConfig = mergeComponentConfig(defaults, comp.overrides);
|
|
|
|
// 🆕 분할 패널인 경우 내부 컴포넌트에도 기본값 적용
|
|
if (componentType === "v2-split-panel-layout") {
|
|
mergedConfig = applyDefaultsToSplitPanelComponents(mergedConfig);
|
|
}
|
|
|
|
// 🆕 탭 위젯인 경우 탭 내부 컴포넌트에도 기본값 적용
|
|
if (componentType === "v2-tabs-widget" && mergedConfig.tabs) {
|
|
mergedConfig = {
|
|
...mergedConfig,
|
|
tabs: mergedConfig.tabs.map((tab: any) => {
|
|
if (tab?.components) {
|
|
return {
|
|
...tab,
|
|
components: applyDefaultsToNestedComponents(tab.components),
|
|
};
|
|
}
|
|
return tab;
|
|
}),
|
|
};
|
|
}
|
|
|
|
// 🆕 overrides에서 상위 레벨 속성들 추출
|
|
const overrides = comp.overrides || {};
|
|
|
|
return {
|
|
id: comp.id,
|
|
componentType: componentType,
|
|
widgetType: componentType,
|
|
type: "component",
|
|
position: comp.position,
|
|
size: comp.size,
|
|
componentConfig: mergedConfig,
|
|
// 🆕 상위 레벨 속성 복원 (테이블/컬럼 연결 정보)
|
|
tableName: overrides.tableName,
|
|
columnName: overrides.columnName,
|
|
label: overrides.label || mergedConfig.label || "", // 라벨이 없으면 빈 문자열
|
|
required: overrides.required,
|
|
readonly: overrides.readonly,
|
|
hidden: overrides.hidden, // 🆕 숨김 설정 복원
|
|
codeCategory: overrides.codeCategory,
|
|
inputType: overrides.inputType,
|
|
webType: overrides.webType,
|
|
// 🆕 autoFill 설정 복원 (자동 입력 기능)
|
|
autoFill: overrides.autoFill,
|
|
// 🆕 style 설정 복원 (라벨 텍스트, 라벨 스타일 등)
|
|
style: overrides.style || {},
|
|
// 🔧 webTypeConfig 복원 (버튼 제어기능, 플로우 가시성 등)
|
|
webTypeConfig: overrides.webTypeConfig || {},
|
|
// 기존 구조 호환을 위한 추가 필드
|
|
parentId: null,
|
|
gridColumns: 12,
|
|
gridRowIndex: 0,
|
|
};
|
|
});
|
|
|
|
return {
|
|
components,
|
|
gridSettings: v2Layout.gridSettings || {
|
|
enabled: true,
|
|
size: 20,
|
|
color: "#d1d5db",
|
|
opacity: 0.5,
|
|
snapToGrid: true,
|
|
columns: 12,
|
|
gap: 16,
|
|
padding: 16,
|
|
},
|
|
screenResolution: v2Layout.screenResolution || {
|
|
width: 1920,
|
|
height: 1080,
|
|
},
|
|
};
|
|
}
|
|
|
|
// ============================================
|
|
// Legacy → V2 변환 (저장 시)
|
|
// ============================================
|
|
export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
|
|
const components: ComponentV2[] = legacyLayout.components.map((comp, index) => {
|
|
// 컴포넌트 타입 결정
|
|
const componentType = comp.componentType || comp.widgetType || comp.type || "unknown";
|
|
const url = getComponentUrl(componentType);
|
|
|
|
// 기본값 가져오기
|
|
const defaults = getDefaultsByUrl(url);
|
|
|
|
// 🆕 컴포넌트 상위 레벨 속성들도 포함 (tableName, columnName 등)
|
|
const topLevelProps: Record<string, any> = {};
|
|
if (comp.tableName) topLevelProps.tableName = comp.tableName;
|
|
if (comp.columnName) topLevelProps.columnName = comp.columnName;
|
|
// 🔧 label은 빈 문자열도 저장 (라벨 삭제 지원)
|
|
if (comp.label !== undefined) topLevelProps.label = comp.label;
|
|
if (comp.required !== undefined) topLevelProps.required = comp.required;
|
|
if (comp.readonly !== undefined) topLevelProps.readonly = comp.readonly;
|
|
if (comp.hidden !== undefined) topLevelProps.hidden = comp.hidden; // 🆕 숨김 설정 저장
|
|
if (comp.codeCategory) topLevelProps.codeCategory = comp.codeCategory;
|
|
if (comp.inputType) topLevelProps.inputType = comp.inputType;
|
|
if (comp.webType) topLevelProps.webType = comp.webType;
|
|
// 🆕 autoFill 설정 저장 (자동 입력 기능)
|
|
if (comp.autoFill) topLevelProps.autoFill = comp.autoFill;
|
|
// 🆕 style 설정 저장 (라벨 텍스트, 라벨 스타일 등)
|
|
if (comp.style && Object.keys(comp.style).length > 0) topLevelProps.style = comp.style;
|
|
// 🔧 webTypeConfig 저장 (버튼 제어기능, 플로우 가시성 등)
|
|
if (comp.webTypeConfig && Object.keys(comp.webTypeConfig).length > 0) {
|
|
topLevelProps.webTypeConfig = comp.webTypeConfig;
|
|
// 🔍 디버그: webTypeConfig 저장 확인
|
|
if (comp.webTypeConfig.dataflowConfig || comp.webTypeConfig.enableDataflowControl) {
|
|
console.log("💾 webTypeConfig 저장:", {
|
|
componentId: comp.id,
|
|
enableDataflowControl: comp.webTypeConfig.enableDataflowControl,
|
|
dataflowConfig: comp.webTypeConfig.dataflowConfig,
|
|
});
|
|
}
|
|
}
|
|
|
|
// 현재 설정에서 차이값만 추출
|
|
const fullConfig = comp.componentConfig || {};
|
|
const configOverrides = extractCustomConfig(fullConfig, defaults);
|
|
|
|
// 🔧 디버그: style 저장 확인 (주석 처리)
|
|
// if (comp.style?.labelDisplay !== undefined || configOverrides.style?.labelDisplay !== undefined) { console.log("💾 저장 시 style 변환:", { componentId: comp.id, "comp.style": comp.style, "configOverrides.style": configOverrides.style, "topLevelProps.style": topLevelProps.style }); }
|
|
|
|
// 상위 레벨 속성과 componentConfig 병합
|
|
// 🔧 style은 양쪽을 병합하되 comp.style(topLevelProps.style)을 우선시
|
|
const mergedStyle = {
|
|
...(configOverrides.style || {}),
|
|
...(topLevelProps.style || {}),
|
|
};
|
|
|
|
// 🔧 webTypeConfig도 병합 (topLevelProps가 우선, dataflowConfig 등 보존)
|
|
const mergedWebTypeConfig = {
|
|
...(configOverrides.webTypeConfig || {}),
|
|
...(topLevelProps.webTypeConfig || {}),
|
|
};
|
|
|
|
const overrides = {
|
|
...topLevelProps,
|
|
...configOverrides,
|
|
// 🆕 병합된 style 사용 (comp.style 값이 최종 우선)
|
|
...(Object.keys(mergedStyle).length > 0 ? { style: mergedStyle } : {}),
|
|
// 🆕 병합된 webTypeConfig 사용 (comp.webTypeConfig가 최종 우선)
|
|
...(Object.keys(mergedWebTypeConfig).length > 0 ? { webTypeConfig: mergedWebTypeConfig } : {}),
|
|
};
|
|
|
|
return {
|
|
id: comp.id,
|
|
url: url,
|
|
position: comp.position || { x: 0, y: 0 },
|
|
size: comp.size || { width: 100, height: 100 },
|
|
displayOrder: index,
|
|
overrides: overrides,
|
|
};
|
|
});
|
|
|
|
return {
|
|
version: "2.0",
|
|
components,
|
|
// 레이아웃 메타데이터 포함
|
|
gridSettings: legacyLayout.gridSettings,
|
|
screenResolution: legacyLayout.screenResolution,
|
|
metadata: legacyLayout.metadata,
|
|
};
|
|
}
|
|
|
|
// ============================================
|
|
// V2 레이아웃 유효성 검사
|
|
// ============================================
|
|
export function isValidV2Layout(data: any): data is LayoutV2 {
|
|
return data && typeof data === "object" && data.version === "2.0" && Array.isArray(data.components);
|
|
}
|
|
|
|
// ============================================
|
|
// 기존 레이아웃인지 확인
|
|
// ============================================
|
|
export function isLegacyLayout(data: any): boolean {
|
|
return data && typeof data === "object" && Array.isArray(data.components) && data.version !== "2.0";
|
|
}
|