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;
}