@eventcatalog/core 5.0.0-beta.12 → 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/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-FY6MSP6J.js → chunk-BO2HHVZ3.js} +1 -1
- package/dist/{chunk-CX2VB7AI.js → chunk-JPT3QC5V.js} +1 -1
- package/dist/{chunk-BWC4YKW3.js → chunk-PLTLW45Q.js} +5 -5
- package/dist/{chunk-TSY5CC27.js → chunk-T75DOVFP.js} +1 -1
- package/dist/{chunk-QB7X34OD.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/MDX/NodeGraph/AstroNodeGraph.tsx +11 -3
- 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
- package/dist/{chunk-KHACCTW2.js → chunk-ZEY2XUJN.js} +4 -4
|
@@ -1,34 +1,109 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { Users } from 'lucide-react';
|
|
3
|
+
import { MAX_NAME_LENGTH, PERSON_COLORS } from '../identity';
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
const initials = (name: string) =>
|
|
6
|
+
name
|
|
7
|
+
.trim()
|
|
8
|
+
.split(/\s+/)
|
|
9
|
+
.map((word) => word[0])
|
|
10
|
+
.join('')
|
|
11
|
+
.slice(0, 2)
|
|
12
|
+
.toUpperCase();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Joining a canvas the first time (without signing in), as Studio on the web joins a live session: the name and colour
|
|
16
|
+
* you'll be seen by (your pointer, your avatar, your comments), picked before you start, with how you'll look to the
|
|
17
|
+
* others. Remembered in this browser for next time.
|
|
18
|
+
*/
|
|
19
|
+
export default function JoinForm({
|
|
20
|
+
color,
|
|
21
|
+
onJoin,
|
|
22
|
+
}: {
|
|
23
|
+
/** The colour picked to start with (the one kept in this browser, if there is one) */
|
|
24
|
+
color?: string | null;
|
|
25
|
+
onJoin: (me: { name: string; color: string }) => void;
|
|
26
|
+
}) {
|
|
4
27
|
const [draft, setDraft] = useState('');
|
|
28
|
+
const [picked, setPicked] = useState(() => color ?? PERSON_COLORS[Math.floor(Math.random() * PERSON_COLORS.length)]);
|
|
29
|
+
const name = draft.trim();
|
|
5
30
|
|
|
6
31
|
return (
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</div>
|
|
18
|
-
<input
|
|
19
|
-
autoFocus
|
|
20
|
-
value={draft}
|
|
21
|
-
onChange={(e) => setDraft(e.target.value)}
|
|
22
|
-
placeholder="Your name"
|
|
23
|
-
className="w-full rounded-md px-3 py-2 border bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
|
|
24
|
-
/>
|
|
25
|
-
<button
|
|
26
|
-
type="submit"
|
|
27
|
-
disabled={!draft.trim()}
|
|
28
|
-
className="w-full rounded-md px-3 py-2 text-sm disabled:opacity-50 bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]"
|
|
32
|
+
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/40 p-4">
|
|
33
|
+
<form
|
|
34
|
+
role="dialog"
|
|
35
|
+
aria-modal="true"
|
|
36
|
+
aria-labelledby="join-canvas-title"
|
|
37
|
+
onSubmit={(event) => {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
if (name) onJoin({ name, color: picked });
|
|
40
|
+
}}
|
|
41
|
+
className="w-full max-w-md rounded-xl border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))] p-6 text-center text-[rgb(var(--ec-page-text))] shadow-2xl"
|
|
29
42
|
>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
<span className="mx-auto flex h-11 w-11 items-center justify-center rounded-full bg-[color-mix(in_srgb,rgb(var(--ec-accent))_12%,transparent)] text-[rgb(var(--ec-accent))]">
|
|
44
|
+
<Users aria-hidden className="h-5 w-5" />
|
|
45
|
+
</span>
|
|
46
|
+
<h1 id="join-canvas-title" className="mt-3 text-base font-semibold">
|
|
47
|
+
Join this canvas
|
|
48
|
+
</h1>
|
|
49
|
+
<p className="mt-1.5 text-sm leading-relaxed text-[rgb(var(--ec-page-text-muted))]">
|
|
50
|
+
Everyone here sees your pointer and edits with you, live.
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<label className="mt-5 block text-left text-xs font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
54
|
+
Your name
|
|
55
|
+
<input
|
|
56
|
+
autoFocus
|
|
57
|
+
value={draft}
|
|
58
|
+
maxLength={MAX_NAME_LENGTH}
|
|
59
|
+
placeholder="What should others call you?"
|
|
60
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
61
|
+
className="mt-1.5 block h-9 w-full rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-page-bg))] px-3 text-sm text-[rgb(var(--ec-page-text))] outline-none placeholder:text-[rgb(var(--ec-page-text-muted))] focus:border-[rgb(var(--ec-accent))]"
|
|
62
|
+
/>
|
|
63
|
+
</label>
|
|
64
|
+
<div className="mt-4 text-left text-xs font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
65
|
+
Your colour
|
|
66
|
+
<div role="radiogroup" aria-label="Your colour" className="mt-2 flex gap-2">
|
|
67
|
+
{PERSON_COLORS.map((option) => (
|
|
68
|
+
<button
|
|
69
|
+
key={option}
|
|
70
|
+
type="button"
|
|
71
|
+
role="radio"
|
|
72
|
+
aria-checked={option === picked}
|
|
73
|
+
aria-label={option}
|
|
74
|
+
onClick={() => setPicked(option)}
|
|
75
|
+
className={`h-6 w-6 rounded-full transition-transform hover:scale-110 ${
|
|
76
|
+
option === picked
|
|
77
|
+
? 'ring-2 ring-[rgb(var(--ec-page-text))] ring-offset-2 ring-offset-[rgb(var(--ec-card-bg))]'
|
|
78
|
+
: ''
|
|
79
|
+
}`}
|
|
80
|
+
style={{ background: option }}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
{/* How you'll look to the others */}
|
|
87
|
+
{name && (
|
|
88
|
+
<div className="mt-5 flex items-center justify-center gap-2 text-sm">
|
|
89
|
+
<span
|
|
90
|
+
className="flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold text-white"
|
|
91
|
+
style={{ background: picked }}
|
|
92
|
+
>
|
|
93
|
+
{initials(name)}
|
|
94
|
+
</span>
|
|
95
|
+
<span className="font-medium">{name}</span>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<button
|
|
100
|
+
type="submit"
|
|
101
|
+
disabled={!name}
|
|
102
|
+
className="mt-5 inline-flex h-10 w-full items-center justify-center rounded-lg bg-[rgb(var(--ec-page-text))] text-sm font-medium text-[rgb(var(--ec-page-bg))] hover:bg-[rgb(var(--ec-page-text)/0.85)] disabled:opacity-40"
|
|
103
|
+
>
|
|
104
|
+
Join canvas
|
|
105
|
+
</button>
|
|
106
|
+
</form>
|
|
107
|
+
</div>
|
|
33
108
|
);
|
|
34
109
|
}
|
|
@@ -1,75 +1,32 @@
|
|
|
1
|
-
import { memo,
|
|
2
|
-
import { Check,
|
|
1
|
+
import { memo, useMemo, useState } from 'react';
|
|
2
|
+
import { Check, Search } from 'lucide-react';
|
|
3
3
|
import type { CatalogResource } from '../catalog-resources';
|
|
4
4
|
import { catalogNodeData } from '../catalog';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
import {
|
|
6
|
+
isShownOnLevel,
|
|
7
|
+
type Level,
|
|
8
|
+
type StartDrag,
|
|
9
|
+
CATALOG_GROUPS,
|
|
10
|
+
STATUS,
|
|
11
|
+
Palette,
|
|
12
|
+
PaletteItem,
|
|
13
|
+
PaletteSection,
|
|
14
|
+
type PaletteTab,
|
|
15
|
+
} from '@eventcatalog/visualiser/studio';
|
|
16
|
+
|
|
17
|
+
/** What's dragged from the left panel onto the canvas: a component's key, or a catalog resource's key */
|
|
18
|
+
export { COMPONENT_DRAG_TYPE } from '@eventcatalog/visualiser/studio';
|
|
15
19
|
export const CATALOG_DRAG_TYPE = 'application/eventcatalog-studio-resource';
|
|
16
20
|
|
|
17
21
|
/** A catalog group shows this many resources until it's expanded, so big catalogs stay quick */
|
|
18
22
|
const GROUP_PREVIEW_SIZE = 50;
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
'group flex cursor-grab items-center justify-between gap-2 rounded-md border px-2.5 py-2 bg-[rgb(var(--ec-page-bg))] border-[rgb(var(--ec-page-border))] hover:border-[rgb(var(--ec-accent))]';
|
|
22
|
-
const addButtonClass =
|
|
23
|
-
'shrink-0 rounded p-0.5 opacity-0 group-hover:opacity-100 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-accent))]';
|
|
24
|
-
// The sections that are collapsed, by id (e.g. "components:messages"), remembered in this browser
|
|
25
|
-
const COLLAPSED_KEY = 'eventcatalog-studio-collapsed';
|
|
26
|
-
type Collapsed = { collapsed: ReadonlySet<string>; toggle: (id: string) => void };
|
|
27
|
-
|
|
28
|
-
const readCollapsed = (): Set<string> => {
|
|
29
|
-
try {
|
|
30
|
-
return new Set(JSON.parse(localStorage.getItem(COLLAPSED_KEY) ?? '[]') as string[]);
|
|
31
|
-
} catch {
|
|
32
|
-
return new Set();
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
function useCollapsed(): Collapsed {
|
|
37
|
-
const [collapsed, setCollapsed] = useState(readCollapsed);
|
|
38
|
-
const toggle = useCallback((id: string) => {
|
|
39
|
-
setCollapsed((current) => {
|
|
40
|
-
const next = new Set(current);
|
|
41
|
-
if (next.has(id)) next.delete(id);
|
|
42
|
-
else next.add(id);
|
|
43
|
-
try {
|
|
44
|
-
localStorage.setItem(COLLAPSED_KEY, JSON.stringify([...next]));
|
|
45
|
-
} catch {
|
|
46
|
-
// Not remembered (e.g. storage is blocked): it still works for now
|
|
47
|
-
}
|
|
48
|
-
return next;
|
|
49
|
-
});
|
|
50
|
-
}, []);
|
|
51
|
-
return useMemo(() => ({ collapsed, toggle }), [collapsed, toggle]);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** A section's heading, which opens and closes it */
|
|
55
|
-
function SectionHeading({ open, onToggle, children }: { open: boolean; onToggle: () => void; children: ReactNode }) {
|
|
56
|
-
return (
|
|
57
|
-
<h3>
|
|
58
|
-
<button
|
|
59
|
-
onClick={onToggle}
|
|
60
|
-
aria-expanded={open}
|
|
61
|
-
className="flex w-full cursor-pointer items-center gap-1 rounded-md px-1.5 py-1 text-left text-[11px] font-semibold uppercase tracking-wide text-[rgb(var(--ec-page-text-muted))] transition-colors hover:bg-[rgb(var(--ec-page-border)/0.6)] hover:text-[rgb(var(--ec-page-text))]"
|
|
62
|
-
>
|
|
63
|
-
<ChevronRight size={12} className={`shrink-0 transition-transform ${open ? 'rotate-90' : ''}`} />
|
|
64
|
-
{children}
|
|
65
|
-
</button>
|
|
66
|
-
</h3>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
type Tab = 'components' | 'catalog' | 'comments';
|
|
24
|
+
export type LeftPanelTab = 'components' | 'catalog';
|
|
71
25
|
|
|
72
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Studio's palette (as Studio on the web's): components to drag onto the canvas, and here also the catalog's
|
|
28
|
+
* resources, as a tab in the same card (the comments are listed from the bottom left controls)
|
|
29
|
+
*/
|
|
73
30
|
export default memo(function LeftPanel({
|
|
74
31
|
level,
|
|
75
32
|
onShowAll,
|
|
@@ -77,10 +34,9 @@ export default memo(function LeftPanel({
|
|
|
77
34
|
keysOnCanvas,
|
|
78
35
|
onAddComponent,
|
|
79
36
|
onAddResource,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
onOpenThread,
|
|
37
|
+
tab,
|
|
38
|
+
onTabChange,
|
|
39
|
+
startCollapsed,
|
|
84
40
|
}: {
|
|
85
41
|
/** The level shown: only what it shows is listed, so what's added stays on it */
|
|
86
42
|
level: Level;
|
|
@@ -91,71 +47,48 @@ export default memo(function LeftPanel({
|
|
|
91
47
|
keysOnCanvas: Set<string>;
|
|
92
48
|
onAddComponent: (type: string) => void;
|
|
93
49
|
onAddResource: (key: string) => void;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
50
|
+
/** The tab shown, when the canvas chooses it */
|
|
51
|
+
tab?: LeftPanelTab;
|
|
52
|
+
onTabChange?: (tab: LeftPanelTab) => void;
|
|
53
|
+
/** Starts folded away (e.g. in a chat, where there's little room) */
|
|
54
|
+
startCollapsed?: boolean;
|
|
98
55
|
}) {
|
|
99
|
-
const [tab, setTab] = useState<Tab>('components');
|
|
100
|
-
// What's being dragged onto the canvas, drawn as the node it becomes (see DragGhost)
|
|
101
|
-
const { startDrag, ghost } = useDragGhost();
|
|
102
|
-
const sections = useCollapsed();
|
|
103
|
-
const openThreads = threads.filter((thread) => !thread.resolved).length;
|
|
104
56
|
const shownResources = useMemo(
|
|
105
57
|
() => (level === 3 ? resources : resources.filter((resource) => isShownOnLevel(resource.node.type, level))),
|
|
106
58
|
[resources, level]
|
|
107
59
|
);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
60
|
+
const tabs = useMemo(
|
|
61
|
+
(): PaletteTab[] => [
|
|
62
|
+
{
|
|
63
|
+
id: 'catalog',
|
|
64
|
+
label: 'Catalog Resources',
|
|
65
|
+
render: (startDrag) => (
|
|
66
|
+
<>
|
|
67
|
+
{level !== 3 && <LevelHint level={level} onShowAll={onShowAll} />}
|
|
68
|
+
<CatalogList resources={shownResources} keysOnCanvas={keysOnCanvas} onAdd={onAddResource} startDrag={startDrag} />
|
|
69
|
+
</>
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
[level, onShowAll, shownResources, keysOnCanvas, onAddResource]
|
|
74
|
+
);
|
|
112
75
|
|
|
113
76
|
return (
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
<button className={tabClass(tab === 'comments')} onClick={() => setTab('comments')}>
|
|
123
|
-
Comments
|
|
124
|
-
{openThreads > 0 && (
|
|
125
|
-
<span className="rounded-full px-1.5 text-[10px] leading-4 bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]">
|
|
126
|
-
{openThreads}
|
|
127
|
-
</span>
|
|
128
|
-
)}
|
|
129
|
-
</button>
|
|
130
|
-
</div>
|
|
131
|
-
{tab !== 'comments' && level !== 3 && <LevelHint level={level} onShowAll={onShowAll} />}
|
|
132
|
-
{tab === 'components' && <ComponentList level={level} onAdd={onAddComponent} startDrag={startDrag} sections={sections} />}
|
|
133
|
-
{tab === 'catalog' && (
|
|
134
|
-
<CatalogList
|
|
135
|
-
resources={shownResources}
|
|
136
|
-
keysOnCanvas={keysOnCanvas}
|
|
137
|
-
onAdd={onAddResource}
|
|
138
|
-
startDrag={startDrag}
|
|
139
|
-
sections={sections}
|
|
140
|
-
/>
|
|
141
|
-
)}
|
|
142
|
-
{tab === 'comments' && (
|
|
143
|
-
<CommentList threads={threads} showResolved={showResolved} onToggleResolved={onToggleResolved} onOpen={onOpenThread} />
|
|
144
|
-
)}
|
|
145
|
-
{ghost}
|
|
146
|
-
</aside>
|
|
77
|
+
<Palette
|
|
78
|
+
level={level}
|
|
79
|
+
onAddComponent={onAddComponent}
|
|
80
|
+
tabs={tabs}
|
|
81
|
+
tab={tab}
|
|
82
|
+
onTabChange={onTabChange as ((tab: string) => void) | undefined}
|
|
83
|
+
startCollapsed={startCollapsed}
|
|
84
|
+
/>
|
|
147
85
|
);
|
|
148
86
|
});
|
|
149
87
|
|
|
150
|
-
const listedDefinitions = NODE_CATEGORIES.map((category) => ({
|
|
151
|
-
...category,
|
|
152
|
-
definitions: nodeDefinitions.filter((definition) => definition.category === category.id && !definition.unlisted),
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
88
|
/** On L1 and L2: only what the level shows is listed (added there, it stays there), the rest is on L3 */
|
|
156
89
|
function LevelHint({ level, onShowAll }: { level: Level; onShowAll: () => void }) {
|
|
157
90
|
return (
|
|
158
|
-
<p className="
|
|
91
|
+
<p className="text-[11px] leading-snug text-[rgb(var(--ec-page-text-muted))]">
|
|
159
92
|
Showing what L{level} shows.{' '}
|
|
160
93
|
<button onClick={onShowAll} className="font-medium text-[rgb(var(--ec-accent))] hover:underline">
|
|
161
94
|
Go to L3 for everything
|
|
@@ -164,77 +97,19 @@ function LevelHint({ level, onShowAll }: { level: Level; onShowAll: () => void }
|
|
|
164
97
|
);
|
|
165
98
|
}
|
|
166
99
|
|
|
167
|
-
|
|
168
|
-
const componentPreviews = new Map(
|
|
169
|
-
nodeDefinitions.map((definition) => [definition.type, { type: definition.type, data: definition.createData() }])
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const ComponentList = memo(function ComponentList({
|
|
173
|
-
level,
|
|
174
|
-
onAdd,
|
|
175
|
-
startDrag,
|
|
176
|
-
sections: { collapsed, toggle },
|
|
177
|
-
}: {
|
|
178
|
-
level: Level;
|
|
179
|
-
onAdd: (type: string) => void;
|
|
180
|
-
startDrag: StartDrag;
|
|
181
|
-
sections: Collapsed;
|
|
182
|
-
}) {
|
|
183
|
-
return (
|
|
184
|
-
<div className="min-h-0 flex-1 space-y-3 overflow-y-auto px-3 pb-3">
|
|
185
|
-
{listedDefinitions.map((category) => {
|
|
186
|
-
const definitions = category.definitions.filter((definition) => isShownOnLevel(definition.type, level));
|
|
187
|
-
if (definitions.length === 0) return null;
|
|
188
|
-
const id = `components:${category.id}`;
|
|
189
|
-
const open = !collapsed.has(id);
|
|
190
|
-
return (
|
|
191
|
-
<section key={category.id} className="space-y-1.5">
|
|
192
|
-
<SectionHeading open={open} onToggle={() => toggle(id)}>
|
|
193
|
-
{category.label}
|
|
194
|
-
</SectionHeading>
|
|
195
|
-
{open &&
|
|
196
|
-
definitions.map(({ type, label }) => {
|
|
197
|
-
const { icon: Icon, className } = NODE_ICONS[type];
|
|
198
|
-
return (
|
|
199
|
-
<div
|
|
200
|
-
key={type}
|
|
201
|
-
draggable
|
|
202
|
-
onDragStart={startDrag(COMPONENT_DRAG_TYPE, type, componentPreviews.get(type)!)}
|
|
203
|
-
className={itemClass}
|
|
204
|
-
>
|
|
205
|
-
<span className="flex items-center gap-2 text-xs font-medium">
|
|
206
|
-
<Icon size={16} className={className} />
|
|
207
|
-
{label}
|
|
208
|
-
</span>
|
|
209
|
-
<button onClick={() => onAdd(type)} title={`Add ${label}`} className={addButtonClass}>
|
|
210
|
-
<Plus size={14} />
|
|
211
|
-
</button>
|
|
212
|
-
</div>
|
|
213
|
-
);
|
|
214
|
-
})}
|
|
215
|
-
</section>
|
|
216
|
-
);
|
|
217
|
-
})}
|
|
218
|
-
</div>
|
|
219
|
-
);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const CatalogList = memo(function CatalogList({
|
|
100
|
+
function CatalogList({
|
|
223
101
|
resources,
|
|
224
102
|
keysOnCanvas,
|
|
225
103
|
onAdd,
|
|
226
104
|
startDrag,
|
|
227
|
-
sections: { collapsed, toggle },
|
|
228
105
|
}: {
|
|
229
106
|
resources: CatalogResource[];
|
|
230
107
|
keysOnCanvas: Set<string>;
|
|
231
108
|
onAdd: (key: string) => void;
|
|
232
109
|
startDrag: StartDrag;
|
|
233
|
-
sections: Collapsed;
|
|
234
110
|
}) {
|
|
235
111
|
const [search, setSearch] = useState('');
|
|
236
112
|
const [expanded, setExpanded] = useState<Set<string>>(() => new Set());
|
|
237
|
-
const searching = search.trim() !== '';
|
|
238
113
|
const groups = useMemo(() => {
|
|
239
114
|
const term = search.trim().toLowerCase();
|
|
240
115
|
const matches = term
|
|
@@ -247,60 +122,52 @@ const CatalogList = memo(function CatalogList({
|
|
|
247
122
|
}, [resources, search]);
|
|
248
123
|
|
|
249
124
|
return (
|
|
250
|
-
|
|
251
|
-
<div className="relative
|
|
125
|
+
<>
|
|
126
|
+
<div className="relative">
|
|
252
127
|
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]" />
|
|
253
128
|
<input
|
|
254
129
|
value={search}
|
|
255
130
|
onChange={(e) => setSearch(e.target.value)}
|
|
256
131
|
placeholder="Search the catalog"
|
|
257
|
-
className="w-full rounded-
|
|
132
|
+
className="w-full rounded-lg border py-1.5 pl-8 pr-2 text-xs bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
|
|
258
133
|
/>
|
|
259
134
|
</div>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
Show all {items.length} {label.toLowerCase()}
|
|
291
|
-
</button>
|
|
292
|
-
)}
|
|
293
|
-
</section>
|
|
294
|
-
);
|
|
295
|
-
})}
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
135
|
+
{groups.length === 0 && (
|
|
136
|
+
<p className="py-6 text-center text-xs text-[rgb(var(--ec-page-text-muted))]">No resources found.</p>
|
|
137
|
+
)}
|
|
138
|
+
{groups.map(({ collection, label, icon, className, items }) => {
|
|
139
|
+
const shown = expanded.has(collection) ? items : items.slice(0, GROUP_PREVIEW_SIZE);
|
|
140
|
+
return (
|
|
141
|
+
<PaletteSection key={collection} title={`${label} (${items.length})`}>
|
|
142
|
+
{shown.map((resource) => (
|
|
143
|
+
<CatalogItem
|
|
144
|
+
key={resource.key}
|
|
145
|
+
resource={resource}
|
|
146
|
+
icon={icon}
|
|
147
|
+
iconClassName={className}
|
|
148
|
+
onCanvas={keysOnCanvas.has(resource.key)}
|
|
149
|
+
onAdd={onAdd}
|
|
150
|
+
startDrag={startDrag}
|
|
151
|
+
/>
|
|
152
|
+
))}
|
|
153
|
+
{shown.length < items.length && (
|
|
154
|
+
<button
|
|
155
|
+
onClick={() => setExpanded((current) => new Set(current).add(collection))}
|
|
156
|
+
className="w-full rounded-md px-2 py-1.5 text-xs font-medium text-[rgb(var(--ec-accent))] hover:bg-[rgb(var(--ec-accent-subtle))]"
|
|
157
|
+
>
|
|
158
|
+
Show all {items.length} {label.toLowerCase()}
|
|
159
|
+
</button>
|
|
160
|
+
)}
|
|
161
|
+
</PaletteSection>
|
|
162
|
+
);
|
|
163
|
+
})}
|
|
164
|
+
</>
|
|
298
165
|
);
|
|
299
|
-
}
|
|
166
|
+
}
|
|
300
167
|
|
|
301
168
|
const CatalogItem = memo(function CatalogItem({
|
|
302
169
|
resource,
|
|
303
|
-
icon
|
|
170
|
+
icon,
|
|
304
171
|
iconClassName,
|
|
305
172
|
onCanvas,
|
|
306
173
|
onAdd,
|
|
@@ -316,27 +183,21 @@ const CatalogItem = memo(function CatalogItem({
|
|
|
316
183
|
// Drawn like the card it becomes (domains and systems can become containers too, but start as cards)
|
|
317
184
|
const preview = useMemo(() => ({ type: resource.node.type, data: catalogNodeData(resource) }), [resource]);
|
|
318
185
|
return (
|
|
319
|
-
<
|
|
320
|
-
|
|
186
|
+
<PaletteItem
|
|
187
|
+
icon={icon}
|
|
188
|
+
iconClassName={iconClassName}
|
|
189
|
+
label={resource.name}
|
|
190
|
+
description={resource.summary ? `v${resource.version} · ${resource.summary}` : `v${resource.version}`}
|
|
321
191
|
onDragStart={startDrag(CATALOG_DRAG_TYPE, resource.key, preview)}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<span className="truncate">{resource.name}</span>
|
|
328
|
-
<span className="shrink-0 text-[10px] text-[rgb(var(--ec-page-text-muted))]">v{resource.version}</span>
|
|
329
|
-
</span>
|
|
330
|
-
<span className="flex shrink-0 items-center gap-1">
|
|
331
|
-
{onCanvas && (
|
|
332
|
-
<span title="On the canvas">
|
|
192
|
+
onClick={() => onAdd(resource.key)}
|
|
193
|
+
title={`${resource.name}: drag onto the canvas, or click to add`}
|
|
194
|
+
trailing={
|
|
195
|
+
onCanvas ? (
|
|
196
|
+
<span title="On the canvas" className="flex-none">
|
|
333
197
|
<Check size={14} className={STATUS.success.text} />
|
|
334
198
|
</span>
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</button>
|
|
339
|
-
</span>
|
|
340
|
-
</div>
|
|
199
|
+
) : undefined
|
|
200
|
+
}
|
|
201
|
+
/>
|
|
341
202
|
);
|
|
342
203
|
});
|