@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
|
@@ -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,212 +1,115 @@
|
|
|
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
|
-
|
|
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';
|
|
11
16
|
|
|
12
|
-
/** What's dragged from the left panel onto the canvas: a component's
|
|
13
|
-
export
|
|
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';
|
|
14
19
|
export const CATALOG_DRAG_TYPE = 'application/eventcatalog-studio-resource';
|
|
15
20
|
|
|
16
21
|
/** A catalog group shows this many resources until it's expanded, so big catalogs stay quick */
|
|
17
22
|
const GROUP_PREVIEW_SIZE = 50;
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
'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))]';
|
|
21
|
-
const addButtonClass =
|
|
22
|
-
'shrink-0 rounded p-0.5 opacity-0 group-hover:opacity-100 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-accent))]';
|
|
23
|
-
// The sections that are collapsed, by id (e.g. "components:messages"), remembered in this browser
|
|
24
|
-
const COLLAPSED_KEY = 'eventcatalog-studio-collapsed';
|
|
25
|
-
type Collapsed = { collapsed: ReadonlySet<string>; toggle: (id: string) => void };
|
|
24
|
+
export type LeftPanelTab = 'components' | 'catalog';
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return new Set();
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function useCollapsed(): Collapsed {
|
|
36
|
-
const [collapsed, setCollapsed] = useState(readCollapsed);
|
|
37
|
-
const toggle = useCallback((id: string) => {
|
|
38
|
-
setCollapsed((current) => {
|
|
39
|
-
const next = new Set(current);
|
|
40
|
-
if (next.has(id)) next.delete(id);
|
|
41
|
-
else next.add(id);
|
|
42
|
-
try {
|
|
43
|
-
localStorage.setItem(COLLAPSED_KEY, JSON.stringify([...next]));
|
|
44
|
-
} catch {
|
|
45
|
-
// Not remembered (e.g. storage is blocked): it still works for now
|
|
46
|
-
}
|
|
47
|
-
return next;
|
|
48
|
-
});
|
|
49
|
-
}, []);
|
|
50
|
-
return useMemo(() => ({ collapsed, toggle }), [collapsed, toggle]);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** A section's heading, which opens and closes it */
|
|
54
|
-
function SectionHeading({ open, onToggle, children }: { open: boolean; onToggle: () => void; children: ReactNode }) {
|
|
55
|
-
return (
|
|
56
|
-
<h3>
|
|
57
|
-
<button
|
|
58
|
-
onClick={onToggle}
|
|
59
|
-
aria-expanded={open}
|
|
60
|
-
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))]"
|
|
61
|
-
>
|
|
62
|
-
<ChevronRight size={12} className={`shrink-0 transition-transform ${open ? 'rotate-90' : ''}`} />
|
|
63
|
-
{children}
|
|
64
|
-
</button>
|
|
65
|
-
</h3>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
type Tab = 'components' | 'catalog' | 'comments';
|
|
70
|
-
|
|
71
|
-
/** Components and catalog resources to drag onto the canvas, and the comments on it */
|
|
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
|
+
*/
|
|
72
30
|
export default memo(function LeftPanel({
|
|
31
|
+
level,
|
|
32
|
+
onShowAll,
|
|
73
33
|
resources,
|
|
74
34
|
keysOnCanvas,
|
|
75
35
|
onAddComponent,
|
|
76
36
|
onAddResource,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
onOpenThread,
|
|
37
|
+
tab,
|
|
38
|
+
onTabChange,
|
|
39
|
+
startCollapsed,
|
|
81
40
|
}: {
|
|
41
|
+
/** The level shown: only what it shows is listed, so what's added stays on it */
|
|
42
|
+
level: Level;
|
|
43
|
+
/** Goes to L3, which shows everything */
|
|
44
|
+
onShowAll: () => void;
|
|
82
45
|
resources: CatalogResource[];
|
|
83
46
|
/** The catalog resources on the canvas */
|
|
84
47
|
keysOnCanvas: Set<string>;
|
|
85
48
|
onAddComponent: (type: string) => void;
|
|
86
49
|
onAddResource: (key: string) => void;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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;
|
|
91
55
|
}) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
56
|
+
const shownResources = useMemo(
|
|
57
|
+
() => (level === 3 ? resources : resources.filter((resource) => isShownOnLevel(resource.node.type, level))),
|
|
58
|
+
[resources, level]
|
|
59
|
+
);
|
|
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
|
+
);
|
|
101
75
|
|
|
102
76
|
return (
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
<button className={tabClass(tab === 'comments')} onClick={() => setTab('comments')}>
|
|
112
|
-
Comments
|
|
113
|
-
{openThreads > 0 && (
|
|
114
|
-
<span className="rounded-full px-1.5 text-[10px] leading-4 bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]">
|
|
115
|
-
{openThreads}
|
|
116
|
-
</span>
|
|
117
|
-
)}
|
|
118
|
-
</button>
|
|
119
|
-
</div>
|
|
120
|
-
{tab === 'components' && <ComponentList onAdd={onAddComponent} startDrag={startDrag} sections={sections} />}
|
|
121
|
-
{tab === 'catalog' && (
|
|
122
|
-
<CatalogList
|
|
123
|
-
resources={resources}
|
|
124
|
-
keysOnCanvas={keysOnCanvas}
|
|
125
|
-
onAdd={onAddResource}
|
|
126
|
-
startDrag={startDrag}
|
|
127
|
-
sections={sections}
|
|
128
|
-
/>
|
|
129
|
-
)}
|
|
130
|
-
{tab === 'comments' && (
|
|
131
|
-
<CommentList threads={threads} showResolved={showResolved} onToggleResolved={onToggleResolved} onOpen={onOpenThread} />
|
|
132
|
-
)}
|
|
133
|
-
{ghost}
|
|
134
|
-
</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
|
+
/>
|
|
135
85
|
);
|
|
136
86
|
});
|
|
137
87
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
definitions: nodeDefinitions.filter((definition) => definition.category === category.id && !definition.unlisted),
|
|
141
|
-
}));
|
|
142
|
-
|
|
143
|
-
// Each component's data, made once, for the node shown while it's dragged
|
|
144
|
-
const componentPreviews = new Map(
|
|
145
|
-
nodeDefinitions.map((definition) => [definition.type, { type: definition.type, data: definition.createData() }])
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const ComponentList = memo(function ComponentList({
|
|
149
|
-
onAdd,
|
|
150
|
-
startDrag,
|
|
151
|
-
sections: { collapsed, toggle },
|
|
152
|
-
}: {
|
|
153
|
-
onAdd: (type: string) => void;
|
|
154
|
-
startDrag: StartDrag;
|
|
155
|
-
sections: Collapsed;
|
|
156
|
-
}) {
|
|
88
|
+
/** On L1 and L2: only what the level shows is listed (added there, it stays there), the rest is on L3 */
|
|
89
|
+
function LevelHint({ level, onShowAll }: { level: Level; onShowAll: () => void }) {
|
|
157
90
|
return (
|
|
158
|
-
<
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<SectionHeading open={open} onToggle={() => toggle(id)}>
|
|
165
|
-
{category.label}
|
|
166
|
-
</SectionHeading>
|
|
167
|
-
{open &&
|
|
168
|
-
category.definitions.map(({ type, label }) => {
|
|
169
|
-
const { icon: Icon, className } = NODE_ICONS[type];
|
|
170
|
-
return (
|
|
171
|
-
<div
|
|
172
|
-
key={type}
|
|
173
|
-
draggable
|
|
174
|
-
onDragStart={startDrag(COMPONENT_DRAG_TYPE, type, componentPreviews.get(type)!)}
|
|
175
|
-
className={itemClass}
|
|
176
|
-
>
|
|
177
|
-
<span className="flex items-center gap-2 text-xs font-medium">
|
|
178
|
-
<Icon size={16} className={className} />
|
|
179
|
-
{label}
|
|
180
|
-
</span>
|
|
181
|
-
<button onClick={() => onAdd(type)} title={`Add ${label}`} className={addButtonClass}>
|
|
182
|
-
<Plus size={14} />
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
185
|
-
);
|
|
186
|
-
})}
|
|
187
|
-
</section>
|
|
188
|
-
);
|
|
189
|
-
})}
|
|
190
|
-
</div>
|
|
91
|
+
<p className="text-[11px] leading-snug text-[rgb(var(--ec-page-text-muted))]">
|
|
92
|
+
Showing what L{level} shows.{' '}
|
|
93
|
+
<button onClick={onShowAll} className="font-medium text-[rgb(var(--ec-accent))] hover:underline">
|
|
94
|
+
Go to L3 for everything
|
|
95
|
+
</button>
|
|
96
|
+
</p>
|
|
191
97
|
);
|
|
192
|
-
}
|
|
98
|
+
}
|
|
193
99
|
|
|
194
|
-
|
|
100
|
+
function CatalogList({
|
|
195
101
|
resources,
|
|
196
102
|
keysOnCanvas,
|
|
197
103
|
onAdd,
|
|
198
104
|
startDrag,
|
|
199
|
-
sections: { collapsed, toggle },
|
|
200
105
|
}: {
|
|
201
106
|
resources: CatalogResource[];
|
|
202
107
|
keysOnCanvas: Set<string>;
|
|
203
108
|
onAdd: (key: string) => void;
|
|
204
109
|
startDrag: StartDrag;
|
|
205
|
-
sections: Collapsed;
|
|
206
110
|
}) {
|
|
207
111
|
const [search, setSearch] = useState('');
|
|
208
112
|
const [expanded, setExpanded] = useState<Set<string>>(() => new Set());
|
|
209
|
-
const searching = search.trim() !== '';
|
|
210
113
|
const groups = useMemo(() => {
|
|
211
114
|
const term = search.trim().toLowerCase();
|
|
212
115
|
const matches = term
|
|
@@ -219,60 +122,52 @@ const CatalogList = memo(function CatalogList({
|
|
|
219
122
|
}, [resources, search]);
|
|
220
123
|
|
|
221
124
|
return (
|
|
222
|
-
|
|
223
|
-
<div className="relative
|
|
125
|
+
<>
|
|
126
|
+
<div className="relative">
|
|
224
127
|
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]" />
|
|
225
128
|
<input
|
|
226
129
|
value={search}
|
|
227
130
|
onChange={(e) => setSearch(e.target.value)}
|
|
228
131
|
placeholder="Search the catalog"
|
|
229
|
-
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))]"
|
|
230
133
|
/>
|
|
231
134
|
</div>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
Show all {items.length} {label.toLowerCase()}
|
|
263
|
-
</button>
|
|
264
|
-
)}
|
|
265
|
-
</section>
|
|
266
|
-
);
|
|
267
|
-
})}
|
|
268
|
-
</div>
|
|
269
|
-
</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
|
+
</>
|
|
270
165
|
);
|
|
271
|
-
}
|
|
166
|
+
}
|
|
272
167
|
|
|
273
168
|
const CatalogItem = memo(function CatalogItem({
|
|
274
169
|
resource,
|
|
275
|
-
icon
|
|
170
|
+
icon,
|
|
276
171
|
iconClassName,
|
|
277
172
|
onCanvas,
|
|
278
173
|
onAdd,
|
|
@@ -288,27 +183,21 @@ const CatalogItem = memo(function CatalogItem({
|
|
|
288
183
|
// Drawn like the card it becomes (domains and systems can become containers too, but start as cards)
|
|
289
184
|
const preview = useMemo(() => ({ type: resource.node.type, data: catalogNodeData(resource) }), [resource]);
|
|
290
185
|
return (
|
|
291
|
-
<
|
|
292
|
-
|
|
186
|
+
<PaletteItem
|
|
187
|
+
icon={icon}
|
|
188
|
+
iconClassName={iconClassName}
|
|
189
|
+
label={resource.name}
|
|
190
|
+
description={resource.summary ? `v${resource.version} · ${resource.summary}` : `v${resource.version}`}
|
|
293
191
|
onDragStart={startDrag(CATALOG_DRAG_TYPE, resource.key, preview)}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
<span className="truncate">{resource.name}</span>
|
|
300
|
-
<span className="shrink-0 text-[10px] text-[rgb(var(--ec-page-text-muted))]">v{resource.version}</span>
|
|
301
|
-
</span>
|
|
302
|
-
<span className="flex shrink-0 items-center gap-1">
|
|
303
|
-
{onCanvas && (
|
|
304
|
-
<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">
|
|
305
197
|
<Check size={14} className={STATUS.success.text} />
|
|
306
198
|
</span>
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
</button>
|
|
311
|
-
</span>
|
|
312
|
-
</div>
|
|
199
|
+
) : undefined
|
|
200
|
+
}
|
|
201
|
+
/>
|
|
313
202
|
);
|
|
314
203
|
});
|