// 레거시 레이아웃 로더 // DB에 저장된 V5(12칸) 좌표를 현재 블록 좌표로 변환한다. // DB 데이터는 건드리지 않고, 로드 시 메모리에서만 변환. import { PopGridPosition, PopLayoutData, BLOCK_SIZE, BLOCK_GAP, BLOCK_PADDING, getBlockColumns, } from "../types/pop-layout"; const LEGACY_COLUMNS = 12; const LEGACY_ROW_HEIGHT = 48; const LEGACY_GAP = 16; const DESIGN_WIDTH = 1024; function isLegacyGridConfig(layout: PopLayoutData): boolean { if (layout.gridConfig?.rowHeight === BLOCK_SIZE) return false; const maxCol = Object.values(layout.components).reduce((max, comp) => { const end = comp.position.col + comp.position.colSpan - 1; return Math.max(max, end); }, 0); return maxCol <= LEGACY_COLUMNS; } function convertLegacyPosition( pos: PopGridPosition, targetColumns: number, ): PopGridPosition { const colRatio = targetColumns / LEGACY_COLUMNS; const rowRatio = (LEGACY_ROW_HEIGHT + LEGACY_GAP) / (BLOCK_SIZE + BLOCK_GAP); const newCol = Math.max(1, Math.round((pos.col - 1) * colRatio) + 1); let newColSpan = Math.max(1, Math.round(pos.colSpan * colRatio)); const newRowSpan = Math.max(1, Math.round(pos.rowSpan * rowRatio)); if (newCol + newColSpan - 1 > targetColumns) { newColSpan = targetColumns - newCol + 1; } return { col: newCol, row: pos.row, colSpan: newColSpan, rowSpan: newRowSpan }; } const BLOCK_GRID_CONFIG = { rowHeight: BLOCK_SIZE, gap: BLOCK_GAP, padding: BLOCK_PADDING, }; /** * DB에서 로드한 레이아웃을 현재 블록 좌표로 변환한다. * * - 12칸 레거시 좌표 → 블록 좌표 변환 * - 이미 블록 좌표인 경우 → gridConfig만 보정 * - 구 모드별 overrides는 항상 제거 (리플로우가 대체) */ export function loadLegacyLayout(layout: PopLayoutData): PopLayoutData { if (!isLegacyGridConfig(layout)) { return { ...layout, gridConfig: BLOCK_GRID_CONFIG, overrides: undefined, }; } const blockColumns = getBlockColumns(DESIGN_WIDTH); const rowGroups: Record = {}; Object.entries(layout.components).forEach(([id, comp]) => { const r = comp.position.row; if (!rowGroups[r]) rowGroups[r] = []; rowGroups[r].push(id); }); const convertedPositions: Record = {}; Object.entries(layout.components).forEach(([id, comp]) => { convertedPositions[id] = convertLegacyPosition(comp.position, blockColumns); }); const sortedRows = Object.keys(rowGroups).map(Number).sort((a, b) => a - b); const rowMapping: Record = {}; let currentRow = 1; for (const legacyRow of sortedRows) { rowMapping[legacyRow] = currentRow; const maxSpan = Math.max( ...rowGroups[legacyRow].map(id => convertedPositions[id].rowSpan) ); currentRow += maxSpan; } const newComponents = { ...layout.components }; Object.entries(newComponents).forEach(([id, comp]) => { const converted = convertedPositions[id]; const mappedRow = rowMapping[comp.position.row] ?? converted.row; newComponents[id] = { ...comp, position: { ...converted, row: mappedRow }, }; }); const newModals = layout.modals?.map(modal => { const modalComps = { ...modal.components }; Object.entries(modalComps).forEach(([id, comp]) => { modalComps[id] = { ...comp, position: convertLegacyPosition(comp.position, blockColumns), }; }); return { ...modal, gridConfig: BLOCK_GRID_CONFIG, components: modalComps, overrides: undefined, }; }); return { ...layout, gridConfig: BLOCK_GRID_CONFIG, components: newComponents, overrides: undefined, modals: newModals, }; }