@eventcatalog/core 5.0.0-beta.9 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -12
- package/README.md +5 -5
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +91 -44
- package/dist/analytics/log-build.d.cts +2 -2
- package/dist/analytics/log-build.d.ts +2 -2
- package/dist/analytics/log-build.js +6 -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/catalog-runtime.cjs +1 -1
- package/dist/{chunk-RFIXKATC.js → chunk-4G7FSRPN.js} +35 -42
- package/dist/{chunk-AWOO63ME.js → chunk-AD7CL772.js} +11 -4
- package/dist/chunk-CA6WCQ2T.js +54 -0
- package/dist/{chunk-GAHKCOQM.js → chunk-JZN4RSMF.js} +1 -1
- package/dist/{chunk-JQMYHZWI.js → chunk-NEAVAFSJ.js} +16 -8
- package/dist/{chunk-4CFZUHGG.js → chunk-NIVST4WY.js} +1 -1
- package/dist/{chunk-ENAG6UH7.js → chunk-NKNOXQ62.js} +1 -6
- package/dist/{chunk-ZEY2XUJN.js → chunk-OX6TSVHY.js} +6 -6
- package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
- package/dist/{chunk-EUNQJO3J.js → chunk-RE7UTPWH.js} +1 -1
- package/dist/chunk-S5GWAUG5.js +29 -0
- package/dist/{chunk-TZVDMWQX.js → chunk-YQGRMDRB.js} +4 -2
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/api/02-config.md +57 -7
- package/dist/docs/development/00-why-eventcatalog.md +11 -11
- package/dist/docs/development/01-fundamentals.md +20 -13
- package/dist/docs/development/01-getting-started/configuration-overview.md +2 -2
- package/dist/docs/development/01-getting-started/installation.md +9 -1
- package/dist/docs/development/_getting-started.mdx +1 -1
- package/dist/docs/development/ask-your-architecture/01-intro.md +5 -5
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/01-what-is-eventcatalog-assistant.md +0 -4
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/02-configuration.md +15 -3
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/03-bring-your-own-tools.md +0 -3
- package/dist/docs/development/ask-your-architecture/02-eventcatalog-assistant/_category_.json +1 -1
- package/dist/docs/development/ask-your-architecture/02-llms.txt.md +1 -1
- package/dist/docs/development/ask-your-architecture/{05-skills/01-introduction.md → 03-install-skills.md} +57 -7
- package/dist/docs/development/ask-your-architecture/03-mcp-server/_category_.json +1 -1
- package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +86 -33
- package/dist/docs/development/ask-your-architecture/03-mcp-server/introduction.md +14 -4
- package/dist/docs/development/ask-your-architecture/03-schemas.txt.md +1 -1
- package/dist/docs/development/ask-your-architecture/04-agents/_category_.json +1 -1
- package/dist/docs/development/authentication/01-introduction.md +2 -23
- package/dist/docs/development/authentication/02-enabling-authentication.md +0 -20
- package/dist/docs/development/authentication/07-rbac-middleware.md +0 -2
- package/dist/docs/development/authentication/providers/03-setting-up-github.md +0 -2
- package/dist/docs/development/authentication/providers/03a-setting-up-google.md +0 -2
- package/dist/docs/development/authentication/providers/04-setting-up-azure-ad.md +0 -2
- package/dist/docs/development/authentication/providers/05-setting-up-okta.md +0 -3
- package/dist/docs/development/authentication/providers/06-setting-up-auth0.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/01-introduction.md +0 -4
- package/dist/docs/development/bring-your-own-documentation/custom-pages/01-introduction.md +0 -3
- package/dist/docs/development/bring-your-own-documentation/custom-pages/02-adding-custom-docs.md +0 -17
- package/dist/docs/development/bring-your-own-documentation/custom-pages/03-components.md +0 -4
- package/dist/docs/development/bring-your-own-documentation/custom-pages/04-owners.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/resource-docs/01-introduction.md +1 -3
- package/dist/docs/development/bring-your-own-documentation/resource-docs/02-adding-resource-docs.md +1 -4
- package/dist/docs/development/bring-your-own-documentation/resource-docs/03-categories.md +0 -2
- package/dist/docs/development/bring-your-own-documentation/resource-docs/04-versioning.md +1 -3
- package/dist/docs/development/components/components/11-flow.md +17 -0
- package/dist/docs/development/components/components/15-nodegraph.md +43 -10
- package/dist/docs/development/components/components/18-remote-schema.md +1 -4
- package/dist/docs/development/components/custom-components/07-reference.md +14 -0
- package/dist/docs/development/customization/02-themes.md +0 -5
- package/dist/docs/development/customization/03-search.md +1 -1
- package/dist/docs/development/customization/05-custom-pages-and-api-routes/01-introduction.md +0 -2
- package/dist/docs/development/customization/05-custom-pages-and-api-routes/04-reference.md +7 -1
- package/dist/docs/development/customization/05-resource-sidebar.md +1 -1
- package/dist/docs/development/customization/09-embed-eventcatalog.md +20 -25
- package/dist/docs/development/deployment/hosting-options.md +7 -2
- package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/05-fix-common-problems.md +16 -0
- package/dist/docs/development/developer-tools/eventcatalog-linter/reference/03-rules.md +31 -1
- package/dist/docs/development/developer-tools/eventcatalog-linter/reference/05-supported-resources.md +1 -1
- package/dist/docs/development/governance/architecture-change-detection/01-introduction.md +0 -10
- package/dist/docs/development/governance/architecture-change-detection/06-pipeline-gates.md +0 -4
- package/dist/docs/development/guides/97-architecture-graph.md +1 -1
- package/dist/docs/development/guides/99-adding-analytics.md +0 -7
- package/dist/docs/development/guides/domains/03-model-subdomains.md +2 -2
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/01-add-systems-to-domains.md +3 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/02-add-services-to-domains.md +5 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/03-add-messages-to-domains.md +2 -2
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/05-add-entities-to-domains.md +1 -1
- package/dist/docs/development/guides/domains/04-add-resources-to-domains/06-domain-resources-page.md +40 -0
- package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-diagram.md +89 -0
- package/dist/docs/development/guides/domains/06-visualize-domains/_category_.json +1 -2
- package/dist/docs/development/guides/domains/09-reference.md +4 -4
- package/dist/docs/development/guides/owners/automated-teams-and-users/create-your-own-connector.md +0 -2
- package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-github.md +0 -3
- package/dist/docs/development/guides/owners/automated-teams-and-users/sync-from-microsoft-entra.md +0 -2
- package/dist/docs/development/guides/resources/adrs/03-linking-resources.md +8 -0
- package/dist/docs/development/guides/resources/adrs/06-reference.md +4 -4
- package/dist/docs/development/guides/resources/agents/11-reference.md +1 -1
- package/dist/docs/development/guides/resources/data/04-reference.md +1 -1
- package/dist/docs/development/guides/resources/data-products/07-reference.md +1 -1
- package/dist/docs/development/guides/resources/entities/05-entity-maps.md +1 -1
- package/dist/docs/development/guides/resources/entities/06-reference.md +2 -2
- package/dist/docs/development/guides/resources/flows/01-introduction.md +11 -1
- package/dist/docs/development/guides/resources/flows/03-model-flow-steps.md +65 -6
- package/dist/docs/development/guides/resources/flows/05-explore-flows.md +112 -0
- package/dist/docs/development/guides/resources/flows/06-reference.md +25 -4
- package/dist/docs/development/guides/resources/messages/01-what-are-messages.md +19 -0
- package/dist/docs/development/guides/resources/messages/04-message-channels/01-introduction.md +16 -0
- package/dist/docs/development/guides/resources/messages/04-message-channels/05-reference.md +2 -1
- package/dist/docs/development/guides/resources/messages/10-reference.md +6 -3
- package/dist/docs/development/guides/resources/schemas/04-explore-schemas/01-schema-explorer.md +2 -2
- package/dist/docs/development/guides/resources/schemas/04-explore-schemas/02-fields-explorer.md +0 -9
- package/dist/docs/development/guides/resources/schemas/05-schema-api.md +0 -3
- package/dist/docs/development/guides/resources/schemas/06-schema-mcp.md +11 -3
- package/dist/docs/development/guides/resources/services/04-add-resources-to-services/03-add-entities-to-services.md +1 -1
- package/dist/docs/development/guides/resources/services/05-resources-page.md +50 -0
- package/dist/docs/development/guides/resources/services/08-reference.md +2 -1
- package/dist/docs/development/guides/systems/01-introduction.md +1 -1
- package/dist/docs/development/guides/systems/02-create-system.md +2 -2
- package/dist/docs/development/guides/systems/03-add-resources-to-systems.md +35 -9
- package/dist/docs/development/guides/systems/04-model-relationships-and-actors.md +5 -3
- package/dist/docs/development/guides/systems/05-system-context-maps.md +39 -22
- package/dist/docs/development/guides/systems/06-reference.md +4 -4
- package/dist/docs/development/license/01-license-file.md +151 -0
- package/dist/docs/development/license/_category_.json +12 -0
- package/dist/docs/development/license-keys/_category_.json +3 -3
- package/dist/docs/development/license-keys/integrations.md +7 -37
- package/dist/docs/development/license-keys/license-validation.md +4 -0
- package/dist/docs/development/license-keys/overview.md +5 -1
- package/dist/docs/development/license-keys/plans.md +4 -2
- package/dist/docs/development/studio/01-introduction.md +60 -0
- package/dist/docs/development/studio/02-create-a-canvas.md +103 -0
- package/dist/docs/development/studio/03-set-up-persistent-storage.md +59 -0
- package/dist/docs/development/studio/04-collaborate-on-a-canvas.md +78 -0
- package/dist/docs/development/studio/05-connect-your-agent.md +102 -0
- package/dist/docs/development/studio/06-run-studio-in-production.md +54 -0
- package/dist/docs/development/studio/07-reference.md +294 -0
- package/dist/docs/development/studio/_category_.json +12 -0
- package/dist/docs/development/upgrading/v5.md +345 -0
- package/dist/docs/federation/00-overview.md +2 -8
- package/dist/docs/federation/01-first-federation.md +6 -18
- package/dist/docs/federation/explanation/how-it-works.md +0 -4
- package/dist/docs/federation/explanation/lockfile-and-cache.md +0 -4
- package/dist/docs/federation/explanation/mvp-status-and-feedback.md +0 -4
- package/dist/docs/federation/explanation/ownership-and-references.md +0 -4
- package/dist/docs/federation/how-to/configure-github-sources.md +6 -5
- package/dist/docs/federation/how-to/configure-validation-rules.md +0 -4
- package/dist/docs/federation/how-to/run-in-ci.md +2 -9
- package/dist/docs/federation/how-to/share-assets-and-components.md +0 -4
- package/dist/docs/federation/how-to/use-local-sources.md +0 -4
- package/dist/docs/federation/legacy-federation/00-introduction.md +4 -22
- package/dist/docs/federation/legacy-federation/01-installation.md +7 -58
- package/dist/docs/federation/legacy-federation/02-plugin-configuration.md +3 -21
- package/dist/docs/federation/legacy-federation/03-examples.md +3 -5
- package/dist/docs/federation/legacy-federation/04-configuration.md +3 -8
- package/dist/docs/federation/legacy-federation/05-setup-team-catalog.md +3 -6
- package/dist/docs/federation/reference/cli.md +2 -8
- package/dist/docs/federation/reference/configuration.md +0 -4
- package/dist/docs/federation/reference/diagnostic-rules.md +0 -5
- package/dist/docs/federation/reference/generated-output.md +0 -4
- package/dist/docs/federation/reference/supported-content.md +0 -4
- package/dist/docs/federation/reference/troubleshooting.md +0 -14
- package/dist/docs/plugins/02-generators.md +1 -1
- package/dist/docs/plugins/amazon-apigateway/00-intro.md +3 -28
- package/dist/docs/plugins/amazon-apigateway/01-installation.md +4 -56
- package/dist/docs/plugins/amazon-apigateway/02-plugin-configuration.md +1 -6
- package/dist/docs/plugins/apicurio/00-intro.md +2 -31
- package/dist/docs/plugins/apicurio/01-installation.md +2 -26
- package/dist/docs/plugins/apicurio/02-plugin-configuration.md +5 -13
- package/dist/docs/plugins/apicurio/03-features.md +1 -3
- package/dist/docs/plugins/asyncapi/00-intro.md +2 -28
- package/dist/docs/plugins/asyncapi/01-installation.md +3 -54
- package/dist/docs/plugins/asyncapi/02-plugin-configuration.md +2 -1
- package/dist/docs/plugins/aws-glue-registry/00-intro.md +4 -32
- package/dist/docs/plugins/aws-glue-registry/00a-installation.md +3 -53
- package/dist/docs/plugins/azure-schema-registry/00-intro.md +4 -32
- package/dist/docs/plugins/azure-schema-registry/01-installation.md +6 -33
- package/dist/docs/plugins/azure-schema-registry/02-plugin-configuration.md +1 -14
- package/dist/docs/plugins/azure-schema-registry/04-examples.md +0 -2
- package/dist/docs/plugins/backstage/00-overview.md +1 -7
- package/dist/docs/plugins/backstage/01-get-started.md +6 -15
- package/dist/docs/plugins/backstage/06-configuration.md +2 -8
- package/dist/docs/plugins/confluent-schema-registry/00-intro.md +2 -29
- package/dist/docs/plugins/confluent-schema-registry/01-installation.md +2 -26
- package/dist/docs/plugins/eventbridge/00-intro.md +0 -4
- package/dist/docs/plugins/eventbridge/00a-installation.md +1 -87
- package/dist/docs/plugins/eventbridge/03-api.md +2 -20
- package/dist/docs/plugins/github/00-intro.md +0 -31
- package/dist/docs/plugins/github/01-installation.md +2 -34
- package/dist/docs/plugins/graphql/00-intro.md +0 -30
- package/dist/docs/plugins/graphql/01-installation.md +1 -53
- package/dist/docs/plugins/graphql/02-plugin-configuration.md +0 -1
- package/dist/docs/plugins/hookdeck/01-intro.md +1 -1
- package/dist/docs/plugins/openapi/00-intro.md +0 -30
- package/dist/docs/plugins/openapi/01-installation.md +1 -57
- package/dist/docs/plugins/openapi/02-plugin-configuration.md +0 -5
- package/dist/docs/plugins/openapi/03-features.md +0 -4
- 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 +340 -249
- package/dist/eventcatalog.config.d.cts +18 -15
- package/dist/eventcatalog.config.d.ts +18 -15
- package/dist/eventcatalog.js +51 -33
- package/dist/features.cjs +36 -49
- package/dist/features.d.cts +1 -3
- package/dist/features.d.ts +1 -3
- package/dist/features.js +4 -6
- package/dist/federation/federate.cjs +37 -44
- package/dist/federation/federate.js +5 -5
- package/dist/federation/source-provider.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/htmlparser2.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +5 -1
- package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.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 +11 -4
- package/dist/utils/license-status.d.cts +3 -2
- package/dist/utils/license-status.d.ts +3 -2
- package/dist/utils/license-status.js +1 -1
- package/eventcatalog/astro.config.mjs +21 -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 +4 -1
- package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
- 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 +37 -76
- package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +122 -1
- package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
- package/eventcatalog/src/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 +21 -1
- 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 +13 -7
- 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 +123 -98
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +118 -101
- package/eventcatalog/src/features/mcp/mcp-server.ts +20 -4
- 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 +13 -10
- 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 +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 +274 -105
- 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 +65 -0
- 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 +122 -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 +1314 -385
- 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 +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 +244 -32
- package/eventcatalog/src/features/studio/identity.ts +25 -1
- package/eventcatalog/src/features/studio/limits.ts +2 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +42 -16
- package/eventcatalog/src/features/studio/pages/index.astro +596 -373
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +12 -15
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +8 -23
- package/eventcatalog/src/features/studio/server/runtime.ts +150 -46
- package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
- package/eventcatalog/src/features/studio/server/storage.ts +53 -73
- package/eventcatalog/src/features/studio/webmcp-relay/LICENSE +21 -0
- package/eventcatalog/src/features/studio/webmcp-relay/README.md +5 -0
- package/eventcatalog/src/features/studio/webmcp-relay/embed.js +2 -0
- package/eventcatalog/src/features/studio/webmcp-relay/widget.html +10 -0
- package/eventcatalog/src/features/studio/webmcp-relay/widget.js +1 -0
- package/eventcatalog/src/features/tools/catalog-tools.ts +3 -1
- package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
- package/eventcatalog/src/layouts/SettingsLayout.astro +2 -2
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +3 -2
- 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 +9 -1
- 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/flows.ts +20 -7
- package/eventcatalog/src/utils/collections/resource-owners.ts +8 -2
- package/eventcatalog/src/utils/collections/resources.ts +2 -0
- package/eventcatalog/src/utils/collections/util.ts +8 -4
- package/eventcatalog/src/utils/git.ts +11 -0
- 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/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 +13 -0
- package/package.json +11 -11
- package/dist/docs/development/ask-your-architecture/05-skills/02-installation.md +0 -60
- package/dist/docs/development/ask-your-architecture/05-skills/_category_.json +0 -11
- package/dist/docs/development/ask-your-architecture/06-slack-integration/01-introduction.md +0 -63
- package/dist/docs/development/ask-your-architecture/06-slack-integration/02-slack-app-setup.md +0 -154
- package/dist/docs/development/ask-your-architecture/06-slack-integration/03-installation.md +0 -169
- package/dist/docs/development/ask-your-architecture/06-slack-integration/04-deployment.md +0 -236
- package/dist/docs/development/ask-your-architecture/06-slack-integration/05-usage.md +0 -140
- package/dist/docs/development/ask-your-architecture/06-slack-integration/06-troubleshooting.md +0 -268
- package/dist/docs/development/ask-your-architecture/06-slack-integration/_category_.json +0 -13
- package/dist/docs/development/guides/domains/06-visualize-domains/01-domain-integration-map.md +0 -40
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +0 -3
- package/eventcatalog/src/features/studio/canvas-doc.ts +0 -409
- 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 -51
- 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/pages/new.astro +0 -24
- package/eventcatalog/src/features/studio/pages/wake.ts +0 -15
- package/eventcatalog/src/features/studio/placement.ts +0 -197
- package/eventcatalog/src/features/studio/studio.css +0 -273
- package/dist/{chunk-A2RZR3U4.js → chunk-6K7XZAYI.js} +3 -3
|
@@ -1,770 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
memo,
|
|
4
|
-
useCallback,
|
|
5
|
-
useContext,
|
|
6
|
-
useEffect,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
type PointerEvent as ReactPointerEvent,
|
|
10
|
-
type ReactNode,
|
|
11
|
-
} from 'react';
|
|
12
|
-
import { createPortal } from 'react-dom';
|
|
13
|
-
import { useInternalNode, useReactFlow, useStore, ViewportPortal, type Node, type XYPosition } from '@xyflow/react';
|
|
14
|
-
import { Bot, Check, Maximize2, ClipboardPaste, Copy, CopyPlus, MessageCircle, Pencil, RotateCcw, Trash2, X } from 'lucide-react';
|
|
15
|
-
import { getAbsolutePosition } from '../grouping';
|
|
16
|
-
import type { Author, CommentAnchor, Thread } from '../hooks/use-comments';
|
|
17
|
-
import { isWrittenOnCanvas } from '../node-types';
|
|
18
|
-
import { STATUS } from './status';
|
|
19
|
-
import Picture from './Picture';
|
|
20
|
-
|
|
21
|
-
const initials = (name: string) => name.slice(0, 2).toUpperCase();
|
|
22
|
-
|
|
23
|
-
export const timeAgo = (timestamp: number) => {
|
|
24
|
-
const seconds = Math.round((Date.now() - timestamp) / 1000);
|
|
25
|
-
if (seconds < 60) return 'just now';
|
|
26
|
-
const minutes = Math.round(seconds / 60);
|
|
27
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
28
|
-
const hours = Math.round(minutes / 60);
|
|
29
|
-
if (hours < 24) return `${hours}h ago`;
|
|
30
|
-
return new Date(timestamp).toLocaleDateString();
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** Where a pin sits on the canvas: on its node (so it moves with it), or where it was dropped */
|
|
34
|
-
export const getAnchorPosition = (anchor: CommentAnchor, nodesById: Map<string, Node>): XYPosition => {
|
|
35
|
-
const node = anchor.nodeId ? nodesById.get(anchor.nodeId) : undefined;
|
|
36
|
-
if (!node || !anchor.offset) return anchor.position;
|
|
37
|
-
// Nodes in containers are positioned relative to them
|
|
38
|
-
const position = getAbsolutePosition(node, nodesById);
|
|
39
|
-
return { x: position.x + anchor.offset.x, y: position.y + anchor.offset.y };
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** Pinned to a node, a comment keeps its offset from the node so it moves with it */
|
|
43
|
-
export const anchorAt = (position: XYPosition, node?: Node, nodesById?: Map<string, Node>): CommentAnchor => {
|
|
44
|
-
if (!node) return { position };
|
|
45
|
-
const nodePosition = getAbsolutePosition(node, nodesById ?? new Map([[node.id, node]]));
|
|
46
|
-
return { position, nodeId: node.id, offset: { x: position.x - nodePosition.x, y: position.y - nodePosition.y } };
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const DRAG_THRESHOLD_PX = 4;
|
|
50
|
-
|
|
51
|
-
const panelClass =
|
|
52
|
-
'nowheel nodrag nopan absolute z-40 w-72 rounded-xl border shadow-xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]';
|
|
53
|
-
|
|
54
|
-
/** The people in a thread, in the order they joined it (the pin shows the first few, like Figma) */
|
|
55
|
-
const participantsOf = (thread: Thread): Author[] => {
|
|
56
|
-
const seen = new Map<string, Author>([[thread.author.name, thread.author]]);
|
|
57
|
-
for (const message of thread.messages) if (!seen.has(message.author.name)) seen.set(message.author.name, message.author);
|
|
58
|
-
return [...seen.values()];
|
|
59
|
-
};
|
|
60
|
-
const MAX_PIN_AVATARS = 3;
|
|
61
|
-
/** How long the pointer rests on a pin before it opens its preview (passing over pins leaves them closed) */
|
|
62
|
-
const PREVIEW_DELAY_MS = 500;
|
|
63
|
-
const repliesLabel = (count: number) => (count === 1 ? '1 reply' : `${count} replies`);
|
|
64
|
-
|
|
65
|
-
function Avatar({ author, muted, size = 'h-6 w-6' }: { author: Author; muted?: boolean; size?: string }) {
|
|
66
|
-
return (
|
|
67
|
-
<span
|
|
68
|
-
title={author.name}
|
|
69
|
-
className={`relative flex ${size} shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white ring-2 ring-[rgb(var(--ec-card-bg))]`}
|
|
70
|
-
style={{ backgroundColor: muted ? 'rgb(var(--ec-page-text-muted))' : author.color }}
|
|
71
|
-
>
|
|
72
|
-
{author.agent ? <Bot size={13} /> : initials(author.name)}
|
|
73
|
-
<Picture src={author.picture} className={`absolute inset-0 h-full w-full ${muted ? 'opacity-50 grayscale' : ''}`} />
|
|
74
|
-
</span>
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* A comment pin, like Figma's: a speech bubble with the people in the thread, that opens into a preview of it
|
|
80
|
-
* (who started it, when, what they said and how many replies) once the pointer rests on it. Click opens the
|
|
81
|
-
* thread, dragging moves it: `onDrag` gets the screen point the pin's tip (its bottom left corner) is over,
|
|
82
|
-
* keeping where on the pin it was grabbed.
|
|
83
|
-
*/
|
|
84
|
-
function Pin({
|
|
85
|
-
author,
|
|
86
|
-
thread,
|
|
87
|
-
active,
|
|
88
|
-
selected,
|
|
89
|
-
resolved,
|
|
90
|
-
onClick,
|
|
91
|
-
onDrag,
|
|
92
|
-
}: {
|
|
93
|
-
author: Author;
|
|
94
|
-
/** The thread it's for (a new comment being written has none yet) */
|
|
95
|
-
thread?: Thread;
|
|
96
|
-
active?: boolean;
|
|
97
|
-
/** Part of what's selected on the canvas (e.g. after selecting everything) */
|
|
98
|
-
selected?: boolean;
|
|
99
|
-
resolved?: boolean;
|
|
100
|
-
onClick?: () => void;
|
|
101
|
-
onDrag?: (tip: XYPosition, phase: 'move' | 'end') => void;
|
|
102
|
-
}) {
|
|
103
|
-
const drag = useRef<{ start: XYPosition; grab: XYPosition; moved: boolean } | null>(null);
|
|
104
|
-
const [hovered, setHovered] = useState(false);
|
|
105
|
-
const hoverTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
106
|
-
useEffect(() => () => clearTimeout(hoverTimer.current), []);
|
|
107
|
-
const [pressed, setPressed] = useState(false);
|
|
108
|
-
const tipOf = (event: { clientX: number; clientY: number }) => ({
|
|
109
|
-
x: event.clientX - drag.current!.grab.x,
|
|
110
|
-
y: event.clientY - drag.current!.grab.y,
|
|
111
|
-
});
|
|
112
|
-
const people = thread ? participantsOf(thread) : [author];
|
|
113
|
-
const [first, ...rest] = thread?.messages ?? [];
|
|
114
|
-
// The preview shows while the pointer is over the pin, not while the thread is open or the pin is dragged
|
|
115
|
-
const expanded = !!thread && !!first && hovered && !active && !pressed;
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<button
|
|
119
|
-
onPointerEnter={() => {
|
|
120
|
-
clearTimeout(hoverTimer.current);
|
|
121
|
-
hoverTimer.current = setTimeout(() => setHovered(true), PREVIEW_DELAY_MS);
|
|
122
|
-
}}
|
|
123
|
-
onPointerLeave={() => {
|
|
124
|
-
clearTimeout(hoverTimer.current);
|
|
125
|
-
setHovered(false);
|
|
126
|
-
}}
|
|
127
|
-
onPointerDown={(event) => {
|
|
128
|
-
if (event.button !== 0) return;
|
|
129
|
-
// Keeps pointer events coming while the pointer is outside the pin (throws if the pointer has gone)
|
|
130
|
-
try {
|
|
131
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
132
|
-
} catch {}
|
|
133
|
-
setPressed(true);
|
|
134
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
135
|
-
drag.current = {
|
|
136
|
-
start: { x: event.clientX, y: event.clientY },
|
|
137
|
-
grab: { x: event.clientX - rect.left, y: event.clientY - rect.bottom },
|
|
138
|
-
moved: false,
|
|
139
|
-
};
|
|
140
|
-
}}
|
|
141
|
-
onPointerMove={(event) => {
|
|
142
|
-
const current = drag.current;
|
|
143
|
-
if (!current || !onDrag) return;
|
|
144
|
-
const distance = Math.hypot(event.clientX - current.start.x, event.clientY - current.start.y);
|
|
145
|
-
if (!current.moved && distance < DRAG_THRESHOLD_PX) return;
|
|
146
|
-
current.moved = true;
|
|
147
|
-
onDrag(tipOf(event), 'move');
|
|
148
|
-
}}
|
|
149
|
-
onPointerUp={(event) => {
|
|
150
|
-
const current = drag.current;
|
|
151
|
-
setPressed(false);
|
|
152
|
-
if (!current) return;
|
|
153
|
-
if (current.moved) onDrag?.(tipOf(event), 'end');
|
|
154
|
-
else onClick?.();
|
|
155
|
-
drag.current = null;
|
|
156
|
-
}}
|
|
157
|
-
onPointerCancel={() => {
|
|
158
|
-
drag.current = null;
|
|
159
|
-
setPressed(false);
|
|
160
|
-
}}
|
|
161
|
-
// Keyboard activation (pointer clicks are handled on pointer up)
|
|
162
|
-
onClick={(event) => event.detail === 0 && onClick?.()}
|
|
163
|
-
aria-label={
|
|
164
|
-
thread ? `Comment by ${thread.author.name}${rest.length ? `, ${repliesLabel(rest.length)}` : ''}` : 'New comment'
|
|
165
|
-
}
|
|
166
|
-
className={`block cursor-grab rounded-[18px] rounded-bl-none border p-1 text-left shadow-lg transition-[box-shadow,transform] active:cursor-grabbing bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] ${
|
|
167
|
-
active ? 'ring-2 ring-[rgb(var(--ec-accent))]' : ''
|
|
168
|
-
} ${expanded ? 'w-64' : ''}`}
|
|
169
|
-
// Selected: a ring in the theme's button colour (strong in light and dark themes)
|
|
170
|
-
style={selected ? { boxShadow: '0 0 0 2px rgb(var(--ec-card-bg)), 0 0 0 4px rgb(var(--ec-button-bg))' } : undefined}
|
|
171
|
-
>
|
|
172
|
-
{expanded ? (
|
|
173
|
-
<span className="block space-y-1 px-1.5 py-1">
|
|
174
|
-
<span className="flex items-center gap-2">
|
|
175
|
-
<span className="flex -space-x-0.5">
|
|
176
|
-
{people.slice(0, MAX_PIN_AVATARS).map((person) => (
|
|
177
|
-
<Avatar key={person.name} author={person} muted={resolved} size="h-5 w-5" />
|
|
178
|
-
))}
|
|
179
|
-
</span>
|
|
180
|
-
<span className="min-w-0 truncate text-xs font-semibold">{thread.author.name}</span>
|
|
181
|
-
<span className="shrink-0 text-[11px] text-[rgb(var(--ec-page-text-muted))]">{timeAgo(first.createdAt)}</span>
|
|
182
|
-
</span>
|
|
183
|
-
<span className="line-clamp-3 block whitespace-pre-wrap break-words text-xs">{first.text}</span>
|
|
184
|
-
{(rest.length > 0 || resolved) && (
|
|
185
|
-
<span className="flex items-center gap-2 text-[11px] font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
186
|
-
{rest.length > 0 && <span>{repliesLabel(rest.length)}</span>}
|
|
187
|
-
{resolved && (
|
|
188
|
-
<span className={`flex items-center gap-0.5 ${STATUS.success.text}`}>
|
|
189
|
-
<Check size={11} /> Resolved
|
|
190
|
-
</span>
|
|
191
|
-
)}
|
|
192
|
-
</span>
|
|
193
|
-
)}
|
|
194
|
-
</span>
|
|
195
|
-
) : (
|
|
196
|
-
<span className="flex -space-x-1">
|
|
197
|
-
{people.slice(0, MAX_PIN_AVATARS).map((person) => (
|
|
198
|
-
<Avatar key={person.name} author={person} muted={resolved} />
|
|
199
|
-
))}
|
|
200
|
-
{people.length > MAX_PIN_AVATARS && (
|
|
201
|
-
<span className="flex h-6 w-6 items-center justify-center rounded-full text-[10px] font-semibold ring-2 ring-[rgb(var(--ec-card-bg))] bg-[rgb(var(--ec-page-border))]">
|
|
202
|
-
+{people.length - MAX_PIN_AVATARS}
|
|
203
|
-
</span>
|
|
204
|
-
)}
|
|
205
|
-
</span>
|
|
206
|
-
)}
|
|
207
|
-
</button>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function Composer({
|
|
212
|
-
placeholder,
|
|
213
|
-
onSubmit,
|
|
214
|
-
onCancel,
|
|
215
|
-
}: {
|
|
216
|
-
placeholder: string;
|
|
217
|
-
onSubmit: (text: string) => void;
|
|
218
|
-
onCancel?: () => void;
|
|
219
|
-
}) {
|
|
220
|
-
const [text, setText] = useState('');
|
|
221
|
-
const submit = () => {
|
|
222
|
-
if (!text.trim()) return;
|
|
223
|
-
onSubmit(text.trim());
|
|
224
|
-
setText('');
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<div className="flex items-end gap-2 p-2">
|
|
229
|
-
<textarea
|
|
230
|
-
autoFocus
|
|
231
|
-
rows={1}
|
|
232
|
-
value={text}
|
|
233
|
-
placeholder={placeholder}
|
|
234
|
-
onChange={(e) => setText(e.target.value)}
|
|
235
|
-
onKeyDown={(e) => {
|
|
236
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
237
|
-
e.preventDefault();
|
|
238
|
-
submit();
|
|
239
|
-
}
|
|
240
|
-
if (e.key === 'Escape') onCancel?.();
|
|
241
|
-
}}
|
|
242
|
-
className="max-h-32 min-h-[34px] flex-1 resize-none rounded-md border px-2 py-1.5 text-sm bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
|
|
243
|
-
/>
|
|
244
|
-
<button
|
|
245
|
-
onClick={submit}
|
|
246
|
-
disabled={!text.trim()}
|
|
247
|
-
className="rounded-md px-2.5 py-1.5 text-xs font-medium disabled:opacity-40 bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]"
|
|
248
|
-
>
|
|
249
|
-
Post
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function IconButton({ title, onClick, children }: { title: string; onClick: () => void; children: ReactNode }) {
|
|
256
|
-
return (
|
|
257
|
-
<button
|
|
258
|
-
title={title}
|
|
259
|
-
onClick={onClick}
|
|
260
|
-
className="rounded p-1 text-[rgb(var(--ec-icon-color))] hover:bg-[rgb(var(--ec-page-border)/0.5)] hover:text-[rgb(var(--ec-icon-hover))]"
|
|
261
|
-
>
|
|
262
|
-
{children}
|
|
263
|
-
</button>
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Room kept between a thread's box and the edges of the canvas */
|
|
268
|
-
const PANEL_MARGIN_PX = 12;
|
|
269
|
-
/** A thread's box is at most this tall: longer threads scroll (or open bigger, in a dialog) */
|
|
270
|
-
const PANEL_MAX_HEIGHT_PX = 360;
|
|
271
|
-
/** What drags a thread's box around (its header) */
|
|
272
|
-
const DragPanelContext = createContext<((event: ReactPointerEvent<HTMLElement>) => void) | null>(null);
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* The box a thread (or a new comment) opens in, beside its pin. It opens moved up as far as needed to be fully
|
|
276
|
-
* on the canvas, and can be dragged around by its header to see more of the canvas or of a long thread. Where it's
|
|
277
|
-
* been moved is only here, and goes when it closes.
|
|
278
|
-
*/
|
|
279
|
-
function FloatingPanel({ children }: { children: ReactNode }) {
|
|
280
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
281
|
-
const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 });
|
|
282
|
-
const offsetRef = useRef(offset);
|
|
283
|
-
offsetRef.current = offset;
|
|
284
|
-
|
|
285
|
-
// Opened low on the canvas, or grown past the bottom of it (replies): moved up so its bottom (and the reply box)
|
|
286
|
-
// can be seen
|
|
287
|
-
useEffect(() => {
|
|
288
|
-
const element = ref.current;
|
|
289
|
-
if (!element) return;
|
|
290
|
-
const fit = () => {
|
|
291
|
-
const rect = element.getBoundingClientRect();
|
|
292
|
-
const canvas = element.closest('.react-flow')?.getBoundingClientRect() ?? { top: 0, bottom: window.innerHeight };
|
|
293
|
-
const up = Math.min(rect.bottom - (canvas.bottom - PANEL_MARGIN_PX), rect.top - (canvas.top + PANEL_MARGIN_PX));
|
|
294
|
-
if (up > 0) setOffset((current) => ({ x: current.x, y: current.y - up }));
|
|
295
|
-
};
|
|
296
|
-
const observer = new ResizeObserver(fit);
|
|
297
|
-
observer.observe(element);
|
|
298
|
-
return () => observer.disconnect();
|
|
299
|
-
}, []);
|
|
300
|
-
|
|
301
|
-
// Pin and box are counter-scaled with the zoom, so the box moves by as much as the pointer does on screen
|
|
302
|
-
const startDrag = useCallback((event: ReactPointerEvent<HTMLElement>) => {
|
|
303
|
-
if (event.button !== 0 || (event.target as Element).closest('button')) return;
|
|
304
|
-
event.preventDefault();
|
|
305
|
-
const element = event.currentTarget;
|
|
306
|
-
element.setPointerCapture(event.pointerId);
|
|
307
|
-
const start = { x: event.clientX, y: event.clientY };
|
|
308
|
-
const from = offsetRef.current;
|
|
309
|
-
const move = (moveEvent: PointerEvent) =>
|
|
310
|
-
setOffset({ x: from.x + moveEvent.clientX - start.x, y: from.y + moveEvent.clientY - start.y });
|
|
311
|
-
const end = () => {
|
|
312
|
-
element.removeEventListener('pointermove', move);
|
|
313
|
-
element.removeEventListener('pointerup', end);
|
|
314
|
-
element.removeEventListener('pointercancel', end);
|
|
315
|
-
};
|
|
316
|
-
element.addEventListener('pointermove', move);
|
|
317
|
-
element.addEventListener('pointerup', end);
|
|
318
|
-
element.addEventListener('pointercancel', end);
|
|
319
|
-
}, []);
|
|
320
|
-
|
|
321
|
-
return (
|
|
322
|
-
<div
|
|
323
|
-
ref={ref}
|
|
324
|
-
className={`${panelClass} flex flex-col`}
|
|
325
|
-
style={{
|
|
326
|
-
left: `calc(100% + 8px + ${offset.x}px)`,
|
|
327
|
-
top: offset.y,
|
|
328
|
-
maxHeight: PANEL_MAX_HEIGHT_PX,
|
|
329
|
-
}}
|
|
330
|
-
>
|
|
331
|
-
<DragPanelContext.Provider value={startDrag}>{children}</DragPanelContext.Provider>
|
|
332
|
-
</div>
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function ThreadPopover({
|
|
337
|
-
thread,
|
|
338
|
-
onReply,
|
|
339
|
-
onResolve,
|
|
340
|
-
onDelete,
|
|
341
|
-
onClose,
|
|
342
|
-
onExpand,
|
|
343
|
-
}: {
|
|
344
|
-
thread: Thread;
|
|
345
|
-
onReply: (text: string) => void;
|
|
346
|
-
onResolve: (resolved: boolean) => void;
|
|
347
|
-
onDelete: () => void;
|
|
348
|
-
onClose: () => void;
|
|
349
|
-
/** Open it bigger, in a dialog (not there when it's already in one) */
|
|
350
|
-
onExpand?: () => void;
|
|
351
|
-
}) {
|
|
352
|
-
const listRef = useRef<HTMLDivElement>(null);
|
|
353
|
-
const startDrag = useContext(DragPanelContext);
|
|
354
|
-
useEffect(() => {
|
|
355
|
-
listRef.current?.scrollTo({ top: listRef.current.scrollHeight });
|
|
356
|
-
}, [thread.messages.length]);
|
|
357
|
-
|
|
358
|
-
return (
|
|
359
|
-
<>
|
|
360
|
-
<div
|
|
361
|
-
onPointerDown={startDrag ?? undefined}
|
|
362
|
-
title={startDrag ? 'Drag to move' : undefined}
|
|
363
|
-
className={`flex shrink-0 select-none items-center justify-between border-b px-3 py-2 border-[rgb(var(--ec-page-border))] ${
|
|
364
|
-
startDrag ? 'cursor-move' : ''
|
|
365
|
-
}`}
|
|
366
|
-
>
|
|
367
|
-
<span className="text-xs font-semibold">{thread.resolved ? 'Resolved thread' : 'Thread'}</span>
|
|
368
|
-
<span className="flex items-center gap-0.5">
|
|
369
|
-
<IconButton title={thread.resolved ? 'Reopen' : 'Resolve'} onClick={() => onResolve(!thread.resolved)}>
|
|
370
|
-
{thread.resolved ? <RotateCcw size={14} /> : <Check size={14} />}
|
|
371
|
-
</IconButton>
|
|
372
|
-
<IconButton title="Delete thread" onClick={onDelete}>
|
|
373
|
-
<Trash2 size={14} />
|
|
374
|
-
</IconButton>
|
|
375
|
-
{onExpand && (
|
|
376
|
-
<IconButton title="Open bigger" onClick={onExpand}>
|
|
377
|
-
<Maximize2 size={14} />
|
|
378
|
-
</IconButton>
|
|
379
|
-
)}
|
|
380
|
-
<IconButton title="Close" onClick={onClose}>
|
|
381
|
-
<X size={14} />
|
|
382
|
-
</IconButton>
|
|
383
|
-
</span>
|
|
384
|
-
</div>
|
|
385
|
-
<div ref={listRef} className="min-h-0 flex-1 space-y-3 overflow-y-auto px-3 py-3">
|
|
386
|
-
{thread.messages.map((message) => (
|
|
387
|
-
<div key={message.id} className="flex gap-2">
|
|
388
|
-
<span
|
|
389
|
-
className="relative flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
|
390
|
-
style={{ backgroundColor: message.author.color }}
|
|
391
|
-
>
|
|
392
|
-
{message.author.agent ? <Bot size={13} /> : initials(message.author.name)}
|
|
393
|
-
<Picture src={message.author.picture} />
|
|
394
|
-
</span>
|
|
395
|
-
<div className="min-w-0">
|
|
396
|
-
<p className="text-xs">
|
|
397
|
-
<span className="font-semibold">{message.author.name}</span>{' '}
|
|
398
|
-
<span className="text-[rgb(var(--ec-page-text-muted))]">{timeAgo(message.createdAt)}</span>
|
|
399
|
-
</p>
|
|
400
|
-
<p className="whitespace-pre-wrap break-words text-sm">{message.text}</p>
|
|
401
|
-
</div>
|
|
402
|
-
</div>
|
|
403
|
-
))}
|
|
404
|
-
</div>
|
|
405
|
-
<div className="shrink-0 border-t border-[rgb(var(--ec-page-border))]">
|
|
406
|
-
<Composer placeholder="Reply" onSubmit={onReply} onCancel={onClose} />
|
|
407
|
-
</div>
|
|
408
|
-
</>
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/** A thread opened bigger, in a dialog over the page (for long threads) */
|
|
413
|
-
function ThreadDialog({
|
|
414
|
-
thread,
|
|
415
|
-
onReply,
|
|
416
|
-
onResolve,
|
|
417
|
-
onDelete,
|
|
418
|
-
onShrink,
|
|
419
|
-
onClose,
|
|
420
|
-
}: {
|
|
421
|
-
thread: Thread;
|
|
422
|
-
onReply: (text: string) => void;
|
|
423
|
-
onResolve: (resolved: boolean) => void;
|
|
424
|
-
onDelete: () => void;
|
|
425
|
-
/** Back to the box beside its pin */
|
|
426
|
-
onShrink: () => void;
|
|
427
|
-
onClose: () => void;
|
|
428
|
-
}) {
|
|
429
|
-
useEffect(() => {
|
|
430
|
-
const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && onShrink();
|
|
431
|
-
window.addEventListener('keydown', onKeyDown);
|
|
432
|
-
return () => window.removeEventListener('keydown', onKeyDown);
|
|
433
|
-
}, [onShrink]);
|
|
434
|
-
return (
|
|
435
|
-
<div
|
|
436
|
-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
|
|
437
|
-
// It's in the canvas's React tree (a portal), so its events would reach the canvas: they stop here (clicks in
|
|
438
|
-
// the dialog stop at it)
|
|
439
|
-
onClick={(event) => {
|
|
440
|
-
event.stopPropagation();
|
|
441
|
-
onShrink();
|
|
442
|
-
}}
|
|
443
|
-
onPointerDown={(event) => event.stopPropagation()}
|
|
444
|
-
onMouseDown={(event) => event.stopPropagation()}
|
|
445
|
-
onDoubleClick={(event) => event.stopPropagation()}
|
|
446
|
-
onContextMenu={(event) => event.stopPropagation()}
|
|
447
|
-
onWheel={(event) => event.stopPropagation()}
|
|
448
|
-
>
|
|
449
|
-
<div
|
|
450
|
-
role="dialog"
|
|
451
|
-
aria-label="Comment thread"
|
|
452
|
-
onClick={(event) => event.stopPropagation()}
|
|
453
|
-
className="flex max-h-[80vh] w-full max-w-xl flex-col rounded-xl border shadow-2xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
|
|
454
|
-
>
|
|
455
|
-
<ThreadPopover thread={thread} onReply={onReply} onResolve={onResolve} onDelete={onDelete} onClose={onClose} />
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/** Where a pin goes on the canvas, following only the node it's on (if it's on one), counter-scaled */
|
|
462
|
-
function PinAt({ anchor, zoom, zIndex, children }: { anchor: CommentAnchor; zoom: number; zIndex: number; children: ReactNode }) {
|
|
463
|
-
const node = useInternalNode(anchor.nodeId ?? '');
|
|
464
|
-
const origin = anchor.nodeId && anchor.offset ? node?.internals.positionAbsolute : undefined;
|
|
465
|
-
const { x, y } = origin && anchor.offset ? { x: origin.x + anchor.offset.x, y: origin.y + anchor.offset.y } : anchor.position;
|
|
466
|
-
return (
|
|
467
|
-
<div className="absolute" style={{ left: x, top: y, transform: `scale(${1 / zoom})`, transformOrigin: '0 0', zIndex }}>
|
|
468
|
-
{/* The pin's tip (bottom left corner) sits on the anchor point, and it grows up and right as it opens */}
|
|
469
|
-
<div className="nopan nodrag nowheel pointer-events-auto absolute bottom-0 left-0">{children}</div>
|
|
470
|
-
</div>
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Comment pins (and the open thread) on the canvas, in its own coordinates (React Flow's viewport portal) so
|
|
476
|
-
* they move with it without re-rendering, counter-scaled to stay the same size on screen. Each pin follows
|
|
477
|
-
* only its own node, so other nodes moving don't re-render them.
|
|
478
|
-
*/
|
|
479
|
-
export const CommentLayer = memo(function CommentLayer({
|
|
480
|
-
threads,
|
|
481
|
-
author,
|
|
482
|
-
openThreadId,
|
|
483
|
-
draft,
|
|
484
|
-
showResolved,
|
|
485
|
-
onOpen,
|
|
486
|
-
onCreate,
|
|
487
|
-
onCancelDraft,
|
|
488
|
-
onReply,
|
|
489
|
-
onResolve,
|
|
490
|
-
onDelete,
|
|
491
|
-
onMove,
|
|
492
|
-
onMoveDraft,
|
|
493
|
-
selectedIds,
|
|
494
|
-
selectionOffset,
|
|
495
|
-
}: {
|
|
496
|
-
threads: Thread[];
|
|
497
|
-
author: Author;
|
|
498
|
-
openThreadId: string | null;
|
|
499
|
-
/** Threads that are part of what's selected */
|
|
500
|
-
selectedIds: ReadonlySet<string>;
|
|
501
|
-
/** How far what's selected has been dragged: selected pins not on a node move with it */
|
|
502
|
-
selectionOffset: XYPosition | null;
|
|
503
|
-
draft: CommentAnchor | null;
|
|
504
|
-
showResolved: boolean;
|
|
505
|
-
onOpen: (threadId: string | null) => void;
|
|
506
|
-
onCreate: (text: string) => void;
|
|
507
|
-
onCancelDraft: () => void;
|
|
508
|
-
onReply: (threadId: string, text: string) => void;
|
|
509
|
-
onResolve: (threadId: string, resolved: boolean) => void;
|
|
510
|
-
onDelete: (threadId: string) => void;
|
|
511
|
-
onMove: (threadId: string, anchor: CommentAnchor) => void;
|
|
512
|
-
onMoveDraft: (anchor: CommentAnchor) => void;
|
|
513
|
-
}) {
|
|
514
|
-
const zoom = useStore((state) => state.transform[2]);
|
|
515
|
-
const { screenToFlowPosition, getInternalNode } = useReactFlow();
|
|
516
|
-
// A pin being dragged stays here until it's dropped, then moves for everyone
|
|
517
|
-
const [moving, setMoving] = useState<{ threadId: string; anchor: CommentAnchor } | null>(null);
|
|
518
|
-
// The open thread, shown bigger in a dialog
|
|
519
|
-
const [expandedId, setExpandedId] = useState<string | null>(null);
|
|
520
|
-
const expanded = expandedId && expandedId === openThreadId ? threads.find((thread) => thread.id === expandedId) : undefined;
|
|
521
|
-
|
|
522
|
-
// While dragging, a pin follows the pointer. Dropped on a node it attaches to it (and moves with it),
|
|
523
|
-
// anywhere else it stays on the canvas.
|
|
524
|
-
const nodeUnder = (point: XYPosition) => {
|
|
525
|
-
const element = document.elementsFromPoint(point.x, point.y).find((el) => el.closest('.react-flow__node'));
|
|
526
|
-
const id = element?.closest('.react-flow__node')?.getAttribute('data-id');
|
|
527
|
-
return id ? getInternalNode(id) : undefined;
|
|
528
|
-
};
|
|
529
|
-
const anchorFor = (tip: XYPosition, phase: 'move' | 'end'): CommentAnchor => {
|
|
530
|
-
const position = screenToFlowPosition(tip);
|
|
531
|
-
const node = phase === 'end' ? nodeUnder(tip) : undefined;
|
|
532
|
-
if (!node) return { position };
|
|
533
|
-
const origin = node.internals.positionAbsolute;
|
|
534
|
-
return { position, nodeId: node.id, offset: { x: position.x - origin.x, y: position.y - origin.y } };
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const visible = threads.filter((thread) => showResolved || !thread.resolved || thread.id === openThreadId);
|
|
538
|
-
|
|
539
|
-
return (
|
|
540
|
-
<ViewportPortal>
|
|
541
|
-
{/* Above nodes (selected ones are raised to 1000) and other people's pointers */}
|
|
542
|
-
<div className="absolute left-0 top-0" style={{ zIndex: 2001 }}>
|
|
543
|
-
{visible.map((thread) => {
|
|
544
|
-
const isOpen = thread.id === openThreadId;
|
|
545
|
-
return (
|
|
546
|
-
<PinAt
|
|
547
|
-
key={thread.id}
|
|
548
|
-
anchor={
|
|
549
|
-
moving?.threadId === thread.id
|
|
550
|
-
? moving.anchor
|
|
551
|
-
: selectionOffset && !thread.nodeId && selectedIds.has(thread.id)
|
|
552
|
-
? { position: { x: thread.position.x + selectionOffset.x, y: thread.position.y + selectionOffset.y } }
|
|
553
|
-
: thread
|
|
554
|
-
}
|
|
555
|
-
zoom={zoom}
|
|
556
|
-
zIndex={isOpen ? 2 : 1}
|
|
557
|
-
>
|
|
558
|
-
<Pin
|
|
559
|
-
author={thread.author}
|
|
560
|
-
thread={thread}
|
|
561
|
-
active={isOpen}
|
|
562
|
-
selected={selectedIds.has(thread.id)}
|
|
563
|
-
resolved={thread.resolved}
|
|
564
|
-
onClick={() => onOpen(isOpen ? null : thread.id)}
|
|
565
|
-
onDrag={(tip, phase) => {
|
|
566
|
-
if (phase === 'move') return setMoving({ threadId: thread.id, anchor: anchorFor(tip, phase) });
|
|
567
|
-
onMove(thread.id, anchorFor(tip, phase));
|
|
568
|
-
setMoving(null);
|
|
569
|
-
}}
|
|
570
|
-
/>
|
|
571
|
-
{/* (not while it's open bigger, in the dialog) */}
|
|
572
|
-
{isOpen && expanded?.id !== thread.id && (
|
|
573
|
-
<FloatingPanel>
|
|
574
|
-
<ThreadPopover
|
|
575
|
-
thread={thread}
|
|
576
|
-
onReply={(text) => onReply(thread.id, text)}
|
|
577
|
-
onResolve={(resolved) => onResolve(thread.id, resolved)}
|
|
578
|
-
onDelete={() => onDelete(thread.id)}
|
|
579
|
-
onClose={() => onOpen(null)}
|
|
580
|
-
onExpand={() => setExpandedId(thread.id)}
|
|
581
|
-
/>
|
|
582
|
-
</FloatingPanel>
|
|
583
|
-
)}
|
|
584
|
-
</PinAt>
|
|
585
|
-
);
|
|
586
|
-
})}
|
|
587
|
-
|
|
588
|
-
{expanded &&
|
|
589
|
-
createPortal(
|
|
590
|
-
<ThreadDialog
|
|
591
|
-
thread={expanded}
|
|
592
|
-
onReply={(text) => onReply(expanded.id, text)}
|
|
593
|
-
onResolve={(resolved) => onResolve(expanded.id, resolved)}
|
|
594
|
-
onDelete={() => {
|
|
595
|
-
setExpandedId(null);
|
|
596
|
-
onDelete(expanded.id);
|
|
597
|
-
}}
|
|
598
|
-
onShrink={() => setExpandedId(null)}
|
|
599
|
-
onClose={() => {
|
|
600
|
-
setExpandedId(null);
|
|
601
|
-
onOpen(null);
|
|
602
|
-
}}
|
|
603
|
-
/>,
|
|
604
|
-
document.body
|
|
605
|
-
)}
|
|
606
|
-
|
|
607
|
-
{draft && (
|
|
608
|
-
<PinAt anchor={draft} zoom={zoom} zIndex={3}>
|
|
609
|
-
<Pin author={author} active onDrag={(tip, phase) => onMoveDraft(anchorFor(tip, phase))} />
|
|
610
|
-
<FloatingPanel>
|
|
611
|
-
<Composer placeholder="Add a comment" onSubmit={onCreate} onCancel={onCancelDraft} />
|
|
612
|
-
</FloatingPanel>
|
|
613
|
-
</PinAt>
|
|
614
|
-
)}
|
|
615
|
-
</div>
|
|
616
|
-
</ViewportPortal>
|
|
617
|
-
);
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
export type ContextMenuState = { x: number; y: number; flowPosition: XYPosition; node?: Node } | null;
|
|
621
|
-
|
|
622
|
-
export const CanvasContextMenu = memo(function CanvasContextMenu({
|
|
623
|
-
menu,
|
|
624
|
-
onAddComment,
|
|
625
|
-
onEditDetails,
|
|
626
|
-
onCopy,
|
|
627
|
-
onDuplicate,
|
|
628
|
-
onPaste,
|
|
629
|
-
onDeleteNode,
|
|
630
|
-
onClose,
|
|
631
|
-
}: {
|
|
632
|
-
menu: ContextMenuState;
|
|
633
|
-
onAddComment: () => void;
|
|
634
|
-
/** Open the node's details in the panel (not for notes, which are edited on the canvas) */
|
|
635
|
-
onEditDetails: () => void;
|
|
636
|
-
/** Copy the node (or the selection it's in) */
|
|
637
|
-
onCopy: () => void;
|
|
638
|
-
onDuplicate: () => void;
|
|
639
|
-
/** Paste what was last copied where the menu was opened, if anything was */
|
|
640
|
-
onPaste?: () => void;
|
|
641
|
-
/** Delete the node (or the selection it's in) */
|
|
642
|
-
onDeleteNode: () => void;
|
|
643
|
-
onClose: () => void;
|
|
644
|
-
}) {
|
|
645
|
-
useEffect(() => {
|
|
646
|
-
if (!menu) return;
|
|
647
|
-
const close = (event: Event) => {
|
|
648
|
-
if (event instanceof KeyboardEvent && event.key !== 'Escape') return;
|
|
649
|
-
onClose();
|
|
650
|
-
};
|
|
651
|
-
window.addEventListener('pointerdown', close);
|
|
652
|
-
window.addEventListener('keydown', close);
|
|
653
|
-
window.addEventListener('wheel', close);
|
|
654
|
-
return () => {
|
|
655
|
-
window.removeEventListener('pointerdown', close);
|
|
656
|
-
window.removeEventListener('keydown', close);
|
|
657
|
-
window.removeEventListener('wheel', close);
|
|
658
|
-
};
|
|
659
|
-
}, [menu, onClose]);
|
|
660
|
-
|
|
661
|
-
if (!menu) return null;
|
|
662
|
-
|
|
663
|
-
const itemClass =
|
|
664
|
-
'flex w-full items-center gap-2 rounded px-2.5 py-1.5 text-left text-xs hover:bg-[rgb(var(--ec-page-border)/0.5)]';
|
|
665
|
-
const shortcutClass = 'ml-auto text-[rgb(var(--ec-page-text-muted))]';
|
|
666
|
-
|
|
667
|
-
return (
|
|
668
|
-
<div
|
|
669
|
-
// Keep the window pointerdown listener from closing the menu before a click lands
|
|
670
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
671
|
-
className="fixed z-50 w-44 rounded-lg border p-1 shadow-xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
|
|
672
|
-
style={{ left: menu.x, top: menu.y }}
|
|
673
|
-
>
|
|
674
|
-
<button className={itemClass} onClick={onAddComment}>
|
|
675
|
-
<MessageCircle size={14} />
|
|
676
|
-
Add comment
|
|
677
|
-
<span className={shortcutClass}>C</span>
|
|
678
|
-
</button>
|
|
679
|
-
{menu.node && (
|
|
680
|
-
<>
|
|
681
|
-
{!isWrittenOnCanvas(menu.node.type) && (
|
|
682
|
-
<button className={itemClass} onClick={onEditDetails}>
|
|
683
|
-
<Pencil size={14} />
|
|
684
|
-
Edit details
|
|
685
|
-
</button>
|
|
686
|
-
)}
|
|
687
|
-
<button className={itemClass} onClick={onCopy}>
|
|
688
|
-
<Copy size={14} />
|
|
689
|
-
Copy
|
|
690
|
-
<span className={shortcutClass}>⌘C</span>
|
|
691
|
-
</button>
|
|
692
|
-
<button className={itemClass} onClick={onDuplicate}>
|
|
693
|
-
<CopyPlus size={14} />
|
|
694
|
-
Duplicate
|
|
695
|
-
<span className={shortcutClass}>⌘D</span>
|
|
696
|
-
</button>
|
|
697
|
-
<div className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
698
|
-
<button className={`${itemClass} ${STATUS.danger.text}`} onClick={onDeleteNode}>
|
|
699
|
-
<Trash2 size={14} />
|
|
700
|
-
Delete
|
|
701
|
-
<span className={shortcutClass}>⌫</span>
|
|
702
|
-
</button>
|
|
703
|
-
</>
|
|
704
|
-
)}
|
|
705
|
-
{!menu.node && onPaste && (
|
|
706
|
-
<button className={itemClass} onClick={onPaste}>
|
|
707
|
-
<ClipboardPaste size={14} />
|
|
708
|
-
Paste here
|
|
709
|
-
<span className={shortcutClass}>⌘V</span>
|
|
710
|
-
</button>
|
|
711
|
-
)}
|
|
712
|
-
</div>
|
|
713
|
-
);
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
/** All threads, for the left panel. Clicking one opens it on the canvas. */
|
|
717
|
-
export function CommentList({
|
|
718
|
-
threads,
|
|
719
|
-
showResolved,
|
|
720
|
-
onToggleResolved,
|
|
721
|
-
onOpen,
|
|
722
|
-
}: {
|
|
723
|
-
threads: Thread[];
|
|
724
|
-
showResolved: boolean;
|
|
725
|
-
onToggleResolved: () => void;
|
|
726
|
-
onOpen: (thread: Thread) => void;
|
|
727
|
-
}) {
|
|
728
|
-
const shown = threads.filter((thread) => showResolved || !thread.resolved).reverse();
|
|
729
|
-
|
|
730
|
-
return (
|
|
731
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
732
|
-
<label className="mx-3 mb-2 flex items-center gap-2 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
733
|
-
<input type="checkbox" checked={showResolved} onChange={onToggleResolved} />
|
|
734
|
-
Show resolved
|
|
735
|
-
</label>
|
|
736
|
-
<div className="min-h-0 flex-1 space-y-1.5 overflow-y-auto px-3 pb-3">
|
|
737
|
-
{shown.length === 0 && (
|
|
738
|
-
<p className="py-6 text-center text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
739
|
-
No comments yet. Right click the canvas, or press C, to add one.
|
|
740
|
-
</p>
|
|
741
|
-
)}
|
|
742
|
-
{shown.map((thread) => {
|
|
743
|
-
const [first, ...replies] = thread.messages;
|
|
744
|
-
return (
|
|
745
|
-
<button
|
|
746
|
-
key={thread.id}
|
|
747
|
-
onClick={() => onOpen(thread)}
|
|
748
|
-
className={`w-full space-y-1 rounded-md border px-2.5 py-2 text-left bg-[rgb(var(--ec-page-bg))] border-[rgb(var(--ec-page-border))] hover:border-[rgb(var(--ec-accent))] ${
|
|
749
|
-
thread.resolved ? 'opacity-60' : ''
|
|
750
|
-
}`}
|
|
751
|
-
>
|
|
752
|
-
<p className="flex items-center gap-1.5 text-xs">
|
|
753
|
-
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: thread.author.color }} />
|
|
754
|
-
<span className="font-semibold">{thread.author.name}</span>
|
|
755
|
-
<span className="text-[rgb(var(--ec-page-text-muted))]">{timeAgo(thread.createdAt)}</span>
|
|
756
|
-
{thread.resolved && <Check size={12} className={`ml-auto ${STATUS.success.text}`} />}
|
|
757
|
-
</p>
|
|
758
|
-
<p className="line-clamp-2 text-xs">{first?.text}</p>
|
|
759
|
-
{replies.length > 0 && (
|
|
760
|
-
<p className="text-[11px] text-[rgb(var(--ec-page-text-muted))]">
|
|
761
|
-
{replies.length} {replies.length === 1 ? 'reply' : 'replies'}
|
|
762
|
-
</p>
|
|
763
|
-
)}
|
|
764
|
-
</button>
|
|
765
|
-
);
|
|
766
|
-
})}
|
|
767
|
-
</div>
|
|
768
|
-
</div>
|
|
769
|
-
);
|
|
770
|
-
}
|