Thomas G. Lopes commited on
Commit
aecbec0
·
1 Parent(s): 6c3123d

fix overlaps

Browse files
Files changed (1) hide show
  1. src/routes/canvas/chat-node.svelte +122 -3
src/routes/canvas/chat-node.svelte CHANGED
@@ -23,7 +23,7 @@
23
  };
24
  let { id, data }: Props = $props();
25
 
26
- let { updateNodeData, updateNode, getNode } = useSvelteFlow();
27
  onMount(() => {
28
  if (!data.modelId) data.modelId = models.trending[0]?.id;
29
  if (!data.provider) data.provider = "auto";
@@ -140,6 +140,110 @@
140
  if (!data.response) return "";
141
  return marked(data.response);
142
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </script>
144
 
145
  <div
@@ -221,9 +325,16 @@
221
  onclick={() => {
222
  const curr = getNode(id);
223
  const newNodeId = crypto.randomUUID();
 
 
 
 
 
 
 
224
  const newNode: Node = {
225
  id: newNodeId,
226
- position: { x: (curr?.position.x ?? 100) + 50, y: (curr?.position.y ?? 0) + 50 },
227
  data: {
228
  query: data.query,
229
  response: data.response,
@@ -263,9 +374,17 @@
263
  hover:bg-gray-900 focus:ring-2 focus:ring-gray-900/20 focus:outline-none active:scale-[0.98]"
264
  onclick={() => {
265
  const curr = getNode(id);
 
 
 
 
 
 
 
 
266
  const newNode: Node = {
267
  id: crypto.randomUUID(),
268
- position: { x: curr?.position.x ?? 100, y: (curr?.position.y ?? 0) + size.height + 40 },
269
  data: { query: "", response: "", modelId: data.modelId, provider: data.provider },
270
  type: "chat",
271
  width: undefined,
 
23
  };
24
  let { id, data }: Props = $props();
25
 
26
+ let { updateNodeData, updateNode, getNode, getViewport } = useSvelteFlow();
27
  onMount(() => {
28
  if (!data.modelId) data.modelId = models.trending[0]?.id;
29
  if (!data.provider) data.provider = "auto";
 
140
  if (!data.response) return "";
141
  return marked(data.response);
142
  });
143
+
144
+ // Helper function to get actual node dimensions from DOM, converted to flow coordinates
145
+ function getNodeDimensions(nodeId: string) {
146
+ const nodeElement = document.querySelector(`[data-id="${nodeId}"]`);
147
+ if (nodeElement) {
148
+ const rect = nodeElement.getBoundingClientRect();
149
+ const viewport = getViewport();
150
+ // Convert from screen coordinates to flow coordinates
151
+ const flowWidth = rect.width / viewport.zoom;
152
+ const flowHeight = rect.height / viewport.zoom;
153
+ return { width: flowWidth, height: flowHeight };
154
+ }
155
+ // Fallback to default size
156
+ return { width: 500, height: 200 };
157
+ }
158
+
159
+ // Helper function to find a non-overlapping position
160
+ function findAvailablePosition(
161
+ startX: number,
162
+ startY: number,
163
+ newNodeWidth = 500,
164
+ newNodeHeight = 200,
165
+ constrainBelow = false,
166
+ ) {
167
+ const spacing = 40;
168
+ let x = startX;
169
+ let y = startY;
170
+
171
+ // Check if position overlaps with any existing node
172
+ const isOverlapping = (testX: number, testY: number) => {
173
+ return nodes.current.some(node => {
174
+ if (node.id === id) return false; // Don't check against self
175
+
176
+ const existingDims = getNodeDimensions(node.id);
177
+ const nodeWidth = existingDims.width;
178
+ const nodeHeight = existingDims.height;
179
+
180
+ // Check for overlap with proper spacing
181
+ return !(
182
+ testX >= node.position.x + nodeWidth + spacing ||
183
+ testX + newNodeWidth + spacing <= node.position.x ||
184
+ testY >= node.position.y + nodeHeight + spacing ||
185
+ testY + newNodeHeight + spacing <= node.position.y
186
+ );
187
+ });
188
+ };
189
+
190
+ // For add node (constrainBelow = true), maintain same Y level and search horizontally
191
+ if (constrainBelow) {
192
+ const fixedY = y; // Always use the same Y distance from parent
193
+
194
+ // Try the preferred X position first
195
+ if (!isOverlapping(x, fixedY)) return { x, y: fixedY };
196
+
197
+ // Search horizontally at the fixed Y level, checking each position carefully
198
+ let testX = x;
199
+ let attempts = 0;
200
+ while (attempts < 50) {
201
+ // Try moving right by small increments
202
+ testX += 50;
203
+ if (!isOverlapping(testX, fixedY)) {
204
+ return { x: testX, y: fixedY };
205
+ }
206
+ attempts++;
207
+ }
208
+
209
+ // Try going left from original position
210
+ testX = x;
211
+ attempts = 0;
212
+ while (attempts < 50) {
213
+ testX -= 50;
214
+ if (!isOverlapping(testX, fixedY)) {
215
+ return { x: testX, y: fixedY };
216
+ }
217
+ attempts++;
218
+ }
219
+
220
+ // Fallback: force position far to the right
221
+ return { x: x + 2000, y: fixedY };
222
+ }
223
+
224
+ // For duplicate (constrainBelow = false), use spiral pattern
225
+ let offset = 0;
226
+ while (offset < 1000) {
227
+ // Try right
228
+ if (!isOverlapping(x + offset, y)) return { x: x + offset, y };
229
+ // Try left
230
+ if (!isOverlapping(x - offset, y)) return { x: x - offset, y };
231
+ // Try down
232
+ if (!isOverlapping(x, y + offset)) return { x, y: y + offset };
233
+ // Try up
234
+ if (!isOverlapping(x, y - offset)) return { x, y: y - offset };
235
+ // Try diagonal combinations
236
+ if (!isOverlapping(x + offset, y + offset)) return { x: x + offset, y: y + offset };
237
+ if (!isOverlapping(x - offset, y + offset)) return { x: x - offset, y: y + offset };
238
+ if (!isOverlapping(x + offset, y - offset)) return { x: x + offset, y: y - offset };
239
+ if (!isOverlapping(x - offset, y - offset)) return { x: x - offset, y: y - offset };
240
+
241
+ offset += 50;
242
+ }
243
+
244
+ // Fallback: return original position with larger offset
245
+ return { x: x + 150, y: y + 150 };
246
+ }
247
  </script>
248
 
249
  <div
 
325
  onclick={() => {
326
  const curr = getNode(id);
327
  const newNodeId = crypto.randomUUID();
328
+ const currentDims = getNodeDimensions(id);
329
+ const preferredPos = findAvailablePosition(
330
+ (curr?.position.x ?? 100) + 50,
331
+ (curr?.position.y ?? 0) + 50,
332
+ currentDims.width,
333
+ currentDims.height,
334
+ );
335
  const newNode: Node = {
336
  id: newNodeId,
337
+ position: preferredPos,
338
  data: {
339
  query: data.query,
340
  response: data.response,
 
374
  hover:bg-gray-900 focus:ring-2 focus:ring-gray-900/20 focus:outline-none active:scale-[0.98]"
375
  onclick={() => {
376
  const curr = getNode(id);
377
+ const currentDims = getNodeDimensions(id);
378
+ const preferredPos = findAvailablePosition(
379
+ curr?.position.x ?? 100,
380
+ (curr?.position.y ?? 0) + size.height + 40,
381
+ currentDims.width,
382
+ currentDims.height,
383
+ true, // constrainBelow = true for Add Node
384
+ );
385
  const newNode: Node = {
386
  id: crypto.randomUUID(),
387
+ position: preferredPos,
388
  data: { query: "", response: "", modelId: data.modelId, provider: data.provider },
389
  type: "chat",
390
  width: undefined,