Spaces:
Running
Running
File size: 3,655 Bytes
618aeaa | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | export function applySnapToGuides(dragged, items, options = {}) {
const threshold = options.threshold ?? 10;
let next = { ...dragged };
const snapGuides = [];
const candidates = items.filter((item) => item.id !== dragged.id);
const best = { x: null, y: null };
const verticalPreference = new Set(options.preferVertical || []);
const horizontalPreference = new Set(options.preferHorizontal || []);
for (const item of candidates) {
const guides = [
{ type: "top", value: item.y, delta: item.y - next.y },
{ type: "centerY", value: item.y + item.height / 2, delta: item.y + item.height / 2 - (next.y + next.height / 2) },
{ type: "bottom", value: item.y + item.height, delta: item.y + item.height - (next.y + next.height) },
{ type: "left", value: item.x, delta: item.x - next.x },
{ type: "centerX", value: item.x + item.width / 2, delta: item.x + item.width / 2 - (next.x + next.width / 2) },
{ type: "right", value: item.x + item.width, delta: item.x + item.width - (next.x + next.width) },
];
for (const guide of guides) {
if (Math.abs(guide.delta) > threshold) continue;
if (guide.type === "top" || guide.type === "centerY" || guide.type === "bottom") {
if (isBetterGuide(guide, best.y, verticalPreference)) {
best.y = { ...guide, targetId: item.id };
}
} else {
if (isBetterGuide(guide, best.x, horizontalPreference)) {
best.x = { ...guide, targetId: item.id };
}
}
}
}
if (best.x) {
next = { ...next, x: next.x + best.x.delta };
snapGuides.push({ type: best.x.type, value: best.x.value, targetId: best.x.targetId });
}
if (best.y) {
next = { ...next, y: next.y + best.y.delta };
snapGuides.push({ type: best.y.type, value: best.y.value, targetId: best.y.targetId });
}
return { ...next, snapGuides };
}
function isBetterGuide(candidate, current, preference) {
if (!current) return true;
const candidateDistance = Math.abs(candidate.delta);
const currentDistance = Math.abs(current.delta);
if (candidateDistance < currentDistance) return true;
if (candidateDistance > currentDistance) return false;
return preference.has(candidate.type) && !preference.has(current.type);
}
export function distributeEvenly(items, selectedIds) {
if (!Array.isArray(selectedIds) || selectedIds.length < 3) return items;
const selected = items
.filter((item) => selectedIds.includes(item.id))
.sort((a, b) => a.x - b.x);
if (selected.length < 3) return items;
const first = selected[0];
const last = selected[selected.length - 1];
const step = (last.x - first.x) / (selected.length - 1);
const nextById = new Map(selected.map((item, index) => [item.id, {
...item,
x: Math.round((first.x + step * index) * 100) / 100,
}]));
return items.map((item) => nextById.get(item.id) || item);
}
export function exportWorkbenchLayout(city, items, stage) {
const books = {};
items.forEach((item) => {
books[item.id] = {
title: item.title,
category: item.category,
x: round(item.x),
y: round(item.y),
width: round(item.width),
height: round(item.height),
rx: roundRatio(item.x, stage.width),
ry: roundRatio(item.y, stage.height),
};
});
return {
city,
exportedAt: new Date().toISOString(),
stage: {
width: Math.round(stage.width),
height: Math.round(stage.height),
},
books,
};
}
function round(value) {
return Math.round(value * 100) / 100;
}
function roundRatio(value, size) {
if (!size) return 0;
return Math.round((value / size) * 10000) / 10000;
}
|