@eventcatalog/core 5.0.0-beta.11 → 5.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +4 -4
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-MFEMKQZ6.js → chunk-BO2HHVZ3.js} +1 -1
- package/dist/{chunk-4YFM63DS.js → chunk-JPT3QC5V.js} +1 -1
- package/dist/{chunk-76DAAJLV.js → chunk-PLTLW45Q.js} +5 -5
- package/dist/{chunk-WM4QYMIA.js → chunk-T75DOVFP.js} +1 -1
- package/dist/{chunk-LQZ7IG7B.js → chunk-XA5W6DXX.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +2 -2
- package/dist/eventcatalog.js +14 -14
- package/dist/federation/federate.js +3 -3
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +42 -3
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
- package/eventcatalog/src/components/NavigationProgress.astro +7 -0
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +3 -3
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +3 -1
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +7 -5
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +132 -70
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +71 -71
- package/eventcatalog/src/features/studio/agent-choreography.ts +13 -10
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +64 -15
- package/eventcatalog/src/features/studio/api/client.ts +6 -1
- package/eventcatalog/src/features/studio/canvas-actions.ts +160 -34
- package/eventcatalog/src/features/studio/canvas-preview.ts +1 -2
- package/eventcatalog/src/features/studio/catalog.ts +16 -14
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +263 -115
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +15 -14
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +272 -119
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +1 -2
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +28 -44
- package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +101 -26
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +119 -230
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
- package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +79 -0
- package/eventcatalog/src/features/studio/components/Presence.tsx +52 -45
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +277 -141
- package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1286 -411
- package/eventcatalog/src/features/studio/document-name.ts +7 -0
- package/eventcatalog/src/features/studio/from-visualiser.ts +138 -0
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +19 -2
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +9 -3
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +2 -2
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +8 -3
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +223 -30
- package/eventcatalog/src/features/studio/identity.ts +23 -1
- package/eventcatalog/src/features/studio/limits.ts +2 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +2 -1
- package/eventcatalog/src/features/studio/pages/index.astro +2 -2
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +8 -23
- package/eventcatalog/src/features/studio/server/runtime.ts +16 -9
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
- package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
- package/package.json +3 -3
- package/eventcatalog/src/features/studio/canvas-doc.ts +0 -414
- package/eventcatalog/src/features/studio/clipboard.ts +0 -97
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +0 -154
- package/eventcatalog/src/features/studio/components/Comments.tsx +0 -770
- package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +0 -145
- package/eventcatalog/src/features/studio/components/DragGhost.tsx +0 -115
- package/eventcatalog/src/features/studio/components/Picture.tsx +0 -21
- package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +0 -212
- package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +0 -255
- package/eventcatalog/src/features/studio/components/icons.ts +0 -53
- package/eventcatalog/src/features/studio/components/status.ts +0 -53
- package/eventcatalog/src/features/studio/edges.ts +0 -56
- package/eventcatalog/src/features/studio/flow-state.ts +0 -92
- package/eventcatalog/src/features/studio/grouping.ts +0 -226
- package/eventcatalog/src/features/studio/layout.ts +0 -42
- package/eventcatalog/src/features/studio/levels.ts +0 -282
- package/eventcatalog/src/features/studio/node-types.ts +0 -205
- package/eventcatalog/src/features/studio/placement.ts +0 -197
- package/eventcatalog/src/features/studio/studio.css +0 -273
- package/dist/{chunk-KHACCTW2.js → chunk-ZEY2XUJN.js} +4 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Canvases' documents on the collaboration server are named `design:<canvasId>` (scripts and peers join by it)
|
|
2
|
+
const DOCUMENT_PREFIX = 'design:';
|
|
3
|
+
export const canvasDocumentName = (canvasId: string) => `${DOCUMENT_PREFIX}${canvasId}`;
|
|
4
|
+
export const canvasIdFromDocumentName = (name: string) =>
|
|
5
|
+
name.startsWith(DOCUMENT_PREFIX) ? name.slice(DOCUMENT_PREFIX.length) : undefined;
|
|
6
|
+
/** Why the collaboration server refuses a canvas that was deleted (told to anyone who still has it open) */
|
|
7
|
+
export const CANVAS_DELETED_REASON = 'canvas-deleted';
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Edge, Node } from '@xyflow/react';
|
|
2
|
+
import type { VisualiserSnapshot } from '@eventcatalog/visualiser';
|
|
3
|
+
import { getNodeCatalogResource } from '@utils/node-graphs/node-resource';
|
|
4
|
+
import type { DiagramLevelsSpec, NewCanvasEdges, NodeSpecs } from './canvas-actions';
|
|
5
|
+
import { DEFAULT_NODE_SIZE, isCatalogCollection, isGroupType } from '@eventcatalog/visualiser/studio';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A canvas started from a diagram (the visualiser's "Open in Studio"): the catalog resources and actors it shows,
|
|
9
|
+
* where they are on it (the same top-left corners), in the domains and systems they're shown in, and the
|
|
10
|
+
* connections between them, drawn along the same routes. Other nodes (e.g. flow steps, users, entities) are left
|
|
11
|
+
* out, with their connections. Its levels 1 and 2 come as the diagram shows them, for Studio to show them the same.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Actors (people and roles) aren't catalog resources: they go on the canvas as Studio's actors, by the name shown
|
|
15
|
+
const ACTOR_TYPES = new Set(['context-actor', 'actor']);
|
|
16
|
+
const actorNameOf = (node: VisualiserSnapshot['nodes'][number]) =>
|
|
17
|
+
node.type && ACTOR_TYPES.has(node.type) && typeof node.data.name === 'string' ? node.data.name : undefined;
|
|
18
|
+
|
|
19
|
+
export const canvasFromVisualiser = (
|
|
20
|
+
snapshot: VisualiserSnapshot
|
|
21
|
+
): { nodes: NodeSpecs; edges: NewCanvasEdges; levels: DiagramLevelsSpec } => {
|
|
22
|
+
const byId = new Map(snapshot.nodes.map((node) => [node.id, node]));
|
|
23
|
+
const depthOf = (node: VisualiserSnapshot['nodes'][number]): number => {
|
|
24
|
+
const parent = node.parentId ? byId.get(node.parentId) : undefined;
|
|
25
|
+
return parent ? 1 + depthOf(parent) : 0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const containers = new Set<string>();
|
|
29
|
+
const parentOf = new Map<string, string>();
|
|
30
|
+
const nodes = [...snapshot.nodes]
|
|
31
|
+
// Containers before what's in them (specs refer to containers given before them)
|
|
32
|
+
.sort((a, b) => depthOf(a) - depthOf(b))
|
|
33
|
+
.flatMap((node) => {
|
|
34
|
+
const resource = getNodeCatalogResource(node);
|
|
35
|
+
const fromCatalog =
|
|
36
|
+
resource && isCatalogCollection(resource.collection) ? { collection: resource.collection, id: resource.id } : undefined;
|
|
37
|
+
const actorName = actorNameOf(node);
|
|
38
|
+
if (!fromCatalog && !actorName) return [];
|
|
39
|
+
// Domains and systems shown as boxes are containers on the canvas
|
|
40
|
+
const container = isGroupType(node.type);
|
|
41
|
+
// In the innermost container shown that's on the canvas
|
|
42
|
+
let inside: string | undefined;
|
|
43
|
+
for (let parent = node.parentId; parent && !inside; parent = byId.get(parent)?.parentId) {
|
|
44
|
+
if (containers.has(parent)) inside = parent;
|
|
45
|
+
}
|
|
46
|
+
if (container) containers.add(node.id);
|
|
47
|
+
if (inside) parentOf.set(node.id, inside);
|
|
48
|
+
// Where it is: its top-left corner, as cards are put by their centre at the size they're created at (and
|
|
49
|
+
// render at the size they need from there), and containers at their size
|
|
50
|
+
const left = node.center.x - node.width / 2;
|
|
51
|
+
const top = node.center.y - node.height / 2;
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
ref: node.id,
|
|
55
|
+
...(fromCatalog ? { resource: fromCatalog } : { type: 'actor', name: actorName }),
|
|
56
|
+
...(container
|
|
57
|
+
? { x: node.center.x, y: node.center.y, container: true, width: node.width, height: node.height }
|
|
58
|
+
: { x: left + DEFAULT_NODE_SIZE.width / 2, y: top + DEFAULT_NODE_SIZE.height / 2 }),
|
|
59
|
+
...(inside && { inside }),
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Connections between nodes on the canvas, not between a container and what's in it (it holds it instead)
|
|
65
|
+
const onCanvas = new Set(nodes.map((node) => node.ref));
|
|
66
|
+
const isInside = (nodeId: string, containerId: string) => {
|
|
67
|
+
for (let parent = parentOf.get(nodeId); parent; parent = parentOf.get(parent)) if (parent === containerId) return true;
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
70
|
+
const edges = snapshot.edges
|
|
71
|
+
.filter(({ source, target }) => onCanvas.has(source) && onCanvas.has(target) && source !== target)
|
|
72
|
+
.filter(({ source, target }) => !isInside(source, target) && !isInside(target, source))
|
|
73
|
+
.map(({ source, target, label, route }) => ({ from: source, to: target, ...(label && { label }), ...(route && { route }) }));
|
|
74
|
+
|
|
75
|
+
const levels = {
|
|
76
|
+
...(snapshot.levels[1] && { 1: levelFromVisualiser(snapshot.levels[1], nodes) }),
|
|
77
|
+
...(snapshot.levels[2] && { 2: levelFromVisualiser(snapshot.levels[2], nodes) }),
|
|
78
|
+
...(Object.keys(snapshot.unavailableLevels).length > 0 && { unavailable: snapshot.unavailableLevels }),
|
|
79
|
+
};
|
|
80
|
+
return { nodes, edges, levels };
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const sameResource = (a?: { collection: string; id: string }, b?: { collection: string; id: string }) =>
|
|
84
|
+
!!a && !!b && a.collection === b.collection && a.id === b.id;
|
|
85
|
+
const sizeOf = (node: Node) => ({
|
|
86
|
+
width: typeof node.style?.width === 'number' ? node.style.width : (node.width ?? node.measured?.width),
|
|
87
|
+
height: typeof node.style?.height === 'number' ? node.style.height : (node.height ?? node.measured?.height),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* One of the diagram's levels, as it shows it: each node stands for the canvas node with the same id (on level 3),
|
|
92
|
+
* or the same catalog resource (e.g. a system shown as a card on level 1 and as a container on level 3), so moving
|
|
93
|
+
* between levels glides from one to the other. Nodes only on the level keep their own id.
|
|
94
|
+
*/
|
|
95
|
+
const levelFromVisualiser = (level: { nodes: Node[]; edges: Edge[] }, specs: NodeSpecs) => {
|
|
96
|
+
const used = new Set<string>();
|
|
97
|
+
const refs = new Map<string, string>();
|
|
98
|
+
for (const node of level.nodes) {
|
|
99
|
+
const resource = getNodeCatalogResource(node);
|
|
100
|
+
const ref = [
|
|
101
|
+
specs.find((spec) => spec.ref === node.id)?.ref,
|
|
102
|
+
specs.find((spec) => sameResource(spec.resource, resource))?.ref,
|
|
103
|
+
].find((candidate) => candidate && !used.has(candidate));
|
|
104
|
+
refs.set(node.id, ref ?? node.id);
|
|
105
|
+
used.add(ref ?? node.id);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
nodes: level.nodes.map((node) => {
|
|
109
|
+
const { width, height } = sizeOf(node);
|
|
110
|
+
// The diagram marks the resource whose page it's on ("Viewing"): a canvas isn't on one
|
|
111
|
+
const { isFocused: _, ...data } = node.data as Record<string, unknown>;
|
|
112
|
+
return {
|
|
113
|
+
ref: refs.get(node.id)!,
|
|
114
|
+
type: node.type ?? 'default',
|
|
115
|
+
data,
|
|
116
|
+
...(node.parentId && refs.has(node.parentId) && { parent: refs.get(node.parentId) }),
|
|
117
|
+
x: node.position.x,
|
|
118
|
+
y: node.position.y,
|
|
119
|
+
...(isGroupType(node.type) && width && height && { width, height }),
|
|
120
|
+
};
|
|
121
|
+
}),
|
|
122
|
+
edges: level.edges.flatMap((edge) => {
|
|
123
|
+
const [from, to] = [refs.get(edge.source), refs.get(edge.target)];
|
|
124
|
+
if (!from || !to) return [];
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
from,
|
|
128
|
+
to,
|
|
129
|
+
...(typeof edge.label === 'string' && { label: edge.label }),
|
|
130
|
+
...(edge.type && { type: edge.type }),
|
|
131
|
+
...(edge.style && { style: edge.style as Record<string, unknown> }),
|
|
132
|
+
...(edge.markerEnd && { markerEnd: edge.markerEnd }),
|
|
133
|
+
...(edge.data && { data: edge.data }),
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
};
|
|
@@ -2,6 +2,11 @@ import type { Awareness } from 'y-protocols/awareness';
|
|
|
2
2
|
|
|
3
3
|
/** About 30 times a second, like Excalidraw and tldraw: smooth for others, without a message on every mousemove */
|
|
4
4
|
export const PRESENCE_INTERVAL_MS = 33;
|
|
5
|
+
/**
|
|
6
|
+
* Once a second when nobody else is on the canvas (people or agents), as tldraw does: nobody sees it, and the
|
|
7
|
+
* collaboration server sends every one back. Someone arriving gets what's waiting straight away.
|
|
8
|
+
*/
|
|
9
|
+
export const ALONE_INTERVAL_MS = 1000;
|
|
5
10
|
|
|
6
11
|
export type PresenceSender = {
|
|
7
12
|
/** Share these fields of our presence: straight away if nothing was sent lately, otherwise with the next send */
|
|
@@ -16,10 +21,15 @@ export type PresenceSender = {
|
|
|
16
21
|
* sends the whole state on every change, so fields changed together (the pointer and the node it drags) go in one
|
|
17
22
|
* message, and the latest value always goes out (the pointer stops where it really stopped).
|
|
18
23
|
*/
|
|
19
|
-
export function createPresenceSender(
|
|
24
|
+
export function createPresenceSender(
|
|
25
|
+
awareness: Awareness,
|
|
26
|
+
intervalMs = PRESENCE_INTERVAL_MS,
|
|
27
|
+
aloneIntervalMs = ALONE_INTERVAL_MS
|
|
28
|
+
): PresenceSender {
|
|
20
29
|
let pending: Record<string, unknown> | undefined;
|
|
21
30
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
22
31
|
let lastSent = -Infinity;
|
|
32
|
+
const alone = () => awareness.getStates().size <= 1;
|
|
23
33
|
|
|
24
34
|
const flush = () => {
|
|
25
35
|
clearTimeout(timer);
|
|
@@ -30,17 +40,24 @@ export function createPresenceSender(awareness: Awareness, intervalMs = PRESENCE
|
|
|
30
40
|
lastSent = performance.now();
|
|
31
41
|
awareness.setLocalState({ ...awareness.getLocalState(), ...fields });
|
|
32
42
|
};
|
|
43
|
+
// Someone arrived: they get what's waiting now, not at the next (slower) send
|
|
44
|
+
const onChange = ({ added }: { added: number[] }) => {
|
|
45
|
+
if (pending && added.some((id) => id !== awareness.clientID)) flush();
|
|
46
|
+
};
|
|
47
|
+
awareness.on('change', onChange);
|
|
33
48
|
|
|
34
49
|
return {
|
|
35
50
|
send: (fields) => {
|
|
36
51
|
pending = { ...pending, ...fields };
|
|
37
52
|
if (timer) return;
|
|
38
|
-
|
|
53
|
+
const interval = alone() ? aloneIntervalMs : intervalMs;
|
|
54
|
+
timer = setTimeout(flush, Math.max(0, interval - (performance.now() - lastSent)));
|
|
39
55
|
},
|
|
40
56
|
flush,
|
|
41
57
|
destroy: () => {
|
|
42
58
|
clearTimeout(timer);
|
|
43
59
|
pending = undefined;
|
|
60
|
+
awareness.off('change', onChange);
|
|
44
61
|
},
|
|
45
62
|
};
|
|
46
63
|
}
|
|
@@ -2,10 +2,16 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type * as Y from 'yjs';
|
|
4
4
|
import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';
|
|
5
|
-
import { replyToThread, setThreadResolved, type Author, CANVAS_STATUSES, setCanvasStatus } from '../canvas-doc';
|
|
6
5
|
import {
|
|
7
|
-
|
|
6
|
+
replyToThread,
|
|
7
|
+
setThreadResolved,
|
|
8
|
+
type Author,
|
|
9
|
+
CANVAS_STATUSES,
|
|
10
|
+
setCanvasStatus,
|
|
8
11
|
CATALOG_COLLECTIONS,
|
|
12
|
+
} from '@eventcatalog/visualiser/studio';
|
|
13
|
+
import {
|
|
14
|
+
ADD_TO_CANVAS_DESCRIPTION,
|
|
9
15
|
describeCanvas,
|
|
10
16
|
edgeSpecsSchema,
|
|
11
17
|
nodeSpecsSchema,
|
|
@@ -284,7 +290,7 @@ const createCanvasTools = (get: () => CanvasWebMcpOptions, getStage: () => Agent
|
|
|
284
290
|
name: 'layout_canvas',
|
|
285
291
|
title: 'Tidy the canvas',
|
|
286
292
|
description:
|
|
287
|
-
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes), for everyone on it.
|
|
293
|
+
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes), for everyone on it. Call it once when you finish a round of changes, before you hand back to the user, so the canvas is tidy for everyone, and whenever the user asks to tidy up. Don't call it after every change (add_to_canvas already places what you add next to what it connects to while you work), and skip it if the user asked to keep the layout as it is: it moves everything, including what people placed themselves.",
|
|
288
294
|
input: z.object({}),
|
|
289
295
|
execute: async () => {
|
|
290
296
|
await get().reorder();
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
type Author,
|
|
14
14
|
type CommentAnchor,
|
|
15
15
|
type Thread,
|
|
16
|
-
} from '
|
|
16
|
+
} from '@eventcatalog/visualiser/studio';
|
|
17
17
|
|
|
18
|
-
export type { Author, CommentAnchor, Message, Thread } from '
|
|
18
|
+
export type { Author, CommentAnchor, Message, Thread } from '@eventcatalog/visualiser/studio';
|
|
19
19
|
|
|
20
20
|
/** Comment threads in the shared canvas document */
|
|
21
21
|
export function useComments(doc: Y.Doc | null, author: Author) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { getViewportForBounds, useReactFlow, useStore, type Rect } from '@xyflow/react';
|
|
3
|
+
import { DIAGRAM_FIT_VIEW_OPTIONS } from '@eventcatalog/visualiser';
|
|
3
4
|
import type { Peer } from './presence-store';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -30,10 +31,13 @@ const inView = (rect: Rect, view: Rect, margin: number) =>
|
|
|
30
31
|
|
|
31
32
|
export function useFollowCamera({
|
|
32
33
|
enabled,
|
|
34
|
+
fitsContent,
|
|
33
35
|
agentsWorking,
|
|
34
36
|
getAgents,
|
|
35
37
|
}: {
|
|
36
38
|
enabled: boolean;
|
|
39
|
+
/** Whether it fits the canvas's content when it arrives (the canvas people edit, not a level, fitted on its own) */
|
|
40
|
+
fitsContent: boolean;
|
|
37
41
|
/** Whether any agent is working on the canvas (has a pointer on it) */
|
|
38
42
|
agentsWorking: boolean;
|
|
39
43
|
/** The agents working, with where their pointers are now (read when the camera checks, not on every move) */
|
|
@@ -61,11 +65,12 @@ export function useFollowCamera({
|
|
|
61
65
|
hasFitContent.current = false;
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
|
-
if (hasFitContent.current || !width) return;
|
|
68
|
+
if (hasFitContent.current || !width || !fitsContent) return;
|
|
65
69
|
hasFitContent.current = true;
|
|
66
|
-
|
|
70
|
+
// Fitted like the visualiser fits a diagram
|
|
71
|
+
const frame = requestAnimationFrame(() => void fitView({ ...DIAGRAM_FIT_VIEW_OPTIONS, duration: 300 }));
|
|
67
72
|
return () => cancelAnimationFrame(frame);
|
|
68
|
-
}, [hasNodes, width]);
|
|
73
|
+
}, [hasNodes, width, fitsContent]);
|
|
69
74
|
|
|
70
75
|
const follow = useCallback(() => {
|
|
71
76
|
const { enabled, getAgents } = latest.current;
|