холст конструктор

This commit is contained in:
2025-07-16 17:25:33 +03:00
parent b77303fa97
commit 6933fab3bb
13 changed files with 1243 additions and 996 deletions

View File

@@ -1,6 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { CellTarget, FormLayoutSettings, TemplateElement } from "../types/template";
import { CellTarget, ElementLayout, FormLayoutSettings, TemplateElement } from "../types/template";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
@@ -32,19 +32,121 @@ export function migrateTemplateElement(element: any): TemplateElement {
...element,
targetCells,
order: element.order || 0,
category: element.category || '',
width: element.width || 'auto',
layout: element.layout || generateDefaultLayout(element.order || 0),
} as TemplateElement;
}
// Получение настроек отображения по умолчанию
export function getDefaultLayoutSettings(): FormLayoutSettings {
return {
columns: 2,
grouping: 'none',
elementSpacing: 'normal',
showLabels: true,
enableDragDrop: true,
canvasWidth: 1200,
canvasHeight: 800,
gridSize: 20,
showGrid: true,
};
}
// Генерация позиции по умолчанию для нового элемента
export function generateDefaultLayout(order: number = 0): ElementLayout {
const cols = 4; // Количество колонок для автоматического размещения
const elementWidth = 300;
const elementHeight = 80;
const padding = 50;
const col = order % cols;
const row = Math.floor(order / cols);
return {
x: col * (elementWidth + padding) + padding,
y: row * (elementHeight + padding) + padding,
width: elementWidth,
height: elementHeight,
zIndex: 1,
};
}
// Автоматическое размещение элементов на холсте
export function autoArrangeElements(
elements: TemplateElement[],
gridSize: number = 20
): TemplateElement[] {
const elementWidth = 300;
const elementHeight = 80;
const padding = 50;
const cols = 4; // Фиксированное количество колонок
return elements.map((element, index) => {
const col = index % cols;
const row = Math.floor(index / cols);
const x = col * (elementWidth + padding) + padding;
const y = row * (elementHeight + padding) + padding;
// Привязка к сетке
const snappedX = Math.round(x / gridSize) * gridSize;
const snappedY = Math.round(y / gridSize) * gridSize;
return {
...element,
layout: {
x: snappedX,
y: snappedY,
width: elementWidth,
height: elementHeight,
zIndex: 1,
},
};
});
}
// Проверка пересечения элементов
export function checkElementOverlap(
element1: ElementLayout,
element2: ElementLayout,
threshold: number = 5
): boolean {
return !(
element1.x + element1.width + threshold < element2.x ||
element2.x + element2.width + threshold < element1.x ||
element1.y + element1.height + threshold < element2.y ||
element2.y + element2.height + threshold < element1.y
);
}
// Поиск свободного места для элемента (поиск в разумных пределах)
export function findFreePosition(
newElement: ElementLayout,
existingElements: ElementLayout[],
gridSize: number = 20
): ElementLayout {
const step = gridSize;
const maxWidth = 1600; // Максимальная ширина поиска
const maxHeight = 1200; // Максимальная высота поиска
const maxAttempts = 500;
let attempts = 0;
for (let y = 50; y <= maxHeight - newElement.height; y += step) {
for (let x = 50; x <= maxWidth - newElement.width; x += step) {
attempts++;
if (attempts > maxAttempts) break;
const testPosition = { ...newElement, x, y };
const hasOverlap = existingElements.some(existing =>
checkElementOverlap(testPosition, existing)
);
if (!hasOverlap) {
return testPosition;
}
}
if (attempts > maxAttempts) break;
}
// Если не нашли свободное место, размещаем в правом нижнем углу
return {
...newElement,
x: 50,
y: existingElements.length * 100 + 50
};
}