ERP-node/frontend/components/admin/dashboard/gridUtils.ts

201 lines
5.9 KiB
TypeScript
Raw Normal View History

/**
*
* - 12
* - ( = )
* -
*/
2025-10-16 09:55:14 +09:00
// 기본 그리드 설정 (FHD 기준)
export const GRID_CONFIG = {
2025-10-16 09:55:14 +09:00
COLUMNS: 12, // 모든 해상도에서 12칸 고정
GAP: 5, // 셀 간격 고정
SNAP_THRESHOLD: 10, // 스냅 임계값 (px)
2025-10-13 17:16:44 +09:00
ELEMENT_PADDING: 4, // 요소 주위 여백 (px)
SUB_GRID_DIVISIONS: 5, // 각 그리드 칸을 5x5로 세분화 (세밀한 조정용)
2025-10-16 09:55:14 +09:00
// CELL_SIZE와 CANVAS_WIDTH는 해상도에 따라 동적 계산
} as const;
2025-10-16 09:55:14 +09:00
/**
*
* : (CELL_SIZE + GAP) * 12 - GAP = canvasWidth
* CELL_SIZE = (canvasWidth + GAP) / 12 - GAP
*/
export function calculateCellSize(canvasWidth: number): number {
return Math.floor((canvasWidth + GRID_CONFIG.GAP) / GRID_CONFIG.COLUMNS) - GRID_CONFIG.GAP;
}
/**
* ( )
*/
export function calculateSubGridSize(cellSize: number): number {
return Math.floor(cellSize / GRID_CONFIG.SUB_GRID_DIVISIONS);
}
2025-10-16 09:55:14 +09:00
/**
*
*/
export function calculateGridConfig(canvasWidth: number) {
const cellSize = calculateCellSize(canvasWidth);
const subGridSize = calculateSubGridSize(cellSize);
2025-10-16 09:55:14 +09:00
return {
...GRID_CONFIG,
CELL_SIZE: cellSize,
SUB_GRID_SIZE: subGridSize,
2025-10-16 09:55:14 +09:00
CANVAS_WIDTH: canvasWidth,
};
}
/**
* (gap )
*/
export const getCellWithGap = () => {
return GRID_CONFIG.CELL_SIZE + GRID_CONFIG.GAP;
};
/**
*
*/
export const getCanvasWidth = () => {
const cellWithGap = getCellWithGap();
return GRID_CONFIG.COLUMNS * cellWithGap - GRID_CONFIG.GAP;
};
/**
* ( )
* @param value -
* @param subGridSize - (, 기본값: cellSize/3 43px)
* @returns
*/
export const snapToGrid = (value: number, subGridSize?: number): number => {
// 서브 그리드 크기가 지정되지 않으면 기본 그리드 크기의 1/3 사용 (3x3 서브그리드)
const snapSize = subGridSize ?? Math.floor(GRID_CONFIG.CELL_SIZE / 3);
// 서브 그리드 단위로 스냅
const gridIndex = Math.round(value / snapSize);
return gridIndex * snapSize;
};
/**
* ( )
* @param value -
* @param cellSize - ()
* @returns ( , )
*/
export const snapToGridWithThreshold = (value: number, cellSize: number = GRID_CONFIG.CELL_SIZE): number => {
const snapped = snapToGrid(value, cellSize);
const distance = Math.abs(value - snapped);
return distance <= GRID_CONFIG.SNAP_THRESHOLD ? snapped : value;
};
/**
*
* @param size -
* @param minCells - (기본값: 2)
* @param cellSize - ()
* @returns
*/
export const snapSizeToGrid = (
size: number,
minCells: number = 2,
cellSize: number = GRID_CONFIG.CELL_SIZE,
): number => {
const cellWithGap = cellSize + GRID_CONFIG.GAP;
const cells = Math.max(minCells, Math.round(size / cellWithGap));
return cells * cellWithGap - GRID_CONFIG.GAP;
};
/**
*
*/
export interface GridPosition {
x: number;
y: number;
}
export interface GridSize {
width: number;
height: number;
}
export interface GridBounds {
position: GridPosition;
size: GridSize;
}
/**
*
* @param bounds -
* @param canvasWidth - ( )
* @param canvasHeight - ( )
* @returns
*/
export const snapBoundsToGrid = (bounds: GridBounds, canvasWidth?: number, canvasHeight?: number): GridBounds => {
// 위치 스냅
let snappedX = snapToGrid(bounds.position.x);
let snappedY = snapToGrid(bounds.position.y);
// 크기 스냅
const snappedWidth = snapSizeToGrid(bounds.size.width);
const snappedHeight = snapSizeToGrid(bounds.size.height);
// 캔버스 경계 체크
if (canvasWidth) {
snappedX = Math.min(snappedX, canvasWidth - snappedWidth);
}
if (canvasHeight) {
snappedY = Math.min(snappedY, canvasHeight - snappedHeight);
}
// 음수 방지
snappedX = Math.max(0, snappedX);
snappedY = Math.max(0, snappedY);
return {
position: { x: snappedX, y: snappedY },
size: { width: snappedWidth, height: snappedHeight },
};
};
/**
*
* @param value -
* @returns (0 )
*/
export const getGridIndex = (value: number): number => {
const cellWithGap = getCellWithGap();
return Math.floor(value / cellWithGap);
};
/**
*
* @param index -
* @returns
*/
export const gridIndexToCoordinate = (index: number): number => {
const cellWithGap = getCellWithGap();
return index * cellWithGap;
};
/**
*
* @param value -
* @returns
*/
export const getNearbyGridLines = (value: number): number[] => {
const snapped = snapToGrid(value);
const cellWithGap = getCellWithGap();
return [snapped - cellWithGap, snapped, snapped + cellWithGap].filter((line) => line >= 0);
};
/**
*
* @param value -
* @param snapValue -
* @returns true
*/
export const isWithinSnapThreshold = (value: number, snapValue: number): boolean => {
return Math.abs(value - snapValue) <= GRID_CONFIG.SNAP_THRESHOLD;
};