mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(canvas): let cards move when their group is already close to another group
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ec5d11790e
commit
c83bb4bdf9
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// DOM-free: `node --test` imports this file.
|
||||
|
||||
import { apart, BOX_GAP, freeSpot, MAX_ZOOM, MIN_ZOOM } from './canvas-core.js';
|
||||
import { BOX_GAP, freeSpot, MAX_ZOOM, MIN_ZOOM } from './canvas-core.js';
|
||||
|
||||
export const CARD_WIDTH = 260;
|
||||
export const COLUMN_GAP = 48;
|
||||
@@ -464,12 +464,18 @@ const frameAround = (boxes, side, top) => {
|
||||
return grow({ x, y, w: Math.max(...boxes.map(b => b.x + b.w)) - x, h: Math.max(...boxes.map(b => b.y + b.h)) - y }, side, top);
|
||||
};
|
||||
|
||||
// Area of `b` within `gap` of `a`: 0 when they are apart().
|
||||
const crowding = (a, b, gap) => Math.max(0, Math.min(a.x + a.w + gap, b.x + b.w) - Math.max(a.x - gap, b.x))
|
||||
* Math.max(0, Math.min(a.y + a.h + gap, b.y + b.h) - Math.max(a.y - gap, b.y));
|
||||
|
||||
/**
|
||||
* What a dragged card must keep clear of, for freeSpot(): `others` are the
|
||||
* other cards and the other zone frames (and, server view, the other project /
|
||||
* environment frames in its zone), grown by the room the card's own frames
|
||||
* take around it. `fits` checks the card's own frames, which also span its
|
||||
* other cards, against those frames.
|
||||
* other cards, against those frames: they must not come closer to them than
|
||||
* at the start of the drag. Frames that are already too close (the layout
|
||||
* uses estimated card heights) do not lock the cards in place.
|
||||
*/
|
||||
export function dragRules(graph, positions, sizes, id) {
|
||||
const node = graph.nodes.find(n => n.id === id);
|
||||
@@ -487,11 +493,20 @@ export function dragRules(graph, positions, sizes, id) {
|
||||
const groupMates = grouped ? rects(own.nodes.filter(n => context(n) === context(node))) : [];
|
||||
// The other box must keep the frame's room on the side that faces the card.
|
||||
const room = (b, s, t) => ({ x: b.x - s, y: b.y - s, w: b.w + s * 2, h: b.h + s + t });
|
||||
const at = node && cardRect(node, positions, sizes);
|
||||
const notCloser = (frame, boxes, gap) => {
|
||||
const start = at ? frame(toBox(at)) : null;
|
||||
return box => {
|
||||
const moved = frame(box);
|
||||
return boxes.every(b => crowding(moved, b, gap) <= (start ? crowding(start, b, gap) : 0));
|
||||
};
|
||||
};
|
||||
const zoneFits = notCloser(box => frameAround([...zoneMates, box], side, top), zones, BOX_GAP);
|
||||
// Same gap between groups as layoutServerView, so a reload does not move the group.
|
||||
const groupFits = grouped ? notCloser(box => frameAround([...groupMates, box], groupSide, groupTop), groups, GROUP_GAP) : () => true;
|
||||
return {
|
||||
others: [...rects(graph.nodes), ...zones.map(z => room(z, side, top)), ...groups.map(g => room(g, groupSide, groupTop))],
|
||||
fits: box => zones.every(z => apart(frameAround([...zoneMates, box], side, top), z, BOX_GAP))
|
||||
// Same gap between groups as layoutServerView, so a reload does not move the group.
|
||||
&& (!grouped || groups.every(g => apart(frameAround([...groupMates, box], groupSide, groupTop), g, GROUP_GAP))),
|
||||
fits: box => zoneFits(box) && groupFits(box),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -629,6 +629,24 @@ test('server view: a dragged card keeps its group as far from another group as t
|
||||
assert.equal(freeSpot({ ...start, x: start.x - 10 }, others, undefined, fits), null);
|
||||
});
|
||||
|
||||
test('server view: cards still move when the measured cards put their group too close to another group', () => {
|
||||
const g = {
|
||||
layout: 'server',
|
||||
zones: [{ id: 'z1', name: 'web' }],
|
||||
nodes: [srv('a', 'application', 'Shop / prod'), srv('c', 'application', 'Blog / prod')],
|
||||
links: [],
|
||||
positions: {},
|
||||
};
|
||||
const before = layoutServerView(g);
|
||||
// The layout uses estimated sizes; the real Blog card is wider, so the groups are less than GROUP_GAP apart.
|
||||
const sizes = { c: { width: CARD_WIDTH + 20, height: estimateNodeHeight(g.nodes[1]) } };
|
||||
const { fits } = dragRules(g, before, sizes, 'a');
|
||||
const start = { ...before.a, w: CARD_WIDTH, h: estimateNodeHeight(g.nodes[0]) };
|
||||
assert.ok(fits(start), 'the card can stay where it is');
|
||||
assert.ok(fits({ ...start, x: start.x + 50 }), 'the card can move away from the other group');
|
||||
assert.ok(!fits({ ...start, x: start.x - 10 }), 'the card cannot move its group closer to the other group');
|
||||
});
|
||||
|
||||
test('groupKey: a group with only an empty-environment placeholder has no key, so it cannot be moved', () => {
|
||||
const environment = { uuid: 'env', name: 'production' };
|
||||
assert.equal(groupKey([{ id: 'empty:env', type: 'empty', environment }], 'empty'), null);
|
||||
|
||||
Reference in New Issue
Block a user