@eventcatalog/core 5.0.0-beta.3 → 5.0.0-beta.30
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 +234 -134
- package/README.md +5 -5
- package/dist/analytics/analytics.cjs +18 -3
- package/dist/analytics/analytics.d.cts +1 -1
- package/dist/analytics/analytics.d.ts +1 -1
- package/dist/analytics/analytics.js +3 -2
- package/dist/analytics/log-build.cjs +160 -77
- package/dist/analytics/log-build.d.cts +2 -2
- package/dist/analytics/log-build.d.ts +2 -2
- package/dist/analytics/log-build.js +7 -5
- package/dist/analytics/studio-usage.cjs +90 -0
- package/dist/analytics/studio-usage.d.cts +5 -0
- package/dist/analytics/studio-usage.d.ts +5 -0
- package/dist/analytics/studio-usage.js +10 -0
- package/dist/analytics/telemetry.cjs +42 -0
- package/dist/analytics/telemetry.d.cts +20 -0
- package/dist/analytics/telemetry.d.ts +20 -0
- package/dist/analytics/telemetry.js +8 -0
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-RFIXKATC.js → chunk-4G7FSRPN.js} +35 -42
- package/dist/{chunk-6OC46WQJ.js → chunk-55QETQZ3.js} +66 -35
- package/dist/{chunk-TQLRG35T.js → chunk-AD7CL772.js} +78 -45
- package/dist/chunk-CA6WCQ2T.js +54 -0
- package/dist/chunk-CPW6G62V.js +17 -0
- package/dist/{chunk-423S2KHN.js → chunk-FLAZ2JVN.js} +4 -2
- package/dist/{chunk-BH2F5WPN.js → chunk-GQI5UYXZ.js} +1 -1
- package/dist/{chunk-GYIVJXOJ.js → chunk-NA7OTBWW.js} +1 -1
- package/dist/{chunk-MDWB5HLO.js → chunk-NKNOXQ62.js} +1 -1
- package/dist/{chunk-3PKS2IIP.js → chunk-OX6TSVHY.js} +1 -1
- package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
- package/dist/chunk-S5GWAUG5.js +29 -0
- package/dist/{chunk-NDJOAITI.js → chunk-T3OROMAU.js} +9 -3
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog-config-file-utils.cjs +34 -41
- package/dist/eventcatalog-config-file-utils.d.cts +56 -58
- package/dist/eventcatalog-config-file-utils.d.ts +56 -58
- package/dist/eventcatalog-config-file-utils.js +1 -1
- package/dist/eventcatalog.cjs +531 -356
- package/dist/eventcatalog.config.d.cts +59 -1
- package/dist/eventcatalog.config.d.ts +59 -1
- package/dist/eventcatalog.js +63 -25
- package/dist/features.cjs +34 -41
- package/dist/features.js +2 -2
- package/dist/federation/federate.cjs +37 -44
- package/dist/federation/federate.js +2 -2
- package/dist/generate.cjs +37 -42
- package/dist/generate.js +4 -4
- package/dist/resolve-catalog-dependencies.cjs +34 -41
- package/dist/resolve-catalog-dependencies.js +2 -2
- 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/@hocuspocus__provider.mjs +3 -0
- package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
- package/dist/runtime-dependencies/htmlparser2.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +13 -1
- package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
- package/dist/runtime-dependencies/yjs.mjs +3 -0
- package/dist/utils/child-process-signals.cjs +53 -0
- package/dist/utils/child-process-signals.d.cts +19 -0
- package/dist/utils/child-process-signals.d.ts +19 -0
- package/dist/utils/child-process-signals.js +6 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/dist/utils/license-status.cjs +78 -45
- package/dist/utils/license-status.d.cts +15 -5
- package/dist/utils/license-status.d.ts +15 -5
- package/dist/utils/license-status.js +1 -1
- package/eventcatalog/astro.config.mjs +23 -1
- package/eventcatalog/integrations/config-loader.mjs +56 -58
- package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
- package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
- package/eventcatalog/integrations/storage.mjs +20 -0
- package/eventcatalog/integrations/studio-server.mjs +32 -0
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
- package/eventcatalog/src/components/Header.astro +3 -3
- package/eventcatalog/src/components/Homepage/CatalogDashboard.astro +342 -0
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +60 -4
- package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
- package/eventcatalog/src/components/NavigationProgress.astro +7 -0
- package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +2 -0
- package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -0
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
- package/eventcatalog/src/components/Search/SearchModal.tsx +2 -10
- package/eventcatalog/src/components/Search/search-utils.ts +26 -0
- package/eventcatalog/src/components/Seo.astro +5 -1
- package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +53 -48
- package/eventcatalog/src/components/Settings/GeneralSettingsForm.tsx +183 -178
- package/eventcatalog/src/components/Settings/LicenseSettings.tsx +140 -31
- package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +40 -35
- package/eventcatalog/src/components/Settings/LogoUpload.tsx +1 -1
- package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +9 -8
- package/eventcatalog/src/components/Settings/Row.tsx +7 -4
- package/eventcatalog/src/components/Settings/SettingsShared.tsx +3 -3
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +53 -83
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +131 -0
- package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
- package/eventcatalog/src/components/TrialBadge.astro +20 -9
- package/eventcatalog/src/content.config.ts +19 -2
- package/eventcatalog/src/features/auth/error.astro +4 -2
- package/eventcatalog/src/features/auth/login.astro +2 -1
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +10 -0
- package/eventcatalog/src/features/auth/unauthorized.astro +1 -1
- package/eventcatalog/src/features/fields/pages/fields.astro +1 -1
- package/eventcatalog/src/features/integrations/eventcatalog-features.ts +34 -1
- package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +47 -0
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +24 -0
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +278 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +272 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +65 -65
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
- package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
- package/eventcatalog/src/features/mcp/mcp-server.ts +36 -5
- package/eventcatalog/src/features/server/start.ts +25 -0
- package/eventcatalog/src/features/storage/database.ts +88 -0
- package/eventcatalog/src/features/storage/migrations.ts +85 -0
- package/eventcatalog/src/features/storage/sql/database.sql +5 -0
- package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
- package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
- package/eventcatalog/src/features/storage/sql.ts +28 -0
- package/eventcatalog/src/features/studio/agent-choreography.ts +384 -0
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +219 -0
- package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
- package/eventcatalog/src/features/studio/api/client.ts +40 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +540 -0
- package/eventcatalog/src/features/studio/canvas-preview.ts +52 -0
- package/eventcatalog/src/features/studio/catalog-resources.ts +155 -0
- package/eventcatalog/src/features/studio/catalog.ts +173 -0
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +518 -0
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +118 -0
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +434 -0
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +143 -0
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +65 -0
- package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +109 -0
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +206 -0
- 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 +293 -0
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +396 -0
- package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +2091 -0
- package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
- 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 +63 -0
- package/eventcatalog/src/features/studio/hooks/presence-store.ts +119 -0
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +353 -0
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +54 -0
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +109 -0
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +782 -0
- package/eventcatalog/src/features/studio/identity.ts +59 -0
- package/eventcatalog/src/features/studio/limits.ts +2 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +69 -0
- package/eventcatalog/src/features/studio/pages/index.astro +784 -0
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +495 -0
- package/eventcatalog/src/features/studio/server/runtime.ts +498 -0
- package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
- package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
- package/eventcatalog/src/features/studio/server/storage.ts +90 -0
- package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
- package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
- package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
- package/eventcatalog/src/layouts/SettingsLayout.astro +2 -2
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +13 -5
- package/eventcatalog/src/pages/_index.astro +5 -342
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +2 -3
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -1
- package/eventcatalog/src/pages/diagrams/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +1 -0
- package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/directory/[type]/_index.data.ts +5 -7
- package/eventcatalog/src/pages/directory/[type]/index.astro +3 -1
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -7
- package/eventcatalog/src/pages/discover/[type]/index.astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/_[...filename].data.ts +0 -4
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +3 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +2 -3
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_[filename].data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +2 -1
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +1 -0
- package/eventcatalog/src/pages/docs/custom/[...path]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +1 -0
- package/eventcatalog/src/pages/docs/services/[id]/asyncapi/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/graphql/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/services/[id]/spec/[filename].astro +1 -1
- package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -0
- package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -0
- package/eventcatalog/src/pages/index.astro +4 -2
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +0 -7
- package/eventcatalog/src/pages/schemas/explorer/index.astro +4 -2
- package/eventcatalog/src/pages/settings/license.astro +9 -1
- package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/_index.data.ts +38 -0
- package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +18 -41
- package/eventcatalog/src/pages/visualiser/designs/[id]/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -1
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +0 -7
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +1 -0
- package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +1 -0
- package/eventcatalog/src/types/react-syntax-highlighter.d.ts +10 -0
- package/eventcatalog/src/utils/collections/util.ts +8 -4
- package/eventcatalog/src/utils/feature.ts +8 -0
- package/eventcatalog/src/utils/git.ts +22 -5
- package/eventcatalog/src/utils/homepage-dashboard.ts +143 -0
- package/eventcatalog/src/utils/license.ts +1 -1
- package/eventcatalog/src/utils/link-validation.ts +25 -19
- package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +3 -1
- package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
- package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
- package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +4 -2
- package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +20 -1
- package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
- package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
- package/eventcatalog/src/utils/page-loaders/hybrid-page.ts +4 -15
- package/eventcatalog/src/utils/page-title.ts +15 -0
- package/eventcatalog/src/utils/syntax-highlighter-renderer.ts +64 -0
- package/eventcatalog/src/utils/trial.ts +14 -1
- package/package.json +19 -13
- package/dist/runtime-dependencies/shelljs.mjs +0 -3
- package/eventcatalog/public/studio-bg.png +0 -0
- package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
- package/eventcatalog/src/pages/studio.astro +0 -316
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useRef, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Studio's dialogs: a card over the page, closed by Escape or clicking outside it (not while it's busy). Focus stays
|
|
5
|
+
* in it: anything that moves focus outside (e.g. the menu it was opened from, closing) sends it back to its first
|
|
6
|
+
* field or button.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const SECONDARY_BUTTON =
|
|
10
|
+
'rounded-lg border px-4 py-2 text-sm font-medium border-[rgb(var(--ec-page-border))] hover:bg-[rgb(var(--ec-page-border)/0.4)] disabled:opacity-50';
|
|
11
|
+
export const PRIMARY_BUTTON =
|
|
12
|
+
'rounded-lg px-4 py-2 text-sm font-medium bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))] hover:bg-[rgb(var(--ec-button-bg-hover))] disabled:opacity-60';
|
|
13
|
+
|
|
14
|
+
export default function Dialog({
|
|
15
|
+
labelledBy,
|
|
16
|
+
role = 'dialog',
|
|
17
|
+
busy = false,
|
|
18
|
+
onClose,
|
|
19
|
+
children,
|
|
20
|
+
}: {
|
|
21
|
+
/** The id of its heading */
|
|
22
|
+
labelledBy: string;
|
|
23
|
+
/** `alertdialog` when it asks to confirm something that can't be undone */
|
|
24
|
+
role?: 'dialog' | 'alertdialog';
|
|
25
|
+
/** Doing what was asked: it can't be closed until it's done */
|
|
26
|
+
busy?: boolean;
|
|
27
|
+
onClose: () => void;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}) {
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && !busy && onClose();
|
|
32
|
+
window.addEventListener('keydown', onKeyDown);
|
|
33
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
34
|
+
}, [onClose, busy]);
|
|
35
|
+
|
|
36
|
+
const card = useRef<HTMLDivElement>(null);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const keepFocus = (event: FocusEvent) => {
|
|
39
|
+
if (!card.current || card.current.contains(event.target as globalThis.Node)) return;
|
|
40
|
+
card.current.querySelector<HTMLElement>('input, button')?.focus();
|
|
41
|
+
};
|
|
42
|
+
document.addEventListener('focusin', keepFocus);
|
|
43
|
+
return () => document.removeEventListener('focusin', keepFocus);
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4" onClick={() => !busy && onClose()}>
|
|
48
|
+
<div
|
|
49
|
+
ref={card}
|
|
50
|
+
role={role}
|
|
51
|
+
aria-modal="true"
|
|
52
|
+
aria-labelledby={labelledBy}
|
|
53
|
+
onClick={(event) => event.stopPropagation()}
|
|
54
|
+
className="w-full max-w-md rounded-xl border shadow-2xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Users } from 'lucide-react';
|
|
3
|
+
import { MAX_NAME_LENGTH, PERSON_COLORS } from '../identity';
|
|
4
|
+
|
|
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
|
+
}) {
|
|
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();
|
|
30
|
+
|
|
31
|
+
return (
|
|
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"
|
|
42
|
+
>
|
|
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>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { memo, useMemo, useState } from 'react';
|
|
2
|
+
import { Check, Search } from 'lucide-react';
|
|
3
|
+
import type { CatalogResource } from '../catalog-resources';
|
|
4
|
+
import { catalogNodeData } from '../catalog';
|
|
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';
|
|
19
|
+
export const CATALOG_DRAG_TYPE = 'application/eventcatalog-studio-resource';
|
|
20
|
+
|
|
21
|
+
/** A catalog group shows this many resources until it's expanded, so big catalogs stay quick */
|
|
22
|
+
const GROUP_PREVIEW_SIZE = 50;
|
|
23
|
+
|
|
24
|
+
export type LeftPanelTab = 'components' | 'catalog';
|
|
25
|
+
|
|
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
|
+
*/
|
|
30
|
+
export default memo(function LeftPanel({
|
|
31
|
+
level,
|
|
32
|
+
onShowAll,
|
|
33
|
+
resources,
|
|
34
|
+
keysOnCanvas,
|
|
35
|
+
onAddComponent,
|
|
36
|
+
onAddResource,
|
|
37
|
+
tab,
|
|
38
|
+
onTabChange,
|
|
39
|
+
startCollapsed,
|
|
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;
|
|
45
|
+
resources: CatalogResource[];
|
|
46
|
+
/** The catalog resources on the canvas */
|
|
47
|
+
keysOnCanvas: Set<string>;
|
|
48
|
+
onAddComponent: (type: string) => void;
|
|
49
|
+
onAddResource: (key: string) => void;
|
|
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;
|
|
55
|
+
}) {
|
|
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
|
+
);
|
|
75
|
+
|
|
76
|
+
return (
|
|
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
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
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 }) {
|
|
90
|
+
return (
|
|
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>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function CatalogList({
|
|
101
|
+
resources,
|
|
102
|
+
keysOnCanvas,
|
|
103
|
+
onAdd,
|
|
104
|
+
startDrag,
|
|
105
|
+
}: {
|
|
106
|
+
resources: CatalogResource[];
|
|
107
|
+
keysOnCanvas: Set<string>;
|
|
108
|
+
onAdd: (key: string) => void;
|
|
109
|
+
startDrag: StartDrag;
|
|
110
|
+
}) {
|
|
111
|
+
const [search, setSearch] = useState('');
|
|
112
|
+
const [expanded, setExpanded] = useState<Set<string>>(() => new Set());
|
|
113
|
+
const groups = useMemo(() => {
|
|
114
|
+
const term = search.trim().toLowerCase();
|
|
115
|
+
const matches = term
|
|
116
|
+
? resources.filter((resource) => resource.name.toLowerCase().includes(term) || resource.id.toLowerCase().includes(term))
|
|
117
|
+
: resources;
|
|
118
|
+
return CATALOG_GROUPS.map((group) => ({
|
|
119
|
+
...group,
|
|
120
|
+
items: matches.filter((resource) => resource.collection === group.collection),
|
|
121
|
+
})).filter((group) => group.items.length > 0);
|
|
122
|
+
}, [resources, search]);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<div className="relative">
|
|
127
|
+
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]" />
|
|
128
|
+
<input
|
|
129
|
+
// The list shows when its tab is chosen: typing then searches it, rather than reaching the canvas's shortcuts
|
|
130
|
+
// (from the tab button, typing "Order Service" picked the Comment tool)
|
|
131
|
+
autoFocus
|
|
132
|
+
value={search}
|
|
133
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
134
|
+
placeholder="Search the catalog"
|
|
135
|
+
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))]"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
{groups.length === 0 && (
|
|
139
|
+
<p className="py-6 text-center text-xs text-[rgb(var(--ec-page-text-muted))]">No resources found.</p>
|
|
140
|
+
)}
|
|
141
|
+
{groups.map(({ collection, label, icon, className, items }) => {
|
|
142
|
+
const shown = expanded.has(collection) ? items : items.slice(0, GROUP_PREVIEW_SIZE);
|
|
143
|
+
return (
|
|
144
|
+
<PaletteSection key={collection} title={`${label} (${items.length})`}>
|
|
145
|
+
{shown.map((resource) => (
|
|
146
|
+
<CatalogItem
|
|
147
|
+
key={resource.key}
|
|
148
|
+
resource={resource}
|
|
149
|
+
icon={icon}
|
|
150
|
+
iconClassName={className}
|
|
151
|
+
onCanvas={keysOnCanvas.has(resource.key)}
|
|
152
|
+
onAdd={onAdd}
|
|
153
|
+
startDrag={startDrag}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
156
|
+
{shown.length < items.length && (
|
|
157
|
+
<button
|
|
158
|
+
onClick={() => setExpanded((current) => new Set(current).add(collection))}
|
|
159
|
+
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))]"
|
|
160
|
+
>
|
|
161
|
+
Show all {items.length} {label.toLowerCase()}
|
|
162
|
+
</button>
|
|
163
|
+
)}
|
|
164
|
+
</PaletteSection>
|
|
165
|
+
);
|
|
166
|
+
})}
|
|
167
|
+
</>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const CatalogItem = memo(function CatalogItem({
|
|
172
|
+
resource,
|
|
173
|
+
icon,
|
|
174
|
+
iconClassName,
|
|
175
|
+
onCanvas,
|
|
176
|
+
onAdd,
|
|
177
|
+
startDrag,
|
|
178
|
+
}: {
|
|
179
|
+
resource: CatalogResource;
|
|
180
|
+
icon: (typeof CATALOG_GROUPS)[number]['icon'];
|
|
181
|
+
iconClassName: string;
|
|
182
|
+
onCanvas: boolean;
|
|
183
|
+
onAdd: (key: string) => void;
|
|
184
|
+
startDrag: StartDrag;
|
|
185
|
+
}) {
|
|
186
|
+
// Drawn like the card it becomes (domains and systems can become containers too, but start as cards)
|
|
187
|
+
const preview = useMemo(() => ({ type: resource.node.type, data: catalogNodeData(resource) }), [resource]);
|
|
188
|
+
return (
|
|
189
|
+
<PaletteItem
|
|
190
|
+
icon={icon}
|
|
191
|
+
iconClassName={iconClassName}
|
|
192
|
+
label={resource.name}
|
|
193
|
+
description={resource.summary ? `v${resource.version} · ${resource.summary}` : `v${resource.version}`}
|
|
194
|
+
onDragStart={startDrag(CATALOG_DRAG_TYPE, resource.key, preview)}
|
|
195
|
+
onClick={() => onAdd(resource.key)}
|
|
196
|
+
title={`${resource.name}: drag onto the canvas, or click to add`}
|
|
197
|
+
trailing={
|
|
198
|
+
onCanvas ? (
|
|
199
|
+
<span title="On the canvas" className="flex-none">
|
|
200
|
+
<Check size={14} className={STATUS.success.text} />
|
|
201
|
+
</span>
|
|
202
|
+
) : undefined
|
|
203
|
+
}
|
|
204
|
+
/>
|
|
205
|
+
);
|
|
206
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { memo, type RefObject } from 'react';
|
|
2
|
+
import type { Node } from '@xyflow/react';
|
|
3
|
+
import { Box, ExternalLink, Library } from 'lucide-react';
|
|
4
|
+
import { getCatalogLink } from '../catalog';
|
|
5
|
+
import {
|
|
6
|
+
EditableText,
|
|
7
|
+
EditStatus,
|
|
8
|
+
Field,
|
|
9
|
+
NodeCard,
|
|
10
|
+
getNodeLabel,
|
|
11
|
+
getResource,
|
|
12
|
+
isVersioned,
|
|
13
|
+
updateResource,
|
|
14
|
+
useSavedFlash,
|
|
15
|
+
type UpdateNodeData,
|
|
16
|
+
NODE_ICONS,
|
|
17
|
+
} from '@eventcatalog/visualiser/studio';
|
|
18
|
+
|
|
19
|
+
/** Something it's connected to: the node at the other end, and the connection's label */
|
|
20
|
+
export type NodeConnection = { id: string; name: string; type?: string; label?: string; outgoing: boolean };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A node's details, in a card next to it (as Studio on the web's): its type and where it is, and its name, version
|
|
24
|
+
* and summary, saved as they're typed, and what it's connected to (click one to go to it). Catalog resources are read only (they're changed in the catalog), as is
|
|
25
|
+
* everything on a locked canvas. Notes and text are edited on the canvas.
|
|
26
|
+
*/
|
|
27
|
+
export default memo(function NodeDetails({
|
|
28
|
+
node,
|
|
29
|
+
parentName,
|
|
30
|
+
connections,
|
|
31
|
+
onOpenNode,
|
|
32
|
+
containerRef,
|
|
33
|
+
locked,
|
|
34
|
+
onChange,
|
|
35
|
+
onClose,
|
|
36
|
+
}: {
|
|
37
|
+
/** What's shown of the node (not its position: the card follows the node on screen by itself) */
|
|
38
|
+
node: Pick<Node, 'id' | 'type' | 'data'>;
|
|
39
|
+
/** The domain or system it's in, if it's in one */
|
|
40
|
+
parentName?: string;
|
|
41
|
+
connections: NodeConnection[];
|
|
42
|
+
/** Goes to a node it's connected to (and shows its card) */
|
|
43
|
+
onOpenNode: (id: string) => void;
|
|
44
|
+
/** The canvas, to put the card next to the node */
|
|
45
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
46
|
+
locked: boolean;
|
|
47
|
+
onChange: UpdateNodeData;
|
|
48
|
+
onClose: () => void;
|
|
49
|
+
}) {
|
|
50
|
+
const { saved, showSaved } = useSavedFlash();
|
|
51
|
+
const resource = getResource(node.type, node.data);
|
|
52
|
+
const link = getCatalogLink(node);
|
|
53
|
+
const label = getNodeLabel(node.type);
|
|
54
|
+
const { icon: Icon, className: iconClassName } = NODE_ICONS[node.type ?? ''] ?? { icon: Box, className: '' };
|
|
55
|
+
const versioned = isVersioned(node.type);
|
|
56
|
+
const name = String(resource.name ?? '');
|
|
57
|
+
const version = String(link?.version ?? resource.version ?? '');
|
|
58
|
+
const summary = String(resource.summary ?? '');
|
|
59
|
+
const save = (fields: Record<string, string>) => onChange(node.id, (data) => updateResource(node.type, data, fields));
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<NodeCard
|
|
63
|
+
nodeId={node.id}
|
|
64
|
+
containerRef={containerRef}
|
|
65
|
+
label={name || label}
|
|
66
|
+
onClose={onClose}
|
|
67
|
+
kicker={
|
|
68
|
+
<>
|
|
69
|
+
{/* Its type, in its colour (as the web's type pill) */}
|
|
70
|
+
<span className={`flex-none ${iconClassName}`}>
|
|
71
|
+
<span className="inline-flex items-center gap-1 rounded bg-current px-1.5 text-[9px] font-bold uppercase leading-4 tracking-wider">
|
|
72
|
+
<Icon aria-hidden size={10} className="text-white" />
|
|
73
|
+
<span className="text-white">{label}</span>
|
|
74
|
+
</span>
|
|
75
|
+
</span>
|
|
76
|
+
{parentName && <span className="ec-doc-card-breadcrumb">in {parentName}</span>}
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
header={
|
|
80
|
+
link || locked ? (
|
|
81
|
+
<>
|
|
82
|
+
<h2 className="ec-doc-card-title">
|
|
83
|
+
{name}
|
|
84
|
+
{version && <span className="ec-doc-card-version">v{version}</span>}
|
|
85
|
+
</h2>
|
|
86
|
+
{summary && <p className="ec-doc-card-summary">{summary}</p>}
|
|
87
|
+
{link && (
|
|
88
|
+
<div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
89
|
+
<span className="inline-flex items-center gap-1.5">
|
|
90
|
+
<Library aria-hidden size={13} className="text-[rgb(var(--ec-accent))]" />
|
|
91
|
+
From your catalog: change it there
|
|
92
|
+
</span>
|
|
93
|
+
<a
|
|
94
|
+
href={link.url}
|
|
95
|
+
target="_blank"
|
|
96
|
+
rel="noreferrer"
|
|
97
|
+
className="inline-flex items-center gap-1 font-medium text-[rgb(var(--ec-accent))] hover:underline"
|
|
98
|
+
>
|
|
99
|
+
Open in EventCatalog <ExternalLink aria-hidden size={12} />
|
|
100
|
+
</a>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</>
|
|
104
|
+
) : (
|
|
105
|
+
// A small form: what's typed is saved as it's typed (there's nothing to press), and shown on the node
|
|
106
|
+
<div className="ec-edit-form" key={node.id}>
|
|
107
|
+
<Field label="Name">
|
|
108
|
+
<EditableText
|
|
109
|
+
label="Name"
|
|
110
|
+
value={name}
|
|
111
|
+
onSave={(next) => save({ name: next })}
|
|
112
|
+
onSaved={showSaved}
|
|
113
|
+
placeholder={`Name this ${label.toLowerCase()}`}
|
|
114
|
+
className="ec-editable-title"
|
|
115
|
+
required
|
|
116
|
+
/>
|
|
117
|
+
</Field>
|
|
118
|
+
{versioned && (
|
|
119
|
+
<Field label="Version" className="ec-field-version">
|
|
120
|
+
<EditableText
|
|
121
|
+
label="Version"
|
|
122
|
+
value={version || '0.0.1'}
|
|
123
|
+
onSave={(next) => save({ version: next })}
|
|
124
|
+
onSaved={showSaved}
|
|
125
|
+
placeholder="0.0.1"
|
|
126
|
+
required
|
|
127
|
+
/>
|
|
128
|
+
</Field>
|
|
129
|
+
)}
|
|
130
|
+
<Field label="Summary">
|
|
131
|
+
<EditableText
|
|
132
|
+
label="Summary"
|
|
133
|
+
value={summary}
|
|
134
|
+
onSave={(next) => save({ summary: next })}
|
|
135
|
+
onSaved={showSaved}
|
|
136
|
+
placeholder="What it does, in a sentence or two"
|
|
137
|
+
className="ec-editable-summary"
|
|
138
|
+
multiline
|
|
139
|
+
/>
|
|
140
|
+
</Field>
|
|
141
|
+
<EditStatus saved={saved} />
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
>
|
|
146
|
+
<div className="ec-doc-card-body px-4 pb-3.5 pt-1 empty:hidden">
|
|
147
|
+
{(['Connects to', 'Connected from'] as const).map((heading) => {
|
|
148
|
+
const items = connections.filter((connection) => connection.outgoing === (heading === 'Connects to'));
|
|
149
|
+
if (!items.length) return null;
|
|
150
|
+
return (
|
|
151
|
+
<section key={heading} className="mt-3">
|
|
152
|
+
<h3 className="mb-1.5 text-xs font-semibold text-[rgb(var(--ec-page-text-muted))]">{heading}</h3>
|
|
153
|
+
<ul className="space-y-0.5">
|
|
154
|
+
{items.map((item) => {
|
|
155
|
+
const { icon: ItemIcon, className: itemIconClassName } = NODE_ICONS[item.type ?? ''] ?? {
|
|
156
|
+
icon: Box,
|
|
157
|
+
className: '',
|
|
158
|
+
};
|
|
159
|
+
return (
|
|
160
|
+
<li key={`${item.id}-${item.label}`}>
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
title={`Go to ${item.name}`}
|
|
164
|
+
onClick={() => onOpenNode(item.id)}
|
|
165
|
+
className="flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-left text-sm hover:bg-[rgb(var(--ec-content-hover))]"
|
|
166
|
+
>
|
|
167
|
+
<ItemIcon aria-hidden size={15} className={`flex-none ${itemIconClassName}`} />
|
|
168
|
+
<span className="min-w-0 flex-1 truncate">{item.name}</span>
|
|
169
|
+
{item.label && (
|
|
170
|
+
<span className="flex-none text-[11px] text-[rgb(var(--ec-page-text-muted))]">{item.label}</span>
|
|
171
|
+
)}
|
|
172
|
+
</button>
|
|
173
|
+
</li>
|
|
174
|
+
);
|
|
175
|
+
})}
|
|
176
|
+
</ul>
|
|
177
|
+
</section>
|
|
178
|
+
);
|
|
179
|
+
})}
|
|
180
|
+
</div>
|
|
181
|
+
</NodeCard>
|
|
182
|
+
);
|
|
183
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState, type FormEvent } from 'react';
|
|
2
|
+
import { MAX_TITLE_LENGTH } from '../limits';
|
|
3
|
+
import Dialog, { PRIMARY_BUTTON, SECONDARY_BUTTON } from './Dialog';
|
|
4
|
+
import { STATUS } from '@eventcatalog/visualiser/studio';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Naming the canvas a diagram is opened in (the visualiser's "Open in Studio"), named after the diagram to start
|
|
8
|
+
* with, like a new canvas on the Studio page. Loaded with the visualiser, so it imports nothing that loads a canvas.
|
|
9
|
+
*/
|
|
10
|
+
export default function OpenInStudioDialog({
|
|
11
|
+
title,
|
|
12
|
+
onOpen,
|
|
13
|
+
onClose,
|
|
14
|
+
}: {
|
|
15
|
+
/** The diagram's title */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** Makes the canvas with this name and opens it (rejects with what went wrong) */
|
|
18
|
+
onOpen: (title: string | undefined) => Promise<void>;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}) {
|
|
21
|
+
const [name, setName] = useState((title ?? '').slice(0, MAX_TITLE_LENGTH));
|
|
22
|
+
const [opening, setOpening] = useState(false);
|
|
23
|
+
const [error, setError] = useState<string>();
|
|
24
|
+
|
|
25
|
+
const submit = async (event: FormEvent) => {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
setOpening(true);
|
|
28
|
+
setError(undefined);
|
|
29
|
+
try {
|
|
30
|
+
// Stays open (and busy) while the canvas page loads
|
|
31
|
+
await onOpen(name.trim() || undefined);
|
|
32
|
+
} catch (failure) {
|
|
33
|
+
setError((failure as Error).message);
|
|
34
|
+
setOpening(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Dialog labelledBy="open-in-studio-title" busy={opening} onClose={onClose}>
|
|
40
|
+
<form onSubmit={submit}>
|
|
41
|
+
<div className="border-b px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
42
|
+
<h2 id="open-in-studio-title" className="text-base font-semibold">
|
|
43
|
+
Open in Studio
|
|
44
|
+
</h2>
|
|
45
|
+
<p className="mt-0.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
46
|
+
A new canvas starts with this diagram. Name it for what you're designing, so your team can find it.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="px-5 py-4">
|
|
50
|
+
<label htmlFor="open-in-studio-name" className="block text-sm font-semibold">
|
|
51
|
+
Name
|
|
52
|
+
</label>
|
|
53
|
+
<input
|
|
54
|
+
id="open-in-studio-name"
|
|
55
|
+
// Ready to type a new name, or keep the diagram's
|
|
56
|
+
autoFocus
|
|
57
|
+
onFocus={(event) => event.target.select()}
|
|
58
|
+
value={name}
|
|
59
|
+
onChange={(event) => setName(event.target.value)}
|
|
60
|
+
maxLength={MAX_TITLE_LENGTH}
|
|
61
|
+
autoComplete="off"
|
|
62
|
+
placeholder="e.g. Payments redesign"
|
|
63
|
+
disabled={opening}
|
|
64
|
+
className="mt-2 w-full rounded-lg border px-3 py-2 text-sm shadow-sm bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))] placeholder:text-[rgb(var(--ec-page-text-muted))] focus:outline-none focus:border-[rgb(var(--ec-accent))] focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.25)]"
|
|
65
|
+
/>
|
|
66
|
+
{error && <p className={`mt-2 text-sm ${STATUS.danger.text}`}>{error}</p>}
|
|
67
|
+
</div>
|
|
68
|
+
<div className="flex justify-end gap-2 border-t px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
69
|
+
<button type="button" onClick={onClose} disabled={opening} className={SECONDARY_BUTTON}>
|
|
70
|
+
Cancel
|
|
71
|
+
</button>
|
|
72
|
+
<button type="submit" disabled={opening} className={PRIMARY_BUTTON}>
|
|
73
|
+
{opening ? 'Opening…' : 'Open in Studio'}
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
</Dialog>
|
|
78
|
+
);
|
|
79
|
+
}
|