@eventcatalog/core 5.0.0-beta.12 → 5.0.0-beta.14
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/LICENSE +13 -12
- 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 +3 -3
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-TSY5CC27.js → chunk-4KE3EUDJ.js} +1 -1
- package/dist/{chunk-FY6MSP6J.js → chunk-I3XDOICA.js} +1 -1
- package/dist/{chunk-QB7X34OD.js → chunk-OFK7U7BX.js} +1 -1
- package/dist/{chunk-CX2VB7AI.js → chunk-PI5YNZZZ.js} +1 -1
- package/dist/{chunk-BWC4YKW3.js → chunk-WY6M3RPT.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +11 -6
- package/dist/eventcatalog.js +14 -9
- 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/astro.config.mjs +13 -1
- package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
- package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +11 -3
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -76
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +122 -1
- package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
- 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 +133 -71
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +66 -66
- package/eventcatalog/src/features/studio/agent-choreography.ts +5 -4
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +1 -1
- package/eventcatalog/src/features/studio/canvas-actions.ts +22 -14
- package/eventcatalog/src/features/studio/canvas-preview.ts +1 -2
- package/eventcatalog/src/features/studio/catalog.ts +1 -4
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +246 -115
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +7 -3
- 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 +1 -1
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +101 -26
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +102 -241
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
- package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +1 -1
- package/eventcatalog/src/features/studio/components/Presence.tsx +6 -8
- 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 +1005 -354
- package/eventcatalog/src/features/studio/document-name.ts +7 -0
- package/eventcatalog/src/features/studio/from-visualiser.ts +1 -1
- 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-studio-flow.ts +162 -23
- package/eventcatalog/src/features/studio/identity.ts +23 -1
- package/eventcatalog/src/features/studio/pages/[id].astro +2 -1
- package/eventcatalog/src/features/studio/pages/index.astro +1 -2
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +7 -22
- package/eventcatalog/src/features/studio/server/runtime.ts +4 -5
- package/package.json +3 -3
- package/eventcatalog/src/features/studio/canvas-doc.ts +0 -569
- package/eventcatalog/src/features/studio/clipboard.ts +0 -97
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +0 -119
- package/eventcatalog/src/features/studio/components/Comments.tsx +0 -777
- 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 -271
- 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 -121
- package/eventcatalog/src/features/studio/grouping.ts +0 -230
- package/eventcatalog/src/features/studio/hooks/use-counter-scale.ts +0 -20
- package/eventcatalog/src/features/studio/layout.ts +0 -56
- package/eventcatalog/src/features/studio/level-layout.ts +0 -145
- package/eventcatalog/src/features/studio/levels.ts +0 -337
- package/eventcatalog/src/features/studio/node-types.ts +0 -245
- package/eventcatalog/src/features/studio/placement.ts +0 -197
- package/eventcatalog/src/features/studio/studio.css +0 -273
|
@@ -1,13 +1,104 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ArrowLeft,
|
|
4
|
+
Bot,
|
|
5
|
+
Check,
|
|
6
|
+
Copy,
|
|
7
|
+
Globe,
|
|
8
|
+
Link2,
|
|
9
|
+
Mail,
|
|
10
|
+
MessageSquareText,
|
|
11
|
+
Pencil,
|
|
12
|
+
Plug,
|
|
13
|
+
UserPlus,
|
|
14
|
+
Users,
|
|
15
|
+
X,
|
|
16
|
+
} from 'lucide-react';
|
|
3
17
|
import { usePeople, type Peer, type PresenceStore } from '../hooks/presence-store';
|
|
4
|
-
import Picture from '
|
|
18
|
+
import { Picture, AgentMark } from '@eventcatalog/visualiser/studio';
|
|
5
19
|
|
|
6
20
|
/**
|
|
7
|
-
* Sharing a canvas,
|
|
8
|
-
*
|
|
9
|
-
* and
|
|
21
|
+
* Sharing a canvas, as Studio on the web does: first the ways to share, side by side (a link to it, working on it
|
|
22
|
+
* together live, pairing with an AI agent), then, for live collaboration, who's on it now (click someone to go to
|
|
23
|
+
* what they're looking at) and the link to invite people with (copied, emailed or as a ready-made message). A canvas
|
|
24
|
+
* here is always live and kept in the catalog: its link is the invite, so there's no session to start.
|
|
10
25
|
*/
|
|
26
|
+
|
|
27
|
+
const button =
|
|
28
|
+
'inline-flex h-9 flex-none items-center gap-2 rounded-lg px-4 text-sm font-medium transition-colors [&_svg]:h-4 [&_svg]:w-4';
|
|
29
|
+
const primary = `${button} bg-[rgb(var(--ec-page-text))] text-[rgb(var(--ec-page-bg))] hover:bg-[rgb(var(--ec-page-text)/0.85)]`;
|
|
30
|
+
const secondary = `${button} border border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))]`;
|
|
31
|
+
const sectionLabel = 'text-[11px] font-semibold uppercase tracking-wide text-[rgb(var(--ec-page-text-muted))]';
|
|
32
|
+
|
|
33
|
+
/** e.g. catalog.example.com/studio/9b1d… */
|
|
34
|
+
function shortened(url: string) {
|
|
35
|
+
try {
|
|
36
|
+
const { host, pathname } = new URL(url);
|
|
37
|
+
return `${host}${pathname}`;
|
|
38
|
+
} catch {
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const copyText = async (text: string) => {
|
|
44
|
+
try {
|
|
45
|
+
await navigator.clipboard.writeText(text);
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** One of the ways to share, as a card: what it is, what it gives them, and what to do */
|
|
53
|
+
function Choice({
|
|
54
|
+
icon,
|
|
55
|
+
title,
|
|
56
|
+
description,
|
|
57
|
+
points,
|
|
58
|
+
children,
|
|
59
|
+
}: {
|
|
60
|
+
icon: ReactNode;
|
|
61
|
+
title: string;
|
|
62
|
+
description: string;
|
|
63
|
+
points: string[];
|
|
64
|
+
children: ReactNode;
|
|
65
|
+
}) {
|
|
66
|
+
return (
|
|
67
|
+
<section className="flex flex-col rounded-xl border border-[rgb(var(--ec-page-border))] p-5 text-left transition-colors hover:border-[color-mix(in_srgb,rgb(var(--ec-accent))_45%,rgb(var(--ec-page-border)))]">
|
|
68
|
+
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-[color-mix(in_srgb,rgb(var(--ec-accent))_12%,transparent)] text-[rgb(var(--ec-accent))] [&_svg]:h-[18px] [&_svg]:w-[18px]">
|
|
69
|
+
{icon}
|
|
70
|
+
</span>
|
|
71
|
+
<h3 className="mt-4 text-sm font-semibold text-[rgb(var(--ec-page-text))]">{title}</h3>
|
|
72
|
+
<p className="mt-1 text-[13px] leading-relaxed text-[rgb(var(--ec-page-text-muted))]">{description}</p>
|
|
73
|
+
<ul className="mt-4 space-y-2">
|
|
74
|
+
{points.map((point) => (
|
|
75
|
+
<li key={point} className="flex items-start gap-2 text-xs leading-snug text-[rgb(var(--ec-page-text-muted))]">
|
|
76
|
+
<Check aria-hidden className="mt-px h-3.5 w-3.5 flex-none text-emerald-500" />
|
|
77
|
+
{point}
|
|
78
|
+
</li>
|
|
79
|
+
))}
|
|
80
|
+
</ul>
|
|
81
|
+
<div className="mt-auto pt-6">{children}</div>
|
|
82
|
+
</section>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Copies some text, saying it did (or that it couldn't) */
|
|
87
|
+
function CopyButton({ text, label, icon, className }: { text: string; label: string; icon: ReactNode; className: string }) {
|
|
88
|
+
const [copied, setCopied] = useState<boolean | null>(null);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (copied === null) return;
|
|
91
|
+
const timer = setTimeout(() => setCopied(null), 1500);
|
|
92
|
+
return () => clearTimeout(timer);
|
|
93
|
+
}, [copied]);
|
|
94
|
+
return (
|
|
95
|
+
<button type="button" onClick={async () => setCopied(await copyText(text))} className={className}>
|
|
96
|
+
{copied ? <Check aria-hidden /> : icon}
|
|
97
|
+
{copied ? 'Copied' : copied === false ? "Couldn't copy" : label}
|
|
98
|
+
</button>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
11
102
|
export default function ShareDialog({
|
|
12
103
|
url,
|
|
13
104
|
title,
|
|
@@ -18,7 +109,10 @@ export default function ShareDialog({
|
|
|
18
109
|
onJumpTo,
|
|
19
110
|
onConnectAgent,
|
|
20
111
|
onClose,
|
|
112
|
+
initialView = 'choose',
|
|
21
113
|
}: {
|
|
114
|
+
/** Straight to live collaboration (who's here, and the invite), e.g. from the menu */
|
|
115
|
+
initialView?: 'choose' | 'invite';
|
|
22
116
|
url: string;
|
|
23
117
|
title?: string;
|
|
24
118
|
/** Canvases are lost when the server restarts */
|
|
@@ -37,6 +131,10 @@ export default function ShareDialog({
|
|
|
37
131
|
const name = title || 'this canvas';
|
|
38
132
|
const invite = `I'm designing ${title ? `"${title}"` : 'an architecture'} in EventCatalog Studio. Join me, we can work on it together live: ${url}`;
|
|
39
133
|
const email = `mailto:?subject=${encodeURIComponent(`Join me on ${name} in EventCatalog Studio`)}&body=${encodeURIComponent(invite)}`;
|
|
134
|
+
// The ways to share first; live collaboration opens who's here and the invite
|
|
135
|
+
const [view, setView] = useState<'choose' | 'invite'>(initialView);
|
|
136
|
+
const choosing = view === 'choose';
|
|
137
|
+
const wide = 'w-full justify-center';
|
|
40
138
|
|
|
41
139
|
useEffect(() => {
|
|
42
140
|
const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && onClose();
|
|
@@ -45,125 +143,182 @@ export default function ShareDialog({
|
|
|
45
143
|
}, [onClose]);
|
|
46
144
|
|
|
47
145
|
return (
|
|
48
|
-
<div className="fixed inset-0 z-
|
|
146
|
+
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/40 p-4" onClick={onClose}>
|
|
49
147
|
<div
|
|
50
148
|
role="dialog"
|
|
51
|
-
aria-
|
|
149
|
+
aria-modal="true"
|
|
150
|
+
aria-labelledby="share-canvas-title"
|
|
52
151
|
onClick={(event) => event.stopPropagation()}
|
|
53
|
-
className=
|
|
152
|
+
className={`w-full ${choosing ? 'max-w-4xl' : 'max-w-lg'} rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))] text-[rgb(var(--ec-page-text))] shadow-2xl`}
|
|
54
153
|
>
|
|
55
|
-
<
|
|
56
|
-
<div className="min-w-0">
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
154
|
+
<div className="flex items-start justify-between gap-3 px-6 pt-5">
|
|
155
|
+
<div className="flex min-w-0 items-start gap-2">
|
|
156
|
+
{!choosing && (
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
aria-label="Back to the ways to share"
|
|
160
|
+
onClick={() => setView('choose')}
|
|
161
|
+
className="-ml-1.5 mt-0.5 rounded-md p-1 text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-page-text))]"
|
|
162
|
+
>
|
|
163
|
+
<ArrowLeft aria-hidden className="h-4 w-4" />
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
<div className="min-w-0">
|
|
167
|
+
<h2 id="share-canvas-title" className="truncate text-base font-semibold">
|
|
168
|
+
{choosing ? 'Share' : 'Live collaboration'}
|
|
169
|
+
</h2>
|
|
170
|
+
<p className="mt-1 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
171
|
+
{choosing
|
|
172
|
+
? `Choose how to work on ${title ? `"${title}"` : 'this canvas'} with others.`
|
|
173
|
+
: "Everyone with the link joins live: you see each other's pointers and changes as they happen."}
|
|
174
|
+
</p>
|
|
175
|
+
</div>
|
|
62
176
|
</div>
|
|
63
177
|
<button
|
|
64
|
-
|
|
178
|
+
type="button"
|
|
65
179
|
aria-label="Close"
|
|
66
|
-
|
|
180
|
+
onClick={onClose}
|
|
181
|
+
className="-mr-1 rounded-md p-1.5 text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-page-text))]"
|
|
67
182
|
>
|
|
68
|
-
<X
|
|
183
|
+
<X aria-hidden className="h-4 w-4" />
|
|
69
184
|
</button>
|
|
70
|
-
</
|
|
185
|
+
</div>
|
|
71
186
|
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
187
|
+
{choosing ? (
|
|
188
|
+
<div className="grid gap-4 px-6 pb-6 pt-5 md:grid-cols-3">
|
|
189
|
+
<Choice
|
|
190
|
+
icon={<Link2 aria-hidden />}
|
|
191
|
+
title="Share a link"
|
|
192
|
+
description="A link to this canvas, in your catalog."
|
|
193
|
+
points={[
|
|
194
|
+
'Teammates open it in EventCatalog',
|
|
195
|
+
'Paste it to your AI agent to build on',
|
|
196
|
+
keptInMemory ? "Kept in this EventCatalog's memory, until it restarts" : 'Kept in your catalog',
|
|
197
|
+
]}
|
|
198
|
+
>
|
|
199
|
+
<CopyButton text={url} label="Copy link" icon={<Link2 aria-hidden />} className={`${secondary} ${wide}`} />
|
|
200
|
+
</Choice>
|
|
201
|
+
|
|
202
|
+
<Choice
|
|
203
|
+
icon={<Users aria-hidden />}
|
|
204
|
+
title="Live collaboration"
|
|
205
|
+
description="Work on this canvas together, in real time."
|
|
206
|
+
points={[
|
|
207
|
+
"See each other's pointers and edits",
|
|
208
|
+
'Comment and talk it through',
|
|
209
|
+
`${people.length} ${people.length === 1 ? 'person' : 'people'} here now`,
|
|
210
|
+
]}
|
|
211
|
+
>
|
|
212
|
+
<button type="button" onClick={() => setView('invite')} className={`${primary} ${wide}`}>
|
|
213
|
+
<UserPlus aria-hidden /> Invite people
|
|
214
|
+
</button>
|
|
215
|
+
</Choice>
|
|
216
|
+
|
|
217
|
+
<Choice
|
|
218
|
+
icon={<Bot aria-hidden />}
|
|
219
|
+
title="Pair with your AI agent"
|
|
220
|
+
description="Sketch ideas with your agent, or have it explain how something works."
|
|
221
|
+
points={['Draws and edits the canvas with you', 'Claude, ChatGPT, Cursor and more']}
|
|
222
|
+
>
|
|
223
|
+
<button
|
|
224
|
+
type="button"
|
|
225
|
+
onClick={() => {
|
|
226
|
+
onClose();
|
|
227
|
+
onConnectAgent();
|
|
228
|
+
}}
|
|
229
|
+
className={`${secondary} ${wide}`}
|
|
92
230
|
>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
231
|
+
<Plug aria-hidden /> Connect agent
|
|
232
|
+
</button>
|
|
233
|
+
</Choice>
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<>
|
|
237
|
+
<div className="space-y-5 px-6 pb-5 pt-4">
|
|
238
|
+
{/* Who's here */}
|
|
239
|
+
<section>
|
|
240
|
+
<div className="flex items-center justify-between">
|
|
241
|
+
<h3 className={sectionLabel}>On this canvas</h3>
|
|
242
|
+
<span className="inline-flex items-center gap-1.5 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
243
|
+
<span className="h-2 w-2 rounded-full bg-emerald-500" /> {people.length}{' '}
|
|
244
|
+
{people.length === 1 ? 'person' : 'people'} here
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
<ul className="mt-2 flex max-h-40 flex-wrap gap-2 overflow-y-auto">
|
|
248
|
+
{you && <YouChip peer={you} onRename={onRename} />}
|
|
249
|
+
{others.map((peer) => (
|
|
250
|
+
<li key={peer.clientId}>
|
|
251
|
+
<button
|
|
252
|
+
type="button"
|
|
253
|
+
title={`Go to what ${peer.name} is looking at${peer.activity ? ` (${peer.activity})` : ''}`}
|
|
254
|
+
onClick={() => {
|
|
255
|
+
onJumpTo(peer);
|
|
256
|
+
onClose();
|
|
257
|
+
}}
|
|
258
|
+
className="inline-flex items-center gap-2 rounded-full border border-[rgb(var(--ec-page-border))] py-1 pl-1 pr-3 text-sm hover:border-[rgb(var(--ec-accent))]"
|
|
259
|
+
>
|
|
260
|
+
<Avatar peer={peer} />
|
|
261
|
+
{peer.name}
|
|
262
|
+
{peer.agent && (
|
|
263
|
+
<span className="rounded px-1 py-px text-[10px] font-medium bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-accent))]">
|
|
264
|
+
AI agent
|
|
265
|
+
</span>
|
|
266
|
+
)}
|
|
267
|
+
</button>
|
|
268
|
+
</li>
|
|
269
|
+
))}
|
|
270
|
+
</ul>
|
|
271
|
+
{others.length === 0 && (
|
|
272
|
+
<p className="mt-2 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
273
|
+
Just you so far. Send the link to bring people in.
|
|
274
|
+
</p>
|
|
275
|
+
)}
|
|
276
|
+
</section>
|
|
104
277
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<div
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
</span>
|
|
119
|
-
)}
|
|
120
|
-
</p>
|
|
121
|
-
{peer.activity && (
|
|
122
|
-
<p className="truncate text-[11px] text-[rgb(var(--ec-page-text-muted))]">{peer.activity}</p>
|
|
123
|
-
)}
|
|
278
|
+
{/* Inviting people */}
|
|
279
|
+
<section>
|
|
280
|
+
<h3 className={sectionLabel}>Invite people</h3>
|
|
281
|
+
<p className="mt-1 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
282
|
+
Anyone with this link joins and edits this canvas with you.
|
|
283
|
+
</p>
|
|
284
|
+
<div className="mt-2 flex w-full items-center gap-2">
|
|
285
|
+
<div
|
|
286
|
+
title={url}
|
|
287
|
+
className="flex h-9 min-w-0 flex-1 items-center gap-2 rounded-lg border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-content-hover))] px-3 text-sm text-[rgb(var(--ec-page-text-muted))]"
|
|
288
|
+
>
|
|
289
|
+
<Link2 aria-hidden className="h-4 w-4 flex-none" />
|
|
290
|
+
<span className="truncate font-mono text-xs">{shortened(url)}</span>
|
|
124
291
|
</div>
|
|
125
|
-
{
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Just you so far. Send the link to bring people in.
|
|
141
|
-
</li>
|
|
142
|
-
)}
|
|
143
|
-
</ul>
|
|
144
|
-
</section>
|
|
292
|
+
<CopyButton text={url} label="Copy invite" icon={<Copy aria-hidden />} className={primary} />
|
|
293
|
+
</div>
|
|
294
|
+
<div className="mt-2 flex flex-wrap gap-2">
|
|
295
|
+
<a href={email} className={`${secondary} h-8 px-3 text-xs`}>
|
|
296
|
+
<Mail aria-hidden /> Email an invite
|
|
297
|
+
</a>
|
|
298
|
+
<CopyButton
|
|
299
|
+
text={invite}
|
|
300
|
+
label="Copy invite message"
|
|
301
|
+
icon={<MessageSquareText aria-hidden />}
|
|
302
|
+
className={`${secondary} h-8 px-3 text-xs`}
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
</section>
|
|
306
|
+
</div>
|
|
145
307
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Claude, ChatGPT or another agent can join and design with you, with its own pointer.
|
|
308
|
+
<div className="flex items-center justify-between gap-3 border-t border-[rgb(var(--ec-page-border))] px-6 py-3">
|
|
309
|
+
<p className="flex items-start gap-1.5 text-[11px] text-[rgb(var(--ec-page-text-muted))]">
|
|
310
|
+
<Globe aria-hidden className="mt-px h-3 w-3 flex-none" />
|
|
311
|
+
<span>
|
|
312
|
+
Anyone with the link can view and edit.
|
|
313
|
+
{keptInMemory && " Canvases are kept in this EventCatalog's memory, so they're lost when it restarts."}
|
|
314
|
+
</span>
|
|
154
315
|
</p>
|
|
316
|
+
<button type="button" onClick={onClose} className={secondary}>
|
|
317
|
+
Done
|
|
318
|
+
</button>
|
|
155
319
|
</div>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
onClose();
|
|
159
|
-
onConnectAgent();
|
|
160
|
-
}}
|
|
161
|
-
className="shrink-0 rounded-lg border px-2.5 py-1.5 text-xs font-medium border-[rgb(var(--ec-page-border))] hover:bg-[rgb(var(--ec-page-border)/0.4)]"
|
|
162
|
-
>
|
|
163
|
-
Connect agent
|
|
164
|
-
</button>
|
|
165
|
-
</section>
|
|
166
|
-
</div>
|
|
320
|
+
</>
|
|
321
|
+
)}
|
|
167
322
|
</div>
|
|
168
323
|
</div>
|
|
169
324
|
);
|
|
@@ -180,17 +335,17 @@ const initials = (name: string) =>
|
|
|
180
335
|
function Avatar({ peer }: { peer: Peer }) {
|
|
181
336
|
return (
|
|
182
337
|
<span
|
|
183
|
-
className="relative flex h-
|
|
338
|
+
className="relative flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
|
184
339
|
style={{ backgroundColor: peer.color }}
|
|
185
340
|
>
|
|
186
|
-
{peer.agent ? <
|
|
341
|
+
{peer.agent ? <AgentMark name={peer.name} size={12} /> : initials(peer.name)}
|
|
187
342
|
<Picture src={peer.picture} />
|
|
188
343
|
</span>
|
|
189
344
|
);
|
|
190
345
|
}
|
|
191
346
|
|
|
192
347
|
/** You, with your name (the name others see you by, which you can change unless you're signed in) */
|
|
193
|
-
function
|
|
348
|
+
function YouChip({ peer, onRename }: { peer: Peer; onRename?: (name: string) => void }) {
|
|
194
349
|
const [editing, setEditing] = useState(false);
|
|
195
350
|
const [draft, setDraft] = useState(peer.name);
|
|
196
351
|
const save = () => {
|
|
@@ -199,7 +354,7 @@ function YouRow({ peer, onRename }: { peer: Peer; onRename?: (name: string) => v
|
|
|
199
354
|
setEditing(false);
|
|
200
355
|
};
|
|
201
356
|
return (
|
|
202
|
-
<li className="flex items-center gap-2
|
|
357
|
+
<li className="inline-flex items-center gap-2 rounded-full border border-[rgb(var(--ec-page-border))] py-1 pl-1 pr-2 text-sm">
|
|
203
358
|
<Avatar peer={peer} />
|
|
204
359
|
{editing ? (
|
|
205
360
|
<input
|
|
@@ -215,46 +370,27 @@ function YouRow({ peer, onRename }: { peer: Peer; onRename?: (name: string) => v
|
|
|
215
370
|
}
|
|
216
371
|
}}
|
|
217
372
|
aria-label="Your name"
|
|
218
|
-
className="
|
|
373
|
+
className="w-36 rounded-md border px-2 py-0.5 text-xs bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
|
|
219
374
|
/>
|
|
220
375
|
) : (
|
|
221
|
-
|
|
222
|
-
{peer.name} <span className="
|
|
223
|
-
|
|
376
|
+
<>
|
|
377
|
+
{peer.name} <span className="text-xs text-[rgb(var(--ec-page-text-muted))]">(you)</span>
|
|
378
|
+
</>
|
|
224
379
|
)}
|
|
225
380
|
{!editing && onRename && (
|
|
226
381
|
<button
|
|
382
|
+
type="button"
|
|
383
|
+
aria-label="Change your name"
|
|
384
|
+
title="Change the name others see you by"
|
|
227
385
|
onClick={() => {
|
|
228
386
|
setDraft(peer.name);
|
|
229
387
|
setEditing(true);
|
|
230
388
|
}}
|
|
231
|
-
className="
|
|
389
|
+
className="rounded-full p-1 text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-page-text))]"
|
|
232
390
|
>
|
|
233
|
-
<Pencil
|
|
234
|
-
Rename
|
|
391
|
+
<Pencil aria-hidden className="h-3 w-3" />
|
|
235
392
|
</button>
|
|
236
393
|
)}
|
|
237
394
|
</li>
|
|
238
395
|
);
|
|
239
396
|
}
|
|
240
|
-
|
|
241
|
-
function CopyButton({ text, label, primary = false }: { text: string; label: string; primary?: boolean }) {
|
|
242
|
-
const [copied, setCopied] = useState(false);
|
|
243
|
-
return (
|
|
244
|
-
<button
|
|
245
|
-
onClick={async () => {
|
|
246
|
-
await navigator.clipboard.writeText(text);
|
|
247
|
-
setCopied(true);
|
|
248
|
-
setTimeout(() => setCopied(false), 1500);
|
|
249
|
-
}}
|
|
250
|
-
className={`flex shrink-0 items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium ${
|
|
251
|
-
primary
|
|
252
|
-
? 'bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))] hover:bg-[rgb(var(--ec-button-bg-hover))]'
|
|
253
|
-
: 'border border-[rgb(var(--ec-page-border))] hover:bg-[rgb(var(--ec-page-border)/0.4)]'
|
|
254
|
-
}`}
|
|
255
|
-
>
|
|
256
|
-
{copied ? <Check size={14} /> : <Copy size={14} />}
|
|
257
|
-
{copied ? 'Copied' : label}
|
|
258
|
-
</button>
|
|
259
|
-
);
|
|
260
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import { ShortcutsList } from '@eventcatalog/visualiser/studio';
|
|
4
|
+
|
|
5
|
+
/** What Studio here doesn't have of the shortcuts Studio on the web lists */
|
|
6
|
+
const NOT_HERE = ['Command palette', 'Find on canvas'];
|
|
7
|
+
|
|
8
|
+
/** Help: the keyboard shortcuts, as Studio on the web lists them */
|
|
9
|
+
export default function ShortcutsDialog({ onClose }: { onClose: () => void }) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && onClose();
|
|
12
|
+
window.addEventListener('keydown', onKeyDown);
|
|
13
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
14
|
+
}, [onClose]);
|
|
15
|
+
return (
|
|
16
|
+
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/40 p-4" onClick={onClose}>
|
|
17
|
+
<div
|
|
18
|
+
role="dialog"
|
|
19
|
+
aria-modal="true"
|
|
20
|
+
aria-labelledby="shortcuts-title"
|
|
21
|
+
onClick={(event) => event.stopPropagation()}
|
|
22
|
+
className="flex max-h-[calc(100vh-2rem)] w-full max-w-3xl flex-col rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))] text-[rgb(var(--ec-page-text))] shadow-2xl"
|
|
23
|
+
>
|
|
24
|
+
<div className="flex flex-none items-center justify-between border-b border-[rgb(var(--ec-page-border))] px-5 py-4">
|
|
25
|
+
<h2 id="shortcuts-title" className="text-base font-semibold">
|
|
26
|
+
Keyboard shortcuts
|
|
27
|
+
</h2>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
aria-label="Close"
|
|
31
|
+
onClick={onClose}
|
|
32
|
+
className="rounded-md p-1.5 text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-page-text))]"
|
|
33
|
+
>
|
|
34
|
+
<X aria-hidden className="h-4 w-4" />
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
<ShortcutsList omit={NOT_HERE} />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|