feat: 레이어 시스템 추가 및 관리 기능 구현

- InteractiveScreenViewer 컴포넌트에 레이어 시스템을 도입하여, 레이어의 활성화 및 조건부 표시 로직을 추가하였습니다.
- ScreenDesigner 컴포넌트에서 레이어 상태 관리 및 레이어 정보 저장 기능을 구현하였습니다.
- 레이어 정의 및 조건부 표시 설정을 위한 새로운 타입과 스키마를 추가하여, 레이어 기반의 UI 구성 요소를 보다 유연하게 관리할 수 있도록 하였습니다.
- 레이어별 컴포넌트 렌더링 로직을 추가하여, 모달 및 드로어 형태의 레이어를 효과적으로 처리할 수 있도록 개선하였습니다.
- 전반적으로 레이어 시스템을 통해 사용자 경험을 향상시키고, UI 구성의 유연성을 높였습니다.
This commit is contained in:
kjs
2026-02-06 09:51:29 +09:00
parent e31bb970a2
commit 4e2209bd5d
8 changed files with 1336 additions and 144 deletions

View File

@@ -148,9 +148,53 @@ export const componentV2Schema = z.object({
overrides: z.record(z.string(), z.any()).default({}),
});
export const layoutV2Schema = z.object({
version: z.string().default("2.0"),
// ============================================
// 레이어 스키마 정의
// ============================================
export const layerTypeSchema = z.enum(["base", "conditional", "modal", "drawer"]);
export const layerSchema = z.object({
id: z.string(),
name: z.string(),
type: layerTypeSchema,
zIndex: z.number().default(0),
isVisible: z.boolean().default(true), // 초기 표시 여부
isLocked: z.boolean().default(false), // 편집 잠금 여부
// 조건부 표시 로직
condition: z
.object({
targetComponentId: z.string(),
operator: z.enum(["eq", "neq", "in"]),
value: z.any(),
})
.optional(),
// 모달/드로어 전용 설정
overlayConfig: z
.object({
backdrop: z.boolean().default(true),
closeOnBackdropClick: z.boolean().default(true),
width: z.union([z.string(), z.number()]).optional(),
height: z.union([z.string(), z.number()]).optional(),
// 모달/드로어 스타일링
backgroundColor: z.string().optional(),
backdropBlur: z.number().optional(),
// 드로어 전용
position: z.enum(["left", "right", "top", "bottom"]).optional(),
})
.optional(),
// 해당 레이어에 속한 컴포넌트들
components: z.array(componentV2Schema).default([]),
});
export type Layer = z.infer<typeof layerSchema>;
export const layoutV2Schema = z.object({
version: z.string().default("2.1"),
layers: z.array(layerSchema).default([]), // 신규 필드
components: z.array(componentV2Schema).default([]), // 하위 호환성 유지
updatedAt: z.string().optional(),
screenResolution: z
.object({
@@ -952,23 +996,78 @@ export function saveComponentV2(component: ComponentV2 & { config?: Record<strin
// ============================================
// V2 레이아웃 로드 (전체 컴포넌트 기본값 병합)
// ============================================
export function loadLayoutV2(
layoutData: any,
): LayoutV2 & { components: Array<ComponentV2 & { config: Record<string, any> }> } {
const parsed = layoutV2Schema.parse(layoutData || { version: "2.0", components: [] });
export function loadLayoutV2(layoutData: any): LayoutV2 & {
components: Array<ComponentV2 & { config: Record<string, any> }>;
layers: Array<Layer & { components: Array<ComponentV2 & { config: Record<string, any> }> }>;
} {
const parsed = layoutV2Schema.parse(layoutData || { version: "2.1", components: [], layers: [] });
// 마이그레이션: components만 있고 layers가 없는 경우 Default Layer 생성
if ((!parsed.layers || parsed.layers.length === 0) && parsed.components && parsed.components.length > 0) {
const defaultLayer: Layer = {
id: "default-layer",
name: "기본 레이어",
type: "base",
zIndex: 0,
isVisible: true,
isLocked: false,
components: parsed.components,
};
parsed.layers = [defaultLayer];
}
// 모든 레이어의 컴포넌트 로드
const loadedLayers = parsed.layers.map((layer) => ({
...layer,
components: layer.components.map(loadComponentV2),
}));
// 하위 호환성을 위한 components 배열 (모든 레이어의 컴포넌트 합침)
const allComponents = loadedLayers.flatMap((layer) => layer.components);
return {
...parsed,
components: parsed.components.map(loadComponentV2),
layers: loadedLayers,
components: allComponents,
};
}
// ============================================
// V2 레이아웃 저장 (전체 컴포넌트 차이값 추출)
// ============================================
export function saveLayoutV2(components: Array<ComponentV2 & { config?: Record<string, any> }>): LayoutV2 {
export function saveLayoutV2(
components: Array<ComponentV2 & { config?: Record<string, any> }>,
layers?: Array<Layer & { components: Array<ComponentV2 & { config?: Record<string, any> }> }>,
): LayoutV2 {
// 레이어가 있는 경우 레이어 구조 저장
if (layers && layers.length > 0) {
const savedLayers = layers.map((layer) => ({
...layer,
components: layer.components.map(saveComponentV2),
}));
return {
version: "2.1",
layers: savedLayers,
components: savedLayers.flatMap((l) => l.components), // 하위 호환성
};
}
// 레이어가 없는 경우 (기존 방식) - Default Layer로 감싸서 저장
const savedComponents = components.map(saveComponentV2);
const defaultLayer: Layer = {
id: "default-layer",
name: "기본 레이어",
type: "base",
zIndex: 0,
isVisible: true,
isLocked: false,
components: savedComponents,
};
return {
version: "2.0",
components: components.map(saveComponentV2),
version: "2.1",
layers: [defaultLayer],
components: savedComponents,
};
}