diff --git a/packages/web/src/designer/Designer.svelte b/packages/web/src/designer/Designer.svelte index 8df08f463..94166df7e 100644 --- a/packages/web/src/designer/Designer.svelte +++ b/packages/web/src/designer/Designer.svelte @@ -305,12 +305,13 @@ ...newTables.map(x => ({ ...x, designerId: uuidv1(), + needsArrange: true, })), ], }; }; - const handleAddTableReferences = table => { + const handleAddTableReferences = async table => { if (!dbInfo) return; const db = $dbInfo; if (!db) return; @@ -318,6 +319,10 @@ return getTablesWithReferences(db, table, current); }); updateFromDbInfo(); + await tick(); + + const rect = (domTables[table.designerId] as any)?.getRect(); + arrange(true, false, rect ? { x: (rect.left + rect.right) / 2, y: (rect.top + rect.bottom) / 2 } : null); }; const performAutoActions = async db => { @@ -466,13 +471,18 @@ return settings?.canArrange; } - export function arrange(skipUndoChain = false) { + export function arrange(skipUndoChain = false, arrangeAll = true, circleMiddle = { x: 0, y: 0 }) { const graph = new GraphDefinition(); for (const table of value?.tables || []) { const domTable = domTables[table.designerId] as any; if (!domTable) continue; const rect = domTable.getRect(); - graph.addNode(table.designerId, rect.right - rect.left, rect.bottom - rect.top); + graph.addNode( + table.designerId, + rect.right - rect.left, + rect.bottom - rect.top, + arrangeAll || table.needsArrange ? null : { x: rect.left + rect.right / 2, y: rect.top + rect.bottom / 2 } + ); } for (const reference of value?.references) { @@ -481,7 +491,7 @@ graph.initialize(); - const layout = GraphLayout.createCircle(graph).springyAlg().doMoveSteps().fixViewBox(); + const layout = GraphLayout.createCircle(graph, circleMiddle).springyAlg().doMoveSteps().fixViewBox(); callChange(current => { return { @@ -492,10 +502,14 @@ return node ? { ...table, + needsArrange: false, left: node.x - node.node.width / 2, top: node.y - node.node.height / 2, } - : table; + : { + ...table, + needsArrange: false, + }; }), }; }, skipUndoChain); diff --git a/packages/web/src/designer/GraphLayout.ts b/packages/web/src/designer/GraphLayout.ts index 2f8179cd5..046ba82ee 100644 --- a/packages/web/src/designer/GraphLayout.ts +++ b/packages/web/src/designer/GraphLayout.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { IBoxBounds, rectangleDistance, rectangleIntersectArea, Vector2D } from './designerMath'; +import { IBoxBounds, IPoint, rectangleDistance, rectangleIntersectArea, Vector2D } from './designerMath'; const MIN_NODE_DISTANCE = 50; const SPRING_LENGTH = 100; @@ -18,7 +18,13 @@ const SCORE_ASPECT_RATIO = 1.6; class GraphNode { neightboors: GraphNode[] = []; radius: number; - constructor(public graph: GraphDefinition, public designerId: string, public width: number, public height: number) {} + constructor( + public graph: GraphDefinition, + public designerId: string, + public width: number, + public height: number, + public fixedPosition: IPoint + ) {} initialize() { this.radius = Math.sqrt((this.width * this.width) / 4 + (this.height * this.height) / 4); @@ -43,8 +49,8 @@ export class GraphDefinition { nodes: { [designerId: string]: GraphNode } = {}; edges: GraphEdge[] = []; - addNode(designerId: string, width: number, height: number) { - this.nodes[designerId] = new GraphNode(this, designerId, width, height); + addNode(designerId: string, width: number, height: number, fixedPosition: IPoint) { + this.nodes[designerId] = new GraphNode(this, designerId, width, height, fixedPosition); } addEdge(sourceId: string, targetId: string) { @@ -94,6 +100,7 @@ class LayoutNode { } translate(dx: number, dy: number) { + if (this.node.fixedPosition) return this; return new LayoutNode(this.node, this.x + dx, this.y + dy); } @@ -199,14 +206,18 @@ export class GraphLayout { constructor(public graph: GraphDefinition) {} - static createCircle(graph: GraphDefinition): GraphLayout { + static createCircle(graph: GraphDefinition, middle: IPoint = { x: 0, y: 0 }): GraphLayout { const res = new GraphLayout(graph); if (_.isEmpty(graph.nodes)) return res; const addedNodes = new Set(); const circleSortedNodes: GraphNode[] = []; - addNodeNeighboors(_.values(graph.nodes), circleSortedNodes, addedNodes); + addNodeNeighboors( + _.values(graph.nodes).filter(x => !x.fixedPosition), + circleSortedNodes, + addedNodes + ); const nodeRadius = _.max(circleSortedNodes.map(x => x.radius)); const nodeCount = circleSortedNodes.length; const radius = (nodeCount * nodeRadius) / (2 * Math.PI) + nodeRadius; @@ -214,9 +225,18 @@ export class GraphLayout { let angle = 0; const dangle = (2 * Math.PI) / circleSortedNodes.length; for (const node of circleSortedNodes) { - res.nodes[node.designerId] = new LayoutNode(node, Math.sin(angle) * radius, Math.cos(angle) * radius); + res.nodes[node.designerId] = new LayoutNode( + node, + middle.x + Math.sin(angle) * radius, + middle.y + Math.cos(angle) * radius + ); angle += dangle; } + + for (const node of _.values(graph.nodes).filter(x => x.fixedPosition)) { + res.nodes[node.designerId] = new LayoutNode(node, node.fixedPosition.x, node.fixedPosition.y); + } + res.fillEdges(); return res; @@ -289,6 +309,7 @@ export class GraphLayout { } tryMoveNode(node: LayoutNode): GraphLayout[] { + if (node.node.fixedPosition) return []; return [ this.changePositions(x => (x == node ? node.translate(MOVE_STEP, 0) : x)), this.changePositions(x => (x == node ? node.translate(-MOVE_STEP, 0) : x)), diff --git a/packages/web/src/designer/designerMath.ts b/packages/web/src/designer/designerMath.ts index ce7cf584b..940be79af 100644 --- a/packages/web/src/designer/designerMath.ts +++ b/packages/web/src/designer/designerMath.ts @@ -1,4 +1,4 @@ -interface IPoint { +export interface IPoint { x: number; y: number; }