@eventcatalog/core 5.0.0-beta.8 → 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 +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 +157 -76
- 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-AWOO63ME.js → chunk-AD7CL772.js} +11 -4
- package/dist/chunk-CA6WCQ2T.js +54 -0
- package/dist/chunk-CPW6G62V.js +17 -0
- package/dist/{chunk-PU25TOOJ.js → chunk-JZN4RSMF.js} +1 -1
- package/dist/{chunk-4W55ZOOS.js → chunk-NEAVAFSJ.js} +66 -35
- package/dist/{chunk-C6T2AAFR.js → chunk-NIVST4WY.js} +9 -3
- package/dist/{chunk-ENAG6UH7.js → chunk-NKNOXQ62.js} +1 -6
- package/dist/{chunk-AYNZXUB5.js → chunk-OX6TSVHY.js} +5 -5
- package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
- package/dist/{chunk-RDIZJ7UT.js → chunk-RE7UTPWH.js} +1 -1
- package/dist/chunk-S5GWAUG5.js +29 -0
- package/dist/{chunk-T4G3VVZ4.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 +408 -281
- package/dist/eventcatalog.config.d.cts +26 -15
- package/dist/eventcatalog.config.d.ts +26 -15
- package/dist/eventcatalog.js +46 -27
- 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,10 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { ChevronDown, ChevronLeft, ChevronRight, Pencil, Plus, Search } from 'lucide-react';
|
|
3
|
+
import { ChevronDown, ChevronLeft, ChevronRight, Copy, EllipsisVertical, Pencil, Plus, Search, Trash2 } from 'lucide-react';
|
|
4
4
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
5
|
+
import StudioNotice from '../components/StudioNotice.astro';
|
|
5
6
|
import { buildUrl } from '@utils/url-builder';
|
|
6
|
-
import { CANVAS_STATUSES, type CanvasStatus } from '
|
|
7
|
-
import {
|
|
7
|
+
import { CANVAS_STATUSES, CANVAS_STATUS_LOOK, STATUS, type CanvasStatus } from '@eventcatalog/visualiser/studio';
|
|
8
|
+
import { MAX_TITLE_LENGTH } from '../limits';
|
|
9
|
+
import { MAX_NAME_LENGTH } from '../identity';
|
|
10
|
+
import { CANVASES_API_PATH } from '../api/canvases-api';
|
|
8
11
|
import { startStudio, type CanvasSummary } from '../server/runtime';
|
|
9
12
|
import { getSignedInUser, isSignedIn } from '../server/sign-in';
|
|
10
13
|
|
|
@@ -12,7 +15,12 @@ export const prerender = false;
|
|
|
12
15
|
|
|
13
16
|
// The canvases on this EventCatalog, listed like the catalog's other tables (filters are in the address, so they can
|
|
14
17
|
// be shared and work without JavaScript)
|
|
15
|
-
|
|
18
|
+
// Studio can't start when its storage can't be used (e.g. the database was updated by a newer EventCatalog): this
|
|
19
|
+
// page says why instead of listing anything
|
|
20
|
+
const started = await startStudio({ isSignedIn }).catch((error: Error) => error);
|
|
21
|
+
const unavailable = started instanceof Error ? started.message : undefined;
|
|
22
|
+
if (unavailable) Astro.response.status = 503;
|
|
23
|
+
const runtime = started instanceof Error ? undefined : started;
|
|
16
24
|
// Signed in (SSO): you're shown by who you signed in as, and asked for nothing
|
|
17
25
|
const signedInAs = getSignedInUser(Astro.locals);
|
|
18
26
|
const storage = runtime?.storage ?? { type: 'memory' as const };
|
|
@@ -22,13 +30,8 @@ const statusParam = Astro.url.searchParams.get('status');
|
|
|
22
30
|
const status = CANVAS_STATUSES.includes(statusParam as CanvasStatus) ? (statusParam as CanvasStatus) : undefined;
|
|
23
31
|
const search = Astro.url.searchParams.get('q')?.trim() ?? '';
|
|
24
32
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
canvas.nodeCount === 0 && canvas.openComments === 0 && !canvas.title && canvas.people.length === 0;
|
|
28
|
-
const canvases = (runtime?.listCanvases({ withPreview: true }) ?? [])
|
|
29
|
-
.filter((canvas) => !isBlank(canvas))
|
|
30
|
-
// The ones changed most recently first
|
|
31
|
-
.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
|
|
33
|
+
// The ones changed most recently first (canvases started and left empty aren't listed)
|
|
34
|
+
const canvases = runtime?.listCanvases({ withPreview: true }) ?? [];
|
|
32
35
|
const counts = Object.fromEntries(
|
|
33
36
|
CANVAS_STATUSES.map((value) => [value, canvases.filter((canvas) => canvas.status === value).length])
|
|
34
37
|
) as Record<CanvasStatus, number>;
|
|
@@ -67,6 +70,9 @@ const ago = (time: number) => {
|
|
|
67
70
|
if (Math.abs(days) < 30) return relativeTime.format(days, 'day');
|
|
68
71
|
return new Date(time).toLocaleDateString('en', { day: 'numeric', month: 'short', year: 'numeric' });
|
|
69
72
|
};
|
|
73
|
+
const canvasesApiUrl = buildUrl(CANVASES_API_PATH, true);
|
|
74
|
+
// Opens the New canvas dialog (also from a canvas's New canvas, or a link)
|
|
75
|
+
const newCanvasUrl = `${buildUrl('/studio')}?new=1`;
|
|
70
76
|
const plural = (count: number, noun: string) => `${count} ${noun}${count === 1 ? '' : 's'}`;
|
|
71
77
|
|
|
72
78
|
// Node colours in previews, like the visualiser's (by kind of node), from the theme's badge colours
|
|
@@ -95,384 +101,474 @@ const strokeOf = (preview: NonNullable<CanvasSummary['preview']>, ratio: number)
|
|
|
95
101
|
Math.max(preview.width, preview.height) / ratio;
|
|
96
102
|
---
|
|
97
103
|
|
|
98
|
-
<VerticalSideBarLayout title="Studio
|
|
99
|
-
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<div
|
|
104
|
+
<VerticalSideBarLayout title="Studio" showNestedSideBar={false}>
|
|
105
|
+
{
|
|
106
|
+
unavailable ? (
|
|
107
|
+
<main
|
|
108
|
+
class="bg-[rgb(var(--ec-page-bg))]"
|
|
109
|
+
style="margin-left: calc(var(--ec-app-content-padding-left, 5rem) * -1); margin-right: calc(var(--ec-app-content-padding-right, 5rem) * -1); height: calc(100dvh - 60px);"
|
|
110
|
+
>
|
|
111
|
+
<StudioNotice title="Studio isn't available" message={unavailable} />
|
|
112
|
+
</main>
|
|
113
|
+
) : (
|
|
114
|
+
<Fragment>
|
|
115
|
+
{/* The page's height, like Discover: the list scrolls between its header and its pages */}
|
|
116
|
+
<main
|
|
117
|
+
class="ml-0 flex flex-col bg-[rgb(var(--ec-page-bg))]"
|
|
118
|
+
style="margin-left: calc(var(--ec-app-content-padding-left, 5rem) * -1); margin-right: calc(var(--ec-app-content-padding-right, 5rem) * -1); height: calc(100dvh - 60px);"
|
|
119
|
+
>
|
|
120
|
+
<div class="flex flex-shrink-0 flex-col gap-4 px-6 py-5 md:flex-row md:items-end md:justify-between">
|
|
121
|
+
<div class="min-w-0">
|
|
122
|
+
<h1 class="text-2xl font-semibold text-[rgb(var(--ec-page-text))] md:text-4xl">Studio ({canvases.length})</h1>
|
|
123
|
+
<p class="max-w-3xl pt-2 text-base font-light text-[rgb(var(--ec-page-text-muted))]">
|
|
124
|
+
Design changes to your architecture on live canvases, with your team and AI agents.
|
|
125
|
+
</p>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="flex shrink-0 flex-col items-start gap-3 md:items-end">
|
|
128
|
+
{/* You, by the name others see on canvases: who you signed in as, or the name kept in this browser (shown by
|
|
129
|
+
the script; its space is kept so nothing moves) */}
|
|
130
|
+
{signedInAs ? (
|
|
131
|
+
// Signed in: you're shown in the app's header already (still read by the script, for New canvas)
|
|
132
|
+
<div data-you data-signed-in-as={signedInAs.name} hidden />
|
|
133
|
+
) : (
|
|
134
|
+
<div data-you class="invisible flex items-center gap-2">
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
title="Change your name"
|
|
138
|
+
class="group inline-flex max-w-48 items-center gap-2 rounded-lg border py-1 pl-1 pr-2.5 text-sm border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))]"
|
|
139
|
+
>
|
|
140
|
+
<span
|
|
141
|
+
data-you-avatar
|
|
142
|
+
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
|
143
|
+
/>
|
|
144
|
+
<span data-you-name class="truncate font-medium" />
|
|
145
|
+
<Pencil
|
|
146
|
+
size={12}
|
|
147
|
+
className="shrink-0 text-[rgb(var(--ec-icon-color))] group-hover:text-[rgb(var(--ec-page-text))]"
|
|
148
|
+
/>
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
<div class="flex items-center gap-2">
|
|
153
|
+
<form method="get" action={buildUrl('/studio')} class="flex items-center gap-2" role="search">
|
|
154
|
+
{pageSize !== PAGE_SIZES[0] && <input type="hidden" name="size" value={pageSize} />}
|
|
155
|
+
<label class="relative">
|
|
156
|
+
<span class="sr-only">Filter canvases</span>
|
|
157
|
+
<Search
|
|
158
|
+
size={14}
|
|
159
|
+
className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
160
|
+
/>
|
|
161
|
+
<input
|
|
162
|
+
type="search"
|
|
163
|
+
name="q"
|
|
164
|
+
value={search}
|
|
165
|
+
placeholder="Filter..."
|
|
166
|
+
class="w-56 rounded-lg border py-1.5 pl-8 pr-3 text-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))]"
|
|
167
|
+
/>
|
|
168
|
+
</label>
|
|
169
|
+
<label class="relative">
|
|
170
|
+
<span class="sr-only">Status</span>
|
|
171
|
+
{/* Our own arrow: the browser's sits against the edge, whatever the padding */}
|
|
172
|
+
<select
|
|
173
|
+
name="status"
|
|
174
|
+
onchange="this.form.requestSubmit()"
|
|
175
|
+
class="cursor-pointer appearance-none rounded-lg border py-1.5 pl-3 pr-9 text-sm bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))] focus:outline-none focus:border-[rgb(var(--ec-accent))]"
|
|
176
|
+
>
|
|
177
|
+
<option value="" selected={!status}>
|
|
178
|
+
All statuses
|
|
179
|
+
</option>
|
|
180
|
+
{CANVAS_STATUSES.map((value) => (
|
|
181
|
+
<option value={value} selected={status === value}>
|
|
182
|
+
{CANVAS_STATUS_LOOK[value].label} ({counts[value]})
|
|
183
|
+
</option>
|
|
184
|
+
))}
|
|
185
|
+
</select>
|
|
186
|
+
<ChevronDown
|
|
187
|
+
size={14}
|
|
188
|
+
className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
189
|
+
/>
|
|
190
|
+
</label>
|
|
191
|
+
</form>
|
|
192
|
+
<a
|
|
193
|
+
href={newCanvasUrl}
|
|
194
|
+
data-new-canvas
|
|
195
|
+
class="inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))] hover:bg-[rgb(var(--ec-button-bg-hover))]"
|
|
196
|
+
>
|
|
197
|
+
<Plus size={14} />
|
|
198
|
+
New canvas
|
|
199
|
+
</a>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div data-canvas-list class="min-h-0 flex-1 overflow-auto px-6 pb-5">
|
|
205
|
+
<div class="overflow-hidden rounded-xl border border-[rgb(var(--ec-page-border)/0.72)] bg-[rgb(var(--ec-dropdown-bg)/0.66)]">
|
|
206
|
+
<table class="min-w-full divide-y divide-[rgb(var(--ec-page-border)/0.62)]">
|
|
207
|
+
<thead class="sticky top-0 z-10 bg-[rgb(var(--ec-content-hover)/0.45)]">
|
|
208
|
+
<tr>
|
|
209
|
+
{['Canvas', 'Status', 'Contents', 'Comments', 'Created', 'Updated'].map((label) => (
|
|
210
|
+
<th
|
|
211
|
+
scope="col"
|
|
212
|
+
class="px-4 py-2.5 text-left text-[11px] font-medium uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))]"
|
|
213
|
+
>
|
|
214
|
+
{label}
|
|
215
|
+
</th>
|
|
216
|
+
))}
|
|
217
|
+
<th scope="col" class="w-px px-4 py-2.5">
|
|
218
|
+
<span class="sr-only">Actions</span>
|
|
219
|
+
</th>
|
|
220
|
+
</tr>
|
|
221
|
+
</thead>
|
|
222
|
+
<tbody class="divide-y divide-[rgb(var(--ec-page-border)/0.5)]">
|
|
223
|
+
{onPage.map((canvas) => {
|
|
224
|
+
const look = CANVAS_STATUS_LOOK[canvas.status];
|
|
225
|
+
const preview = canvas.preview;
|
|
226
|
+
return (
|
|
227
|
+
<tr class="group relative transition-colors hover:bg-[rgb(var(--ec-content-hover)/0.38)]">
|
|
228
|
+
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text))]">
|
|
229
|
+
<a href={buildUrl(`/studio/${canvas.canvasId}`)} class="flex items-center gap-3 font-semibold">
|
|
230
|
+
{/* The whole row opens it */}
|
|
231
|
+
<span class="absolute inset-0" aria-hidden="true" />
|
|
232
|
+
<span class="flex h-9 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border bg-[rgb(var(--ec-page-bg))] border-[rgb(var(--ec-page-border))]">
|
|
233
|
+
{preview && (
|
|
234
|
+
<svg
|
|
235
|
+
viewBox={framed(preview)}
|
|
236
|
+
preserveAspectRatio="xMidYMid meet"
|
|
237
|
+
class="h-full w-full"
|
|
238
|
+
aria-hidden="true"
|
|
239
|
+
>
|
|
240
|
+
{preview.lines.map((line) => (
|
|
241
|
+
<line
|
|
242
|
+
{...line}
|
|
243
|
+
stroke="rgb(var(--ec-page-text-muted))"
|
|
244
|
+
stroke-opacity="0.5"
|
|
245
|
+
stroke-width={strokeOf(preview, 120)}
|
|
246
|
+
/>
|
|
247
|
+
))}
|
|
248
|
+
{preview.shapes.map((shape) => (
|
|
249
|
+
<rect
|
|
250
|
+
x={shape.x}
|
|
251
|
+
y={shape.y}
|
|
252
|
+
width={shape.width}
|
|
253
|
+
height={shape.height}
|
|
254
|
+
rx={strokeOf(preview, 40)}
|
|
255
|
+
fill={shape.container ? 'none' : colourOf(shape.type)}
|
|
256
|
+
fill-opacity="0.35"
|
|
257
|
+
stroke={shape.container ? 'rgb(var(--ec-page-text-muted))' : colourOf(shape.type)}
|
|
258
|
+
stroke-opacity={shape.container ? 0.5 : 1}
|
|
259
|
+
stroke-width={strokeOf(preview, shape.container ? 160 : 100)}
|
|
260
|
+
/>
|
|
261
|
+
))}
|
|
262
|
+
</svg>
|
|
263
|
+
)}
|
|
264
|
+
</span>
|
|
265
|
+
<span class="truncate group-hover:text-[rgb(var(--ec-accent))]">
|
|
266
|
+
{canvas.title ?? 'Untitled canvas'}
|
|
267
|
+
</span>
|
|
268
|
+
</a>
|
|
269
|
+
</td>
|
|
270
|
+
<td class="px-4 py-3 text-sm">
|
|
271
|
+
<span class:list={['inline-flex rounded-full border px-2 py-px text-[11px] font-medium', look.pill]}>
|
|
272
|
+
{look.label}
|
|
273
|
+
</span>
|
|
274
|
+
</td>
|
|
275
|
+
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
276
|
+
{plural(canvas.nodeCount, 'node')}, {plural(canvas.edgeCount, 'connection')}
|
|
277
|
+
</td>
|
|
278
|
+
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
279
|
+
{canvas.openComments > 0 ? `${canvas.openComments} open` : '-'}
|
|
280
|
+
</td>
|
|
281
|
+
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
282
|
+
{[canvas.createdAt && ago(canvas.createdAt), canvas.createdBy && `by ${canvas.createdBy}`]
|
|
283
|
+
.filter(Boolean)
|
|
284
|
+
.join(' ') || '-'}
|
|
285
|
+
</td>
|
|
286
|
+
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
287
|
+
{canvas.updatedAt ? ago(canvas.updatedAt) : '-'}
|
|
288
|
+
</td>
|
|
289
|
+
<td class="px-4 py-3 text-right">
|
|
290
|
+
{/* Above the link that covers the row */}
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
data-canvas-menu={canvas.canvasId}
|
|
294
|
+
data-canvas-title={canvas.title ?? ''}
|
|
295
|
+
title="More"
|
|
296
|
+
aria-label={`More for ${canvas.title ?? 'Untitled canvas'}`}
|
|
297
|
+
aria-haspopup="menu"
|
|
298
|
+
aria-expanded="false"
|
|
299
|
+
class="relative z-[1] rounded-md p-1.5 text-[rgb(var(--ec-icon-color))] hover:bg-[rgb(var(--ec-page-border)/0.5)] hover:text-[rgb(var(--ec-page-text))] aria-expanded:bg-[rgb(var(--ec-page-border)/0.5)]"
|
|
300
|
+
>
|
|
301
|
+
<EllipsisVertical size={16} />
|
|
302
|
+
</button>
|
|
303
|
+
</td>
|
|
304
|
+
</tr>
|
|
305
|
+
);
|
|
306
|
+
})}
|
|
307
|
+
{shown.length === 0 && (
|
|
308
|
+
<tr>
|
|
309
|
+
<td colspan="7" class="px-4 py-12 text-center">
|
|
310
|
+
<p class="text-sm font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
311
|
+
{canvases.length === 0 ? 'No canvases yet' : 'No canvases match'}
|
|
312
|
+
</p>
|
|
313
|
+
<p class="mt-1 text-xs text-[rgb(var(--ec-icon-color))]">
|
|
314
|
+
{canvases.length === 0 ? (
|
|
315
|
+
<a href={newCanvasUrl} data-new-canvas class="text-[rgb(var(--ec-accent))] hover:underline">
|
|
316
|
+
Start a canvas
|
|
317
|
+
</a>
|
|
318
|
+
) : (
|
|
319
|
+
<a href={buildUrl('/studio')} class="text-[rgb(var(--ec-accent))] hover:underline">
|
|
320
|
+
Show all canvases
|
|
321
|
+
</a>
|
|
322
|
+
)}
|
|
323
|
+
</p>
|
|
324
|
+
</td>
|
|
325
|
+
</tr>
|
|
326
|
+
)}
|
|
327
|
+
</tbody>
|
|
328
|
+
</table>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<div class="flex flex-shrink-0 items-center justify-between gap-4 border-t border-[rgb(var(--ec-page-border))] px-6 py-3">
|
|
333
|
+
<div class="flex min-w-0 items-center gap-3 text-xs text-[rgb(var(--ec-page-text-muted))]">
|
|
334
|
+
{shown.length > 0 && (
|
|
335
|
+
<span class="shrink-0">
|
|
336
|
+
<span class="font-medium text-[rgb(var(--ec-page-text))]">{onPage.length}</span> of{' '}
|
|
337
|
+
<span class="font-medium text-[rgb(var(--ec-page-text))]">{shown.length}</span> results
|
|
338
|
+
</span>
|
|
339
|
+
)}
|
|
340
|
+
{shown.length > 0 && <span aria-hidden="true" class="h-3 w-px shrink-0 bg-[rgb(var(--ec-page-border))]" />}
|
|
341
|
+
<form method="get" action={buildUrl('/studio')} class="flex shrink-0 items-center gap-1.5">
|
|
342
|
+
{search && <input type="hidden" name="q" value={search} />}
|
|
343
|
+
{status && <input type="hidden" name="status" value={status} />}
|
|
344
|
+
<label for="studio-page-size">Per page</label>
|
|
345
|
+
<select
|
|
346
|
+
id="studio-page-size"
|
|
347
|
+
name="size"
|
|
348
|
+
onchange="this.form.requestSubmit()"
|
|
349
|
+
class="cursor-pointer rounded bg-transparent px-1 py-0.5 font-medium text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] focus:bg-[rgb(var(--ec-content-hover))] focus:outline-none"
|
|
350
|
+
>
|
|
351
|
+
{PAGE_SIZES.map((size) => (
|
|
352
|
+
<option value={size} selected={size === pageSize}>
|
|
353
|
+
{size}
|
|
354
|
+
</option>
|
|
355
|
+
))}
|
|
356
|
+
</select>
|
|
357
|
+
</form>
|
|
358
|
+
<span aria-hidden="true" class="h-3 w-px shrink-0 bg-[rgb(var(--ec-page-border))]" />
|
|
359
|
+
<span class="truncate">
|
|
360
|
+
{storage.type === 'memory'
|
|
361
|
+
? 'Kept in memory until EventCatalog restarts (set storage in eventcatalog.config.js to keep them)'
|
|
362
|
+
: `Saved in ${savedIn}`}
|
|
363
|
+
</span>
|
|
364
|
+
</div>
|
|
365
|
+
<nav class="flex shrink-0 items-center gap-1.5" aria-label="Pages">
|
|
366
|
+
{pageNumber > 1 ? (
|
|
367
|
+
<a
|
|
368
|
+
href={pageUrl(pageNumber - 1)}
|
|
369
|
+
title="Previous page"
|
|
370
|
+
class="p-1.5 text-[rgb(var(--ec-icon-color))] transition-colors hover:text-[rgb(var(--ec-page-text))]"
|
|
371
|
+
>
|
|
372
|
+
<ChevronLeft className="h-4 w-4" />
|
|
373
|
+
</a>
|
|
374
|
+
) : (
|
|
375
|
+
<span aria-disabled="true" class="p-1.5 text-[rgb(var(--ec-icon-color))] opacity-30">
|
|
376
|
+
<ChevronLeft className="h-4 w-4" />
|
|
377
|
+
</span>
|
|
378
|
+
)}
|
|
379
|
+
<span class="min-w-[60px] text-center text-xs tabular-nums text-[rgb(var(--ec-page-text-muted))]">
|
|
380
|
+
<span class="font-medium text-[rgb(var(--ec-page-text))]">{pageNumber}</span> / {pageCount}
|
|
381
|
+
</span>
|
|
382
|
+
{pageNumber < pageCount ? (
|
|
383
|
+
<a
|
|
384
|
+
href={pageUrl(pageNumber + 1)}
|
|
385
|
+
title="Next page"
|
|
386
|
+
class="p-1.5 text-[rgb(var(--ec-icon-color))] transition-colors hover:text-[rgb(var(--ec-page-text))]"
|
|
387
|
+
>
|
|
388
|
+
<ChevronRight className="h-4 w-4" />
|
|
389
|
+
</a>
|
|
390
|
+
) : (
|
|
391
|
+
<span aria-disabled="true" class="p-1.5 text-[rgb(var(--ec-icon-color))] opacity-30">
|
|
392
|
+
<ChevronRight className="h-4 w-4" />
|
|
393
|
+
</span>
|
|
394
|
+
)}
|
|
395
|
+
</nav>
|
|
396
|
+
</div>
|
|
397
|
+
</main>
|
|
398
|
+
|
|
399
|
+
{/* Naming a new canvas before it opens (created through the Studio API) */}
|
|
400
|
+
<dialog
|
|
401
|
+
id="new-canvas-dialog"
|
|
402
|
+
aria-labelledby="new-canvas-title"
|
|
403
|
+
class="m-auto w-full max-w-md rounded-xl border p-0 shadow-2xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] backdrop:bg-black/40"
|
|
404
|
+
>
|
|
405
|
+
<form data-api={canvasesApiUrl}>
|
|
406
|
+
<div class="border-b px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
407
|
+
<h2 id="new-canvas-title" class="text-base font-semibold">
|
|
408
|
+
New canvas
|
|
409
|
+
</h2>
|
|
410
|
+
<p class="mt-0.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
411
|
+
Name it for what you're designing, so your team can find it.
|
|
412
|
+
</p>
|
|
413
|
+
</div>
|
|
414
|
+
<div class="px-5 py-4">
|
|
415
|
+
<label for="new-canvas-name" class="block text-sm font-semibold">
|
|
416
|
+
Name
|
|
417
|
+
</label>
|
|
418
|
+
<input
|
|
419
|
+
id="new-canvas-name"
|
|
420
|
+
name="title"
|
|
421
|
+
maxlength={MAX_TITLE_LENGTH}
|
|
422
|
+
autocomplete="off"
|
|
423
|
+
placeholder="e.g. Payments redesign"
|
|
424
|
+
class="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)]"
|
|
425
|
+
/>
|
|
426
|
+
{/* Who's starting it: the name they use on canvases, if they've joined one in this browser */}
|
|
427
|
+
<input type="hidden" name="by" />
|
|
428
|
+
<p data-error hidden class:list={['mt-2 text-sm', STATUS.danger.text]} />
|
|
429
|
+
</div>
|
|
430
|
+
<div class="flex justify-end gap-2 border-t px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
122
431
|
<button
|
|
123
432
|
type="button"
|
|
124
|
-
|
|
125
|
-
class="
|
|
433
|
+
data-close-dialog
|
|
434
|
+
class="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)]"
|
|
126
435
|
>
|
|
127
|
-
|
|
128
|
-
data-you-avatar
|
|
129
|
-
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
|
130
|
-
/>
|
|
131
|
-
<span data-you-name class="truncate font-medium" />
|
|
132
|
-
<Pencil
|
|
133
|
-
size={12}
|
|
134
|
-
className="shrink-0 text-[rgb(var(--ec-icon-color))] group-hover:text-[rgb(var(--ec-page-text))]"
|
|
135
|
-
/>
|
|
436
|
+
Cancel
|
|
136
437
|
</button>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div class="flex items-center gap-2">
|
|
141
|
-
<form method="get" action={buildUrl('/studio')} class="flex items-center gap-2" role="search">
|
|
142
|
-
{pageSize !== PAGE_SIZES[0] && <input type="hidden" name="size" value={pageSize} />}
|
|
143
|
-
<label class="relative">
|
|
144
|
-
<span class="sr-only">Filter canvases</span>
|
|
145
|
-
<Search
|
|
146
|
-
size={14}
|
|
147
|
-
className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
148
|
-
/>
|
|
149
|
-
<input
|
|
150
|
-
type="search"
|
|
151
|
-
name="q"
|
|
152
|
-
value={search}
|
|
153
|
-
placeholder="Filter..."
|
|
154
|
-
class="w-56 rounded-lg border py-1.5 pl-8 pr-3 text-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))]"
|
|
155
|
-
/>
|
|
156
|
-
</label>
|
|
157
|
-
<label class="relative">
|
|
158
|
-
<span class="sr-only">Status</span>
|
|
159
|
-
{/* Our own arrow: the browser's sits against the edge, whatever the padding */}
|
|
160
|
-
<select
|
|
161
|
-
name="status"
|
|
162
|
-
onchange="this.form.requestSubmit()"
|
|
163
|
-
class="cursor-pointer appearance-none rounded-lg border py-1.5 pl-3 pr-9 text-sm bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))] focus:outline-none focus:border-[rgb(var(--ec-accent))]"
|
|
438
|
+
<button
|
|
439
|
+
type="submit"
|
|
440
|
+
class="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"
|
|
164
441
|
>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<option value={value} selected={status === value}>
|
|
169
|
-
{CANVAS_STATUS_LOOK[value].label} ({counts[value]})
|
|
170
|
-
</option>
|
|
171
|
-
))
|
|
172
|
-
}
|
|
173
|
-
</select>
|
|
174
|
-
<ChevronDown
|
|
175
|
-
size={14}
|
|
176
|
-
className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[rgb(var(--ec-icon-color))]"
|
|
177
|
-
/>
|
|
178
|
-
</label>
|
|
442
|
+
Create canvas
|
|
443
|
+
</button>
|
|
444
|
+
</div>
|
|
179
445
|
</form>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
446
|
+
</dialog>
|
|
447
|
+
|
|
448
|
+
{/* A canvas's menu, opened from its row (one for the page, moved to the row it's opened from) */}
|
|
449
|
+
<div
|
|
450
|
+
id="canvas-row-menu"
|
|
451
|
+
role="menu"
|
|
452
|
+
hidden
|
|
453
|
+
data-api={canvasesApiUrl}
|
|
454
|
+
class="fixed z-50 min-w-44 rounded-xl border p-1 shadow-xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
|
|
455
|
+
>
|
|
456
|
+
<button
|
|
457
|
+
type="button"
|
|
458
|
+
role="menuitem"
|
|
459
|
+
data-menu-copy
|
|
460
|
+
class="flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs hover:bg-[rgb(var(--ec-page-border)/0.5)] disabled:opacity-60"
|
|
461
|
+
>
|
|
462
|
+
<Copy size={14} />
|
|
463
|
+
<span data-label>Make a copy</span>
|
|
464
|
+
</button>
|
|
465
|
+
<div class="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
|
|
466
|
+
<button
|
|
467
|
+
type="button"
|
|
468
|
+
role="menuitem"
|
|
469
|
+
data-menu-delete
|
|
470
|
+
class:list={[
|
|
471
|
+
'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs',
|
|
472
|
+
STATUS.danger.text,
|
|
473
|
+
STATUS.danger.hover,
|
|
474
|
+
]}
|
|
184
475
|
>
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
</
|
|
476
|
+
<Trash2 size={14} />
|
|
477
|
+
Delete
|
|
478
|
+
</button>
|
|
479
|
+
<p data-error hidden class:list={['max-w-56 px-2.5 py-1.5 text-xs', STATUS.danger.text]} />
|
|
188
480
|
</div>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<div class="min-h-0 flex-1 overflow-auto px-6 pb-5">
|
|
193
|
-
<div
|
|
194
|
-
class="overflow-hidden rounded-xl border border-[rgb(var(--ec-page-border)/0.72)] bg-[rgb(var(--ec-dropdown-bg)/0.66)]"
|
|
195
|
-
>
|
|
196
|
-
<table class="min-w-full divide-y divide-[rgb(var(--ec-page-border)/0.62)]">
|
|
197
|
-
<thead class="sticky top-0 z-10 bg-[rgb(var(--ec-content-hover)/0.45)]">
|
|
198
|
-
<tr>
|
|
199
|
-
{
|
|
200
|
-
['Canvas', 'Status', 'Contents', 'Comments', 'Created', 'Updated'].map((label) => (
|
|
201
|
-
<th
|
|
202
|
-
scope="col"
|
|
203
|
-
class="px-4 py-2.5 text-left text-[11px] font-medium uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))]"
|
|
204
|
-
>
|
|
205
|
-
{label}
|
|
206
|
-
</th>
|
|
207
|
-
))
|
|
208
|
-
}
|
|
209
|
-
</tr>
|
|
210
|
-
</thead>
|
|
211
|
-
<tbody class="divide-y divide-[rgb(var(--ec-page-border)/0.5)]">
|
|
212
|
-
{
|
|
213
|
-
onPage.map((canvas) => {
|
|
214
|
-
const look = CANVAS_STATUS_LOOK[canvas.status];
|
|
215
|
-
const preview = canvas.preview;
|
|
216
|
-
return (
|
|
217
|
-
<tr class="group relative transition-colors hover:bg-[rgb(var(--ec-content-hover)/0.38)]">
|
|
218
|
-
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text))]">
|
|
219
|
-
<a href={buildUrl(`/studio/${canvas.canvasId}`)} class="flex items-center gap-3 font-semibold">
|
|
220
|
-
{/* The whole row opens it */}
|
|
221
|
-
<span class="absolute inset-0" aria-hidden="true" />
|
|
222
|
-
<span class="flex h-9 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border bg-[rgb(var(--ec-page-bg))] border-[rgb(var(--ec-page-border))]">
|
|
223
|
-
{preview && (
|
|
224
|
-
<svg
|
|
225
|
-
viewBox={framed(preview)}
|
|
226
|
-
preserveAspectRatio="xMidYMid meet"
|
|
227
|
-
class="h-full w-full"
|
|
228
|
-
aria-hidden="true"
|
|
229
|
-
>
|
|
230
|
-
{preview.lines.map((line) => (
|
|
231
|
-
<line
|
|
232
|
-
{...line}
|
|
233
|
-
stroke="rgb(var(--ec-page-text-muted))"
|
|
234
|
-
stroke-opacity="0.5"
|
|
235
|
-
stroke-width={strokeOf(preview, 120)}
|
|
236
|
-
/>
|
|
237
|
-
))}
|
|
238
|
-
{preview.shapes.map((shape) => (
|
|
239
|
-
<rect
|
|
240
|
-
x={shape.x}
|
|
241
|
-
y={shape.y}
|
|
242
|
-
width={shape.width}
|
|
243
|
-
height={shape.height}
|
|
244
|
-
rx={strokeOf(preview, 40)}
|
|
245
|
-
fill={shape.container ? 'none' : colourOf(shape.type)}
|
|
246
|
-
fill-opacity="0.35"
|
|
247
|
-
stroke={shape.container ? 'rgb(var(--ec-page-text-muted))' : colourOf(shape.type)}
|
|
248
|
-
stroke-opacity={shape.container ? 0.5 : 1}
|
|
249
|
-
stroke-width={strokeOf(preview, shape.container ? 160 : 100)}
|
|
250
|
-
/>
|
|
251
|
-
))}
|
|
252
|
-
</svg>
|
|
253
|
-
)}
|
|
254
|
-
</span>
|
|
255
|
-
<span class="truncate group-hover:text-[rgb(var(--ec-accent))]">{canvas.title ?? 'Untitled canvas'}</span>
|
|
256
|
-
</a>
|
|
257
|
-
</td>
|
|
258
|
-
<td class="px-4 py-3 text-sm">
|
|
259
|
-
<span class:list={['inline-flex rounded-full border px-2 py-px text-[11px] font-medium', look.pill]}>
|
|
260
|
-
{look.label}
|
|
261
|
-
</span>
|
|
262
|
-
</td>
|
|
263
|
-
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
264
|
-
{plural(canvas.nodeCount, 'node')}, {plural(canvas.edgeCount, 'connection')}
|
|
265
|
-
</td>
|
|
266
|
-
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
267
|
-
{canvas.openComments > 0 ? `${canvas.openComments} open` : '-'}
|
|
268
|
-
</td>
|
|
269
|
-
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
270
|
-
{[canvas.createdAt && ago(canvas.createdAt), canvas.createdBy && `by ${canvas.createdBy}`]
|
|
271
|
-
.filter(Boolean)
|
|
272
|
-
.join(' ') || '-'}
|
|
273
|
-
</td>
|
|
274
|
-
<td class="px-4 py-3 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
275
|
-
{canvas.updatedAt ? ago(canvas.updatedAt) : '-'}
|
|
276
|
-
</td>
|
|
277
|
-
</tr>
|
|
278
|
-
);
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
{
|
|
282
|
-
shown.length === 0 && (
|
|
283
|
-
<tr>
|
|
284
|
-
<td colspan="6" class="px-4 py-12 text-center">
|
|
285
|
-
<p class="text-sm font-medium text-[rgb(var(--ec-page-text-muted))]">
|
|
286
|
-
{canvases.length === 0 ? 'No canvases yet' : 'No canvases match'}
|
|
287
|
-
</p>
|
|
288
|
-
<p class="mt-1 text-xs text-[rgb(var(--ec-icon-color))]">
|
|
289
|
-
{canvases.length === 0 ? (
|
|
290
|
-
<a href={buildUrl('/studio/new')} data-new-canvas class="text-[rgb(var(--ec-accent))] hover:underline">
|
|
291
|
-
Start a canvas
|
|
292
|
-
</a>
|
|
293
|
-
) : (
|
|
294
|
-
<a href={buildUrl('/studio')} class="text-[rgb(var(--ec-accent))] hover:underline">
|
|
295
|
-
Show all canvases
|
|
296
|
-
</a>
|
|
297
|
-
)}
|
|
298
|
-
</p>
|
|
299
|
-
</td>
|
|
300
|
-
</tr>
|
|
301
|
-
)
|
|
302
|
-
}
|
|
303
|
-
</tbody>
|
|
304
|
-
</table>
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
481
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
PAGE_SIZES.map((size) => (
|
|
331
|
-
<option value={size} selected={size === pageSize}>
|
|
332
|
-
{size}
|
|
333
|
-
</option>
|
|
334
|
-
))
|
|
335
|
-
}
|
|
336
|
-
</select>
|
|
337
|
-
</form>
|
|
338
|
-
<span aria-hidden="true" class="h-3 w-px shrink-0 bg-[rgb(var(--ec-page-border))]"></span>
|
|
339
|
-
<span class="truncate">
|
|
340
|
-
{
|
|
341
|
-
storage.type === 'memory'
|
|
342
|
-
? 'Kept in memory until EventCatalog restarts (set studio.storage in eventcatalog.config.js to keep them)'
|
|
343
|
-
: `Saved in ${savedIn}`
|
|
344
|
-
}
|
|
345
|
-
</span>
|
|
346
|
-
</div>
|
|
347
|
-
<nav class="flex shrink-0 items-center gap-1.5" aria-label="Pages">
|
|
348
|
-
{
|
|
349
|
-
pageNumber > 1 ? (
|
|
350
|
-
<a
|
|
351
|
-
href={pageUrl(pageNumber - 1)}
|
|
352
|
-
title="Previous page"
|
|
353
|
-
class="p-1.5 text-[rgb(var(--ec-icon-color))] transition-colors hover:text-[rgb(var(--ec-page-text))]"
|
|
482
|
+
{/* Deleting a canvas, after asking (it's deleted for everyone, for good) */}
|
|
483
|
+
<dialog
|
|
484
|
+
id="delete-canvas-dialog"
|
|
485
|
+
role="alertdialog"
|
|
486
|
+
aria-labelledby="delete-canvas-title"
|
|
487
|
+
data-api={canvasesApiUrl}
|
|
488
|
+
class="m-auto w-full max-w-md rounded-xl border p-0 shadow-2xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] backdrop:bg-black/40"
|
|
489
|
+
>
|
|
490
|
+
<div class="space-y-2 px-5 py-4">
|
|
491
|
+
<h2 id="delete-canvas-title" class="text-base font-semibold">
|
|
492
|
+
Delete this canvas?
|
|
493
|
+
</h2>
|
|
494
|
+
<p class="text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
495
|
+
It's deleted for everyone, with its comments, and can't be brought back. Anyone who has it open is told.
|
|
496
|
+
</p>
|
|
497
|
+
<p data-error hidden class:list={['text-sm', STATUS.danger.text]} />
|
|
498
|
+
</div>
|
|
499
|
+
<div class="flex justify-end gap-2 border-t px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
500
|
+
<button
|
|
501
|
+
type="button"
|
|
502
|
+
data-close-dialog
|
|
503
|
+
class="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)]"
|
|
354
504
|
>
|
|
355
|
-
|
|
356
|
-
</
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
)
|
|
362
|
-
}
|
|
363
|
-
<span class="min-w-[60px] text-center text-xs tabular-nums text-[rgb(var(--ec-page-text-muted))]">
|
|
364
|
-
<span class="font-medium text-[rgb(var(--ec-page-text))]">{pageNumber}</span> / {pageCount}
|
|
365
|
-
</span>
|
|
366
|
-
{
|
|
367
|
-
pageNumber < pageCount ? (
|
|
368
|
-
<a
|
|
369
|
-
href={pageUrl(pageNumber + 1)}
|
|
370
|
-
title="Next page"
|
|
371
|
-
class="p-1.5 text-[rgb(var(--ec-icon-color))] transition-colors hover:text-[rgb(var(--ec-page-text))]"
|
|
505
|
+
Cancel
|
|
506
|
+
</button>
|
|
507
|
+
<button
|
|
508
|
+
type="button"
|
|
509
|
+
data-confirm-delete
|
|
510
|
+
class:list={['rounded-lg px-4 py-2 text-sm font-medium disabled:opacity-60', STATUS.danger.button]}
|
|
372
511
|
>
|
|
373
|
-
|
|
374
|
-
</
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
<ChevronRight className="h-4 w-4" />
|
|
378
|
-
</span>
|
|
379
|
-
)
|
|
380
|
-
}
|
|
381
|
-
</nav>
|
|
382
|
-
</div>
|
|
383
|
-
</main>
|
|
512
|
+
Delete canvas
|
|
513
|
+
</button>
|
|
514
|
+
</div>
|
|
515
|
+
</dialog>
|
|
384
516
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
>
|
|
391
|
-
<form method="post" action={buildUrl('/studio/new')}>
|
|
392
|
-
<div class="border-b px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
393
|
-
<h2 id="new-canvas-title" class="text-base font-semibold">New canvas</h2>
|
|
394
|
-
<p class="mt-0.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
395
|
-
Name it for what you're designing, so your team can find it.
|
|
396
|
-
</p>
|
|
397
|
-
</div>
|
|
398
|
-
<div class="px-5 py-4">
|
|
399
|
-
<label for="new-canvas-name" class="block text-sm font-semibold">Name</label>
|
|
400
|
-
<input
|
|
401
|
-
id="new-canvas-name"
|
|
402
|
-
name="title"
|
|
403
|
-
maxlength="200"
|
|
404
|
-
autocomplete="off"
|
|
405
|
-
placeholder="e.g. Payments redesign"
|
|
406
|
-
class="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)]"
|
|
407
|
-
/>
|
|
408
|
-
{/* Who's starting it: the name they use on canvases, if they've joined one in this browser */}
|
|
409
|
-
<input type="hidden" name="by" />
|
|
410
|
-
</div>
|
|
411
|
-
<div class="flex justify-end gap-2 border-t px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
412
|
-
<button
|
|
413
|
-
type="button"
|
|
414
|
-
data-close-dialog
|
|
415
|
-
class="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)]"
|
|
517
|
+
{/* Your name: asked for on your first visit, changed from the button by New canvas */}
|
|
518
|
+
<dialog
|
|
519
|
+
id="name-dialog"
|
|
520
|
+
aria-labelledby="name-dialog-title"
|
|
521
|
+
class="m-auto w-full max-w-md rounded-xl border p-0 shadow-2xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))] backdrop:bg-black/40"
|
|
416
522
|
>
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
Cancel
|
|
462
|
-
</button>
|
|
463
|
-
<button
|
|
464
|
-
type="submit"
|
|
465
|
-
class="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))]"
|
|
466
|
-
>
|
|
467
|
-
Save
|
|
468
|
-
</button>
|
|
469
|
-
</div>
|
|
470
|
-
</form>
|
|
471
|
-
</dialog>
|
|
523
|
+
<form>
|
|
524
|
+
<div class="border-b px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
525
|
+
<h2 id="name-dialog-title" class="text-base font-semibold" data-first-visit-text="Welcome to Studio">
|
|
526
|
+
Your name
|
|
527
|
+
</h2>
|
|
528
|
+
<p class="mt-0.5 text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
529
|
+
What should others call you? It's shown by your pointer and your comments on every canvas.
|
|
530
|
+
</p>
|
|
531
|
+
</div>
|
|
532
|
+
<div class="px-5 py-4">
|
|
533
|
+
<label for="your-name" class="block text-sm font-semibold">
|
|
534
|
+
Name
|
|
535
|
+
</label>
|
|
536
|
+
<input
|
|
537
|
+
id="your-name"
|
|
538
|
+
name="name"
|
|
539
|
+
required
|
|
540
|
+
maxlength={MAX_NAME_LENGTH}
|
|
541
|
+
autocomplete="name"
|
|
542
|
+
placeholder="e.g. Sam"
|
|
543
|
+
class="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)]"
|
|
544
|
+
/>
|
|
545
|
+
<p class="mt-2 text-xs text-[rgb(var(--ec-page-text-muted))]">Kept in this browser. Change it here any time.</p>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="flex justify-end gap-2 border-t px-5 py-4 border-[rgb(var(--ec-page-border))]">
|
|
548
|
+
<button
|
|
549
|
+
type="button"
|
|
550
|
+
data-close-dialog
|
|
551
|
+
class="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)]"
|
|
552
|
+
>
|
|
553
|
+
Cancel
|
|
554
|
+
</button>
|
|
555
|
+
<button
|
|
556
|
+
type="submit"
|
|
557
|
+
class="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))]"
|
|
558
|
+
>
|
|
559
|
+
Save
|
|
560
|
+
</button>
|
|
561
|
+
</div>
|
|
562
|
+
</form>
|
|
563
|
+
</dialog>
|
|
564
|
+
</Fragment>
|
|
565
|
+
)
|
|
566
|
+
}
|
|
472
567
|
</VerticalSideBarLayout>
|
|
473
568
|
|
|
474
569
|
<script>
|
|
475
570
|
import { colorForName, getStoredName, onStoredNameChange, storeName } from '../identity';
|
|
571
|
+
import { copyCanvasThroughApi, createCanvasThroughApi, deleteCanvasThroughApi } from '../api/client';
|
|
476
572
|
|
|
477
573
|
// On every visit to the page, including ones the app's client-side navigation swaps in (its scripts run only once)
|
|
478
574
|
const setUp = () => {
|
|
@@ -533,9 +629,136 @@ const strokeOf = (preview: NonNullable<CanvasSummary['preview']>, ratio: number)
|
|
|
533
629
|
const open = () => {
|
|
534
630
|
const by = dialog.querySelector<HTMLInputElement>('input[name="by"]');
|
|
535
631
|
if (by) by.value = yourName ?? '';
|
|
632
|
+
dialog.querySelector<HTMLElement>('[data-error]')!.hidden = true;
|
|
536
633
|
dialog.showModal();
|
|
537
634
|
dialog.querySelector<HTMLInputElement>('#new-canvas-name')?.focus();
|
|
538
635
|
};
|
|
636
|
+
// Created through the Studio API, then opened
|
|
637
|
+
const newCanvasForm = dialog.querySelector<HTMLFormElement>('form')!;
|
|
638
|
+
newCanvasForm.addEventListener('submit', async (event) => {
|
|
639
|
+
event.preventDefault();
|
|
640
|
+
const form = new FormData(newCanvasForm);
|
|
641
|
+
const submit = newCanvasForm.querySelector<HTMLButtonElement>('[type="submit"]')!;
|
|
642
|
+
const error = newCanvasForm.querySelector<HTMLElement>('[data-error]')!;
|
|
643
|
+
submit.disabled = true;
|
|
644
|
+
error.hidden = true;
|
|
645
|
+
try {
|
|
646
|
+
const canvas = await createCanvasThroughApi(newCanvasForm.dataset.api!, {
|
|
647
|
+
title: String(form.get('title') ?? '').trim() || undefined,
|
|
648
|
+
createdBy: String(form.get('by') ?? '').trim() || undefined,
|
|
649
|
+
});
|
|
650
|
+
window.location.assign(canvas.url);
|
|
651
|
+
} catch (failure) {
|
|
652
|
+
error.textContent = (failure as Error).message;
|
|
653
|
+
error.hidden = false;
|
|
654
|
+
submit.disabled = false;
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
// A canvas's menu (from the ⋮ on its row): make a copy, or delete it
|
|
659
|
+
const rowMenu = document.querySelector<HTMLElement>('#canvas-row-menu')!;
|
|
660
|
+
const copyItem = rowMenu.querySelector<HTMLButtonElement>('[data-menu-copy]')!;
|
|
661
|
+
const copyLabel = copyItem.querySelector<HTMLElement>('[data-label]')!;
|
|
662
|
+
const menuError = rowMenu.querySelector<HTMLElement>('[data-error]')!;
|
|
663
|
+
let menuFor: HTMLButtonElement | undefined;
|
|
664
|
+
const closeMenu = () => {
|
|
665
|
+
rowMenu.hidden = true;
|
|
666
|
+
menuFor?.setAttribute('aria-expanded', 'false');
|
|
667
|
+
menuFor = undefined;
|
|
668
|
+
};
|
|
669
|
+
const openMenu = (button: HTMLButtonElement, fromKeyboard: boolean) => {
|
|
670
|
+
closeMenu();
|
|
671
|
+
menuFor = button;
|
|
672
|
+
button.setAttribute('aria-expanded', 'true');
|
|
673
|
+
copyItem.disabled = false;
|
|
674
|
+
copyLabel.textContent = 'Make a copy';
|
|
675
|
+
menuError.hidden = true;
|
|
676
|
+
rowMenu.hidden = false;
|
|
677
|
+
// Under the button, lined up with its right edge (above it if there's no room below)
|
|
678
|
+
const at = button.getBoundingClientRect();
|
|
679
|
+
const { width, height } = rowMenu.getBoundingClientRect();
|
|
680
|
+
const below = at.bottom + 4 + height <= window.innerHeight;
|
|
681
|
+
rowMenu.style.top = `${below ? at.bottom + 4 : at.top - 4 - height}px`;
|
|
682
|
+
rowMenu.style.left = `${Math.max(8, at.right - width)}px`;
|
|
683
|
+
// Opened with the keyboard: its first item, ready to choose
|
|
684
|
+
if (fromKeyboard) copyItem.focus();
|
|
685
|
+
};
|
|
686
|
+
document.querySelectorAll<HTMLButtonElement>('[data-canvas-menu]').forEach((button) =>
|
|
687
|
+
// A click with no pointer (detail 0) is the keyboard (Enter or Space)
|
|
688
|
+
button.addEventListener('click', (event) => (menuFor === button ? closeMenu() : openMenu(button, event.detail === 0)))
|
|
689
|
+
);
|
|
690
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
691
|
+
const target = event.target as Node;
|
|
692
|
+
if (!rowMenu.hidden && !rowMenu.contains(target) && !menuFor?.contains(target)) closeMenu();
|
|
693
|
+
};
|
|
694
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
695
|
+
if (event.key !== 'Escape' || rowMenu.hidden) return;
|
|
696
|
+
const button = menuFor;
|
|
697
|
+
closeMenu();
|
|
698
|
+
button?.focus();
|
|
699
|
+
};
|
|
700
|
+
window.addEventListener('pointerdown', onPointerDown);
|
|
701
|
+
window.addEventListener('keydown', onKeyDown);
|
|
702
|
+
// It's placed for where the row was
|
|
703
|
+
window.addEventListener('resize', closeMenu);
|
|
704
|
+
document.querySelector('[data-canvas-list]')?.addEventListener('scroll', closeMenu, { passive: true });
|
|
705
|
+
document.addEventListener(
|
|
706
|
+
'astro:before-swap',
|
|
707
|
+
() => {
|
|
708
|
+
window.removeEventListener('pointerdown', onPointerDown);
|
|
709
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
710
|
+
window.removeEventListener('resize', closeMenu);
|
|
711
|
+
},
|
|
712
|
+
{ once: true }
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
// Making a copy: it's listed at the top (the canvas changed most recently)
|
|
716
|
+
copyItem.addEventListener('click', async () => {
|
|
717
|
+
const canvasId = menuFor?.dataset.canvasMenu;
|
|
718
|
+
if (!canvasId) return;
|
|
719
|
+
copyItem.disabled = true;
|
|
720
|
+
copyLabel.textContent = 'Copying…';
|
|
721
|
+
menuError.hidden = true;
|
|
722
|
+
try {
|
|
723
|
+
await copyCanvasThroughApi(rowMenu.dataset.api!, canvasId, { createdBy: yourName ?? undefined });
|
|
724
|
+
window.location.reload();
|
|
725
|
+
} catch (failure) {
|
|
726
|
+
menuError.textContent = (failure as Error).message;
|
|
727
|
+
menuError.hidden = false;
|
|
728
|
+
copyItem.disabled = false;
|
|
729
|
+
copyLabel.textContent = 'Make a copy';
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// Deleting a canvas from the list, after asking
|
|
734
|
+
const deleteDialog = document.querySelector<HTMLDialogElement>('#delete-canvas-dialog')!;
|
|
735
|
+
const deleteTitle = deleteDialog.querySelector<HTMLElement>('#delete-canvas-title')!;
|
|
736
|
+
const deleteError = deleteDialog.querySelector<HTMLElement>('[data-error]')!;
|
|
737
|
+
const confirmDelete = deleteDialog.querySelector<HTMLButtonElement>('[data-confirm-delete]')!;
|
|
738
|
+
let deleting: string | undefined;
|
|
739
|
+
rowMenu.querySelector('[data-menu-delete]')!.addEventListener('click', () => {
|
|
740
|
+
deleting = menuFor?.dataset.canvasMenu;
|
|
741
|
+
const title = menuFor?.dataset.canvasTitle;
|
|
742
|
+
closeMenu();
|
|
743
|
+
deleteTitle.textContent = `Delete ${title ? `"${title}"` : 'this canvas'}?`;
|
|
744
|
+
deleteError.hidden = true;
|
|
745
|
+
confirmDelete.disabled = false;
|
|
746
|
+
deleteDialog.showModal();
|
|
747
|
+
});
|
|
748
|
+
deleteDialog.querySelector('[data-close-dialog]')!.addEventListener('click', () => deleteDialog.close());
|
|
749
|
+
confirmDelete.addEventListener('click', async () => {
|
|
750
|
+
if (!deleting) return;
|
|
751
|
+
confirmDelete.disabled = true;
|
|
752
|
+
deleteError.hidden = true;
|
|
753
|
+
try {
|
|
754
|
+
await deleteCanvasThroughApi(deleteDialog.dataset.api!, deleting);
|
|
755
|
+
window.location.reload();
|
|
756
|
+
} catch (failure) {
|
|
757
|
+
deleteError.textContent = (failure as Error).message;
|
|
758
|
+
deleteError.hidden = false;
|
|
759
|
+
confirmDelete.disabled = false;
|
|
760
|
+
}
|
|
761
|
+
});
|
|
539
762
|
// Starting a canvas says who started it: ask for a name first if there isn't one yet
|
|
540
763
|
const startCanvas = () => (yourName ? open() : askName(open));
|
|
541
764
|
// A plain button, so Enter in the name creates the canvas
|
|
@@ -546,7 +769,7 @@ const strokeOf = (preview: NonNullable<CanvasSummary['preview']>, ratio: number)
|
|
|
546
769
|
startCanvas();
|
|
547
770
|
})
|
|
548
771
|
);
|
|
549
|
-
// Opened from a
|
|
772
|
+
// Opened from a link (/studio?new=1), e.g. a canvas's New canvas
|
|
550
773
|
const url = new URL(window.location.href);
|
|
551
774
|
if (url.searchParams.has('new')) {
|
|
552
775
|
url.searchParams.delete('new');
|