151 lines
4.5 KiB
TypeScript
151 lines
4.5 KiB
TypeScript
// 그룹화 관련 유틸리티 함수들
|
|
|
|
import { ComponentData, GroupComponent, Position, Size } from "@/types/screen";
|
|
|
|
// 컴포넌트 ID 생성
|
|
export function generateComponentId(): string {
|
|
return `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
// 그룹 컴포넌트 생성
|
|
export function createGroupComponent(
|
|
componentIds: string[],
|
|
title: string = "새 그룹",
|
|
position: Position = { x: 0, y: 0 },
|
|
boundingBox?: { width: number; height: number },
|
|
style?: any,
|
|
): GroupComponent {
|
|
// 픽셀 기반 크기 계산 (격자 제거)
|
|
const groupWidth = Math.max(200, (boundingBox?.width || 200) + 40); // 최소 200px, 여백 40px
|
|
const groupHeight = Math.max(100, (boundingBox?.height || 200) + 40); // 최소 100px, 여백 40px
|
|
|
|
return {
|
|
id: generateComponentId(),
|
|
type: "group",
|
|
position: { ...position, z: position.z || 1 }, // z 값 포함
|
|
size: { width: groupWidth, height: groupHeight },
|
|
title: title, // GroupComponent 타입에 맞게 title 사용
|
|
backgroundColor: "#f8f9fa",
|
|
border: "1px solid #dee2e6",
|
|
borderRadius: 8,
|
|
shadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
collapsible: true,
|
|
collapsed: false,
|
|
children: componentIds,
|
|
style: {
|
|
padding: "16px",
|
|
...style,
|
|
},
|
|
};
|
|
}
|
|
|
|
// 선택된 컴포넌트들의 경계 박스 계산 (픽셀 기반)
|
|
export function calculateBoundingBox(components: ComponentData[]): {
|
|
minX: number;
|
|
minY: number;
|
|
maxX: number;
|
|
maxY: number;
|
|
width: number;
|
|
height: number;
|
|
} {
|
|
if (components.length === 0) {
|
|
return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 };
|
|
}
|
|
|
|
const minX = Math.min(...components.map((c) => c.position.x));
|
|
const minY = Math.min(...components.map((c) => c.position.y));
|
|
const maxX = Math.max(...components.map((c) => c.position.x + c.size.width)); // 격자 계산 제거
|
|
const maxY = Math.max(...components.map((c) => c.position.y + c.size.height));
|
|
|
|
return {
|
|
minX,
|
|
minY,
|
|
maxX,
|
|
maxY,
|
|
width: maxX - minX,
|
|
height: maxY - minY,
|
|
};
|
|
}
|
|
|
|
// 그룹 내 컴포넌트들의 상대 위치 계산
|
|
export function calculateRelativePositions(
|
|
components: ComponentData[],
|
|
groupPosition: Position,
|
|
groupId: string,
|
|
): ComponentData[] {
|
|
return components.map((component) => ({
|
|
...component,
|
|
position: {
|
|
x: component.position.x - groupPosition.x,
|
|
y: component.position.y - groupPosition.y,
|
|
z: component.position.z || 1, // z 값 유지
|
|
},
|
|
parentId: groupId, // 그룹 ID를 부모로 설정
|
|
}));
|
|
}
|
|
|
|
// 그룹 해제 시 컴포넌트들의 절대 위치 복원
|
|
export function restoreAbsolutePositions(components: ComponentData[], groupPosition: Position): ComponentData[] {
|
|
return components.map((component) => ({
|
|
...component,
|
|
position: {
|
|
x: component.position.x + groupPosition.x,
|
|
y: component.position.y + groupPosition.y,
|
|
z: component.position.z || 1, // z 값 유지
|
|
},
|
|
parentId: undefined,
|
|
}));
|
|
}
|
|
|
|
// 그룹 내 컴포넌트 필터링
|
|
export function getGroupChildren(components: ComponentData[], groupId: string): ComponentData[] {
|
|
return components.filter((component) => component.parentId === groupId);
|
|
}
|
|
|
|
// 그룹이 아닌 컴포넌트들 필터링
|
|
export function getNonGroupComponents(components: ComponentData[]): ComponentData[] {
|
|
return components.filter((component) => component.type !== "group");
|
|
}
|
|
|
|
// 선택 가능한 컴포넌트들 필터링 (그룹 제외)
|
|
export function getSelectableComponents(components: ComponentData[]): ComponentData[] {
|
|
return components.filter((component) => component.type === "widget" || component.type === "container");
|
|
}
|
|
|
|
// 그룹 스타일 생성
|
|
export function createGroupStyle(
|
|
backgroundColor: string = "#f8f9fa",
|
|
border: string = "1px solid #dee2e6",
|
|
borderRadius: number = 8,
|
|
): any {
|
|
return {
|
|
backgroundColor,
|
|
border,
|
|
borderRadius,
|
|
padding: "16px",
|
|
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
|
};
|
|
}
|
|
|
|
// 그룹 제목 유효성 검사
|
|
export function validateGroupTitle(title: string): boolean {
|
|
return title.trim().length > 0 && title.trim().length <= 50;
|
|
}
|
|
|
|
// 그룹 크기 자동 조정
|
|
export function autoResizeGroup(group: GroupComponent, children: ComponentData[]): GroupComponent {
|
|
if (children.length === 0) {
|
|
return group;
|
|
}
|
|
|
|
const boundingBox = calculateBoundingBox(children);
|
|
|
|
return {
|
|
...group,
|
|
size: {
|
|
width: Math.max(6, Math.ceil(boundingBox.width / 80) + 2), // 최소 6 그리드, 여백 2
|
|
height: Math.max(100, boundingBox.height + 40), // 최소 100px, 여백 40px
|
|
},
|
|
};
|
|
}
|