feat: 레이어 시스템 추가 및 관리 기능 구현
- InteractiveScreenViewer 컴포넌트에 레이어 시스템을 도입하여, 레이어의 활성화 및 조건부 표시 로직을 추가하였습니다. - ScreenDesigner 컴포넌트에서 레이어 상태 관리 및 레이어 정보 저장 기능을 구현하였습니다. - 레이어 정의 및 조건부 표시 설정을 위한 새로운 타입과 스키마를 추가하여, 레이어 기반의 UI 구성 요소를 보다 유연하게 관리할 수 있도록 하였습니다. - 레이어별 컴포넌트 렌더링 로직을 추가하여, 모달 및 드로어 형태의 레이어를 효과적으로 처리할 수 있도록 개선하였습니다. - 전반적으로 레이어 시스템을 통해 사용자 경험을 향상시키고, UI 구성의 유연성을 높였습니다.
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user