156 lines
4.5 KiB
TypeScript
156 lines
4.5 KiB
TypeScript
import type { ComponentConfig, GridConfig } from "@/types/report";
|
|
|
|
/**
|
|
* 픽셀 좌표를 그리드 좌표로 변환
|
|
*/
|
|
export function pixelToGrid(pixel: number, cellSize: number): number {
|
|
return Math.round(pixel / cellSize);
|
|
}
|
|
|
|
/**
|
|
* 그리드 좌표를 픽셀 좌표로 변환
|
|
*/
|
|
export function gridToPixel(grid: number, cellSize: number): number {
|
|
return grid * cellSize;
|
|
}
|
|
|
|
/**
|
|
* 컴포넌트 위치/크기를 그리드에 스냅
|
|
*/
|
|
export function snapComponentToGrid(component: ComponentConfig, gridConfig: GridConfig): ComponentConfig {
|
|
if (!gridConfig.snapToGrid) {
|
|
return component;
|
|
}
|
|
|
|
// 픽셀 좌표를 그리드 좌표로 변환
|
|
const gridX = pixelToGrid(component.x, gridConfig.cellWidth);
|
|
const gridY = pixelToGrid(component.y, gridConfig.cellHeight);
|
|
const gridWidth = Math.max(1, pixelToGrid(component.width, gridConfig.cellWidth));
|
|
const gridHeight = Math.max(1, pixelToGrid(component.height, gridConfig.cellHeight));
|
|
|
|
// 그리드 좌표를 다시 픽셀로 변환
|
|
return {
|
|
...component,
|
|
gridX,
|
|
gridY,
|
|
gridWidth,
|
|
gridHeight,
|
|
x: gridToPixel(gridX, gridConfig.cellWidth),
|
|
y: gridToPixel(gridY, gridConfig.cellHeight),
|
|
width: gridToPixel(gridWidth, gridConfig.cellWidth),
|
|
height: gridToPixel(gridHeight, gridConfig.cellHeight),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 그리드 충돌 감지
|
|
* 두 컴포넌트가 겹치는지 확인
|
|
*/
|
|
export function detectGridCollision(
|
|
component: ComponentConfig,
|
|
otherComponents: ComponentConfig[],
|
|
gridConfig: GridConfig,
|
|
): boolean {
|
|
const comp1GridX = component.gridX ?? pixelToGrid(component.x, gridConfig.cellWidth);
|
|
const comp1GridY = component.gridY ?? pixelToGrid(component.y, gridConfig.cellHeight);
|
|
const comp1GridWidth = component.gridWidth ?? pixelToGrid(component.width, gridConfig.cellWidth);
|
|
const comp1GridHeight = component.gridHeight ?? pixelToGrid(component.height, gridConfig.cellHeight);
|
|
|
|
for (const other of otherComponents) {
|
|
if (other.id === component.id) continue;
|
|
|
|
const comp2GridX = other.gridX ?? pixelToGrid(other.x, gridConfig.cellWidth);
|
|
const comp2GridY = other.gridY ?? pixelToGrid(other.y, gridConfig.cellHeight);
|
|
const comp2GridWidth = other.gridWidth ?? pixelToGrid(other.width, gridConfig.cellWidth);
|
|
const comp2GridHeight = other.gridHeight ?? pixelToGrid(other.height, gridConfig.cellHeight);
|
|
|
|
// AABB (Axis-Aligned Bounding Box) 충돌 감지
|
|
const xOverlap = comp1GridX < comp2GridX + comp2GridWidth && comp1GridX + comp1GridWidth > comp2GridX;
|
|
const yOverlap = comp1GridY < comp2GridY + comp2GridHeight && comp1GridY + comp1GridHeight > comp2GridY;
|
|
|
|
if (xOverlap && yOverlap) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 페이지 크기 기반 그리드 행/열 계산
|
|
*/
|
|
export function calculateGridDimensions(
|
|
pageWidth: number,
|
|
pageHeight: number,
|
|
cellWidth: number,
|
|
cellHeight: number,
|
|
): { rows: number; columns: number } {
|
|
return {
|
|
columns: Math.floor(pageWidth / cellWidth),
|
|
rows: Math.floor(pageHeight / cellHeight),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 기본 그리드 설정 생성
|
|
*/
|
|
export function createDefaultGridConfig(pageWidth: number, pageHeight: number): GridConfig {
|
|
const cellWidth = 20;
|
|
const cellHeight = 20;
|
|
const { rows, columns } = calculateGridDimensions(pageWidth, pageHeight, cellWidth, cellHeight);
|
|
|
|
return {
|
|
cellWidth,
|
|
cellHeight,
|
|
rows,
|
|
columns,
|
|
visible: true,
|
|
snapToGrid: true,
|
|
gridColor: "#e5e7eb",
|
|
gridOpacity: 0.5,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 위치가 페이지 경계 내에 있는지 확인
|
|
*/
|
|
export function isWithinPageBounds(
|
|
component: ComponentConfig,
|
|
pageWidth: number,
|
|
pageHeight: number,
|
|
margins: { top: number; bottom: number; left: number; right: number },
|
|
): boolean {
|
|
const minX = margins.left;
|
|
const minY = margins.top;
|
|
const maxX = pageWidth - margins.right;
|
|
const maxY = pageHeight - margins.bottom;
|
|
|
|
return (
|
|
component.x >= minX &&
|
|
component.y >= minY &&
|
|
component.x + component.width <= maxX &&
|
|
component.y + component.height <= maxY
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 컴포넌트를 페이지 경계 내로 제한
|
|
*/
|
|
export function constrainToPageBounds(
|
|
component: ComponentConfig,
|
|
pageWidth: number,
|
|
pageHeight: number,
|
|
margins: { top: number; bottom: number; left: number; right: number },
|
|
): ComponentConfig {
|
|
const minX = margins.left;
|
|
const minY = margins.top;
|
|
const maxX = pageWidth - margins.right - component.width;
|
|
const maxY = pageHeight - margins.bottom - component.height;
|
|
|
|
return {
|
|
...component,
|
|
x: Math.max(minX, Math.min(maxX, component.x)),
|
|
y: Math.max(minY, Math.min(maxY, component.y)),
|
|
};
|
|
}
|