/** * ๐Ÿ”„ Width๋ฅผ ์ปฌ๋Ÿผ ์ŠคํŒฌ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ * * ๊ธฐ์กด ํ”ฝ์…€ ๊ธฐ๋ฐ˜ width ๊ฐ’์„ ์ƒˆ๋กœ์šด ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ์˜ ์ปฌ๋Ÿผ ์ŠคํŒฌ์œผ๋กœ ๋ณ€ํ™˜ */ import { ColumnSpanPreset, COLUMN_SPAN_VALUES, getColumnSpanValue } from "@/lib/constants/columnSpans"; import { ComponentData, LayoutData } from "@/types/screen"; /** * ํ”ฝ์…€ width๋ฅผ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ColumnSpanPreset์œผ๋กœ ๋ณ€ํ™˜ * * @param width ํ”ฝ์…€ ๋„ˆ๋น„ * @param canvasWidth ์บ”๋ฒ„์Šค ์ „์ฒด ๋„ˆ๋น„ (๊ธฐ๋ณธ: 1920px) * @returns ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์ปฌ๋Ÿผ ์ŠคํŒฌ ํ”„๋ฆฌ์…‹ */ export function convertWidthToColumnSpan(width: number, canvasWidth: number = 1920): ColumnSpanPreset { if (width <= 0 || canvasWidth <= 0) { return "half"; // ๊ธฐ๋ณธ๊ฐ’ } const percentage = (width / canvasWidth) * 100; // ๊ฐ ํ”„๋ฆฌ์…‹์˜ ๋ฐฑ๋ถ„์œจ ๊ณ„์‚ฐ const presetPercentages: Array<[ColumnSpanPreset, number]> = [ ["full", 100], ["threeQuarters", 75], ["twoThirds", 67], ["half", 50], ["third", 33], ["quarter", 25], ["label", 25], ["input", 75], ["small", 17], ["medium", 33], ["large", 67], ]; // ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ’ ์ฐพ๊ธฐ let closestPreset: ColumnSpanPreset = "half"; let minDiff = Infinity; for (const [preset, presetPercentage] of presetPercentages) { const diff = Math.abs(percentage - presetPercentage); if (diff < minDiff) { minDiff = diff; closestPreset = preset; } } return closestPreset; } /** * Y ์ขŒํ‘œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ–‰ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ * * @param components ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด * @param threshold ๊ฐ™์€ ํ–‰์œผ๋กœ ๊ฐ„์ฃผํ•  Y ์ขŒํ‘œ ์ฐจ์ด (๊ธฐ๋ณธ: 50px) * @returns ํ–‰ ์ธ๋ฑ์Šค๊ฐ€ ์ถ”๊ฐ€๋œ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด */ export function calculateRowIndices(components: ComponentData[], threshold: number = 50): ComponentData[] { if (components.length === 0) return []; // Y ์ขŒํ‘œ๋กœ ์ •๋ ฌ const sorted = [...components].sort((a, b) => a.position.y - b.position.y); let currentRowIndex = 0; let currentY = sorted[0]?.position.y ?? 0; return sorted.map((component) => { if (Math.abs(component.position.y - currentY) > threshold) { currentRowIndex++; currentY = component.position.y; } return { ...component, gridRowIndex: currentRowIndex, }; }); } /** * ๊ฐ™์€ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ ์‹œ์ž‘ ์ปฌ๋Ÿผ ๊ณ„์‚ฐ * * @param components ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด (gridRowIndex ํ•„์š”) * @returns ์‹œ์ž‘ ์ปฌ๋Ÿผ์ด ์ถ”๊ฐ€๋œ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด */ export function calculateColumnStarts(components: ComponentData[]): ComponentData[] { // ํ–‰๋ณ„๋กœ ๊ทธ๋ฃนํ™” const rowGroups = new Map(); for (const component of components) { const rowIndex = component.gridRowIndex ?? 0; if (!rowGroups.has(rowIndex)) { rowGroups.set(rowIndex, []); } rowGroups.get(rowIndex)!.push(component); } // ๊ฐ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ ์ •๋ ฌํ•˜๊ณ  ์‹œ์ž‘ ์ปฌ๋Ÿผ ๊ณ„์‚ฐ const result: ComponentData[] = []; for (const [rowIndex, rowComponents] of rowGroups) { // X ์ขŒํ‘œ๋กœ ์ •๋ ฌ const sorted = rowComponents.sort((a, b) => a.position.x - b.position.x); let currentColumn = 1; for (const component of sorted) { const columnSpan = component.gridColumnSpan || "half"; const spanValue = getColumnSpanValue(columnSpan); // ํ˜„์žฌ ์ปฌ๋Ÿผ์ด 12๋ฅผ ๋„˜์œผ๋ฉด ๋‹ค์Œ ์ค„๋กœ (์‹ค์ œ๋กœ๋Š” ๊ฐ™์€ ํ–‰์ด์ง€๋งŒ ์ž๋™ ์ค„๋ฐ”๊ฟˆ) if (currentColumn + spanValue > 13) { currentColumn = 1; } result.push({ ...component, gridColumnStart: currentColumn, }); // ๋‹ค์Œ ์ปดํฌ๋„ŒํŠธ๋Š” ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ ๋’ค์— ๋ฐฐ์น˜ currentColumn += spanValue; } } return result; } /** * ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด์—์„œ width๋ฅผ gridColumnSpan์œผ๋กœ ์ผ๊ด„ ๋ณ€ํ™˜ * * @param components ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด * @param canvasWidth ์บ”๋ฒ„์Šค ๋„ˆ๋น„ (๊ธฐ๋ณธ: 1920px) * @returns gridColumnSpan์ด ์ถ”๊ฐ€๋œ ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด */ export function migrateComponentsToColumnSpan( components: ComponentData[], canvasWidth: number = 1920, ): ComponentData[] { return components.map((component) => { // ์ด๋ฏธ gridColumnSpan์ด ์žˆ์œผ๋ฉด ์œ ์ง€ if (component.gridColumnSpan) { return component; } // width๋ฅผ ์ปฌ๋Ÿผ ์ŠคํŒฌ์œผ๋กœ ๋ณ€ํ™˜ const gridColumnSpan = convertWidthToColumnSpan(component.size.width, canvasWidth); return { ...component, gridColumnSpan, gridRowIndex: component.gridRowIndex ?? 0, // ์ดˆ๊ธฐ๊ฐ’ }; }); } /** * ์ „์ฒด ๋ ˆ์ด์•„์›ƒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ * * @param layout ๊ธฐ์กด ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ * @param canvasWidth ์บ”๋ฒ„์Šค ๋„ˆ๋น„ (๊ธฐ๋ณธ: 1920px) * @returns ์ƒˆ๋กœ์šด ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ์œผ๋กœ ๋ณ€ํ™˜๋œ ๋ ˆ์ด์•„์›ƒ */ export function migrateLayoutToGridSystem(layout: LayoutData, canvasWidth: number = 1920): LayoutData { // 1๋‹จ๊ณ„: width๋ฅผ gridColumnSpan์œผ๋กœ ๋ณ€ํ™˜ let migratedComponents = migrateComponentsToColumnSpan(layout.components, canvasWidth); // 2๋‹จ๊ณ„: Y ์ขŒํ‘œ๋กœ ํ–‰ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ migratedComponents = calculateRowIndices(migratedComponents); // 3๋‹จ๊ณ„: ๊ฐ™์€ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ gridColumnStart ๊ณ„์‚ฐ migratedComponents = calculateColumnStarts(migratedComponents); return { ...layout, components: migratedComponents, }; } /** * ๋‹จ์ผ ์ปดํฌ๋„ŒํŠธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ * * @param component ๊ธฐ์กด ์ปดํฌ๋„ŒํŠธ * @param canvasWidth ์บ”๋ฒ„์Šค ๋„ˆ๋น„ * @returns ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ์ปดํฌ๋„ŒํŠธ */ export function migrateComponent(component: ComponentData, canvasWidth: number = 1920): ComponentData { // ์ด๋ฏธ ๊ทธ๋ฆฌ๋“œ ์†์„ฑ์ด ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ if (component.gridColumnSpan && component.gridRowIndex !== undefined) { return component; } const gridColumnSpan = component.gridColumnSpan || convertWidthToColumnSpan(component.size.width, canvasWidth); return { ...component, gridColumnSpan, gridRowIndex: component.gridRowIndex ?? 0, gridColumnStart: component.gridColumnStart, }; } /** * ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•„์š” ์—ฌ๋ถ€ ํ™•์ธ * * @param layout ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ * @returns ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•„์š” ์—ฌ๋ถ€ */ export function needsMigration(layout: LayoutData): boolean { return layout.components.some((c) => !c.gridColumnSpan || c.gridRowIndex === undefined); } /** * ์•ˆ์ „ํ•œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์—๋Ÿฌ ์ฒ˜๋ฆฌ ํฌํ•จ) * * @param layout ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ * @param canvasWidth ์บ”๋ฒ„์Šค ๋„ˆ๋น„ * @returns ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ๋ ˆ์ด์•„์›ƒ ๋˜๋Š” ์›๋ณธ (์‹คํŒจ ์‹œ) */ export function safeMigrateLayout(layout: LayoutData, canvasWidth: number = 1920): LayoutData { try { if (!needsMigration(layout)) { return layout; } return migrateLayoutToGridSystem(layout, canvasWidth); } catch (error) { console.error("โŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํŒจ:", error); console.warn("โš ๏ธ ์›๋ณธ ๋ ˆ์ด์•„์›ƒ ๋ฐ˜ํ™˜ - ์ˆ˜๋™ ํ™•์ธ ํ•„์š”"); return layout; } } /** * ๋ฐฑ์—… ๋ฐ์ดํ„ฐ ์ƒ์„ฑ * * @param layout ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ * @returns JSON ๋ฌธ์ž์—ด */ export function createLayoutBackup(layout: LayoutData): string { return JSON.stringify(layout, null, 2); } /** * ๋ฐฑ์—…์—์„œ ๋ณต์› * * @param backupJson JSON ๋ฌธ์ž์—ด * @returns ๋ ˆ์ด์•„์›ƒ ๋ฐ์ดํ„ฐ */ export function restoreFromBackup(backupJson: string): LayoutData { return JSON.parse(backupJson); }