@shortlink-org/portolan 0.1.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 +21 -0
- package/README.md +387 -0
- package/catalog/enum_test.go +46 -0
- package/catalog/model.go +932 -0
- package/catalog/roundtrip_test.go +185 -0
- package/catalog/via_test.go +38 -0
- package/cli/portolan.mjs +368 -0
- package/go.mod +14 -0
- package/go.sum +14 -0
- package/index.html +13 -0
- package/internal/commands/cargo.go +154 -0
- package/internal/commands/commands.go +143 -0
- package/internal/commands/commands_test.go +323 -0
- package/internal/commands/gradle.go +77 -0
- package/internal/commands/justfile.go +82 -0
- package/internal/commands/makefile.go +106 -0
- package/internal/commands/maven.go +93 -0
- package/internal/commands/packagejson.go +159 -0
- package/internal/commands/pyproject.go +169 -0
- package/internal/commands/taskfile.go +101 -0
- package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
- package/internal/commands/testdata/estate/Makefile +33 -0
- package/internal/commands/testdata/estate/Taskfile.yml +21 -0
- package/internal/commands/testdata/estate/build.gradle.kts +21 -0
- package/internal/commands/testdata/estate/justfile +20 -0
- package/internal/commands/testdata/estate/package.json +13 -0
- package/internal/commands/testdata/estate/pom.xml +29 -0
- package/internal/commands/testdata/estate/pyproject.toml +27 -0
- package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
- package/internal/commands/testdata/golden/commands.json +305 -0
- package/internal/gohttp/analyze.go +2500 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +143 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +36 -0
- package/internal/goscan/tree.go +155 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +62 -0
- package/internal/wsdl/parse.go +920 -0
- package/internal/wsdl/parse_test.go +133 -0
- package/package.json +117 -0
- package/plugin/describe.go +107 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +117 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +1160 -0
- package/plugins/extract-adr/describe.go +19 -0
- package/plugins/extract-adr/describe_test.go +11 -0
- package/plugins/extract-adr/extract.go +153 -0
- package/plugins/extract-adr/extract_test.go +350 -0
- package/plugins/extract-adr/history.go +99 -0
- package/plugins/extract-adr/main.go +65 -0
- package/plugins/extract-adr/options.schema.json +26 -0
- package/plugins/extract-adr/parse.go +642 -0
- package/plugins/extract-adr/parse_test.go +405 -0
- package/plugins/extract-asyncapi/describe.go +19 -0
- package/plugins/extract-asyncapi/describe_test.go +11 -0
- package/plugins/extract-asyncapi/extract.go +315 -0
- package/plugins/extract-asyncapi/extract_test.go +197 -0
- package/plugins/extract-asyncapi/main.go +48 -0
- package/plugins/extract-asyncapi/options.schema.json +26 -0
- package/plugins/extract-asyncapi/spec.go +151 -0
- package/plugins/extract-celery/README.md +169 -0
- package/plugins/extract-celery/extract.py +346 -0
- package/plugins/extract-celery/extract_test.py +192 -0
- package/plugins/extract-celery/main.py +66 -0
- package/plugins/extract-celery/options.py +31 -0
- package/plugins/extract-celery/options.schema.json +31 -0
- package/plugins/extract-celery/producers.py +156 -0
- package/plugins/extract-celery/routes_test.py +106 -0
- package/plugins/extract-commands/describe.go +19 -0
- package/plugins/extract-commands/describe_test.go +11 -0
- package/plugins/extract-commands/extract.go +79 -0
- package/plugins/extract-commands/extract_test.go +92 -0
- package/plugins/extract-commands/main.go +55 -0
- package/plugins/extract-commands/options.schema.json +20 -0
- package/plugins/extract-csr/avro.go +258 -0
- package/plugins/extract-csr/describe.go +19 -0
- package/plugins/extract-csr/describe_test.go +11 -0
- package/plugins/extract-csr/extract.go +338 -0
- package/plugins/extract-csr/extract_test.go +374 -0
- package/plugins/extract-csr/jsonschema.go +343 -0
- package/plugins/extract-csr/lock.go +25 -0
- package/plugins/extract-csr/main.go +84 -0
- package/plugins/extract-csr/options.schema.json +43 -0
- package/plugins/extract-csr/subject.go +102 -0
- package/plugins/extract-django/README.md +386 -0
- package/plugins/extract-django/apps.py +100 -0
- package/plugins/extract-django/auth.py +302 -0
- package/plugins/extract-django/auth_test.py +188 -0
- package/plugins/extract-django/clients.py +121 -0
- package/plugins/extract-django/contracts.py +458 -0
- package/plugins/extract-django/database.py +119 -0
- package/plugins/extract-django/domain.py +318 -0
- package/plugins/extract-django/events.py +86 -0
- package/plugins/extract-django/extract.py +601 -0
- package/plugins/extract-django/extract_test.py +397 -0
- package/plugins/extract-django/flows.py +1278 -0
- package/plugins/extract-django/flows_test.py +338 -0
- package/plugins/extract-django/ids.py +42 -0
- package/plugins/extract-django/ids_test.py +30 -0
- package/plugins/extract-django/lifecycle.py +222 -0
- package/plugins/extract-django/lifecycle_test.py +76 -0
- package/plugins/extract-django/main.py +66 -0
- package/plugins/extract-django/openapi.py +229 -0
- package/plugins/extract-django/openapi_test.py +60 -0
- package/plugins/extract-django/operations.py +96 -0
- package/plugins/extract-django/options.py +61 -0
- package/plugins/extract-django/options.schema.json +126 -0
- package/plugins/extract-django/routing.py +214 -0
- package/plugins/extract-django/routing_test.py +198 -0
- package/plugins/extract-django/serializers.py +558 -0
- package/plugins/extract-django/store.py +434 -0
- package/plugins/extract-django/transport.py +242 -0
- package/plugins/extract-flows/describe.go +19 -0
- package/plugins/extract-flows/describe_test.go +11 -0
- package/plugins/extract-flows/extract.go +91 -0
- package/plugins/extract-flows/main.go +45 -0
- package/plugins/extract-flows/options.schema.json +16 -0
- package/plugins/extract-flows/parse.go +593 -0
- package/plugins/extract-flows/parse_test.go +204 -0
- package/plugins/extract-glossary/describe.go +19 -0
- package/plugins/extract-glossary/describe_test.go +11 -0
- package/plugins/extract-glossary/extract.go +115 -0
- package/plugins/extract-glossary/extract_test.go +220 -0
- package/plugins/extract-glossary/main.go +59 -0
- package/plugins/extract-glossary/options.schema.json +21 -0
- package/plugins/extract-glossary/parse.go +214 -0
- package/plugins/extract-glossary/parse_test.go +203 -0
- package/plugins/extract-go/aggregate.go +214 -0
- package/plugins/extract-go/client.go +409 -0
- package/plugins/extract-go/client_test.go +266 -0
- package/plugins/extract-go/describe.go +19 -0
- package/plugins/extract-go/describe_test.go +11 -0
- package/plugins/extract-go/enum.go +195 -0
- package/plugins/extract-go/enum_test.go +82 -0
- package/plugins/extract-go/event.go +99 -0
- package/plugins/extract-go/extract.go +191 -0
- package/plugins/extract-go/extract_test.go +261 -0
- package/plugins/extract-go/flow.go +1441 -0
- package/plugins/extract-go/flow_test.go +609 -0
- package/plugins/extract-go/httpclient.go +174 -0
- package/plugins/extract-go/httpclient_test.go +296 -0
- package/plugins/extract-go/ids.go +92 -0
- package/plugins/extract-go/layout.go +225 -0
- package/plugins/extract-go/layout_test.go +125 -0
- package/plugins/extract-go/lifecycle.go +324 -0
- package/plugins/extract-go/lifecycle_test.go +108 -0
- package/plugins/extract-go/main.go +79 -0
- package/plugins/extract-go/operation.go +157 -0
- package/plugins/extract-go/options.schema.json +66 -0
- package/plugins/extract-go/source.go +316 -0
- package/plugins/extract-go/transport.go +321 -0
- package/plugins/extract-go/transport_test.go +145 -0
- package/plugins/extract-go/wiring.go +434 -0
- package/plugins/extract-go-nats/describe.go +19 -0
- package/plugins/extract-go-nats/describe_test.go +11 -0
- package/plugins/extract-go-nats/extract.go +177 -0
- package/plugins/extract-go-nats/extract_test.go +389 -0
- package/plugins/extract-go-nats/index.go +379 -0
- package/plugins/extract-go-nats/main.go +42 -0
- package/plugins/extract-go-nats/options.schema.json +21 -0
- package/plugins/extract-go-nats/resolve.go +161 -0
- package/plugins/extract-go-nats/sites.go +224 -0
- package/plugins/extract-graphql/describe.go +19 -0
- package/plugins/extract-graphql/describe_test.go +11 -0
- package/plugins/extract-graphql/extract.go +433 -0
- package/plugins/extract-graphql/extract_test.go +256 -0
- package/plugins/extract-graphql/ids.go +49 -0
- package/plugins/extract-graphql/lex.go +237 -0
- package/plugins/extract-graphql/main.go +51 -0
- package/plugins/extract-graphql/options.schema.json +31 -0
- package/plugins/extract-graphql/parse.go +621 -0
- package/plugins/extract-graphql/parse_test.go +122 -0
- package/plugins/extract-http-clients/describe.go +19 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +705 -0
- package/plugins/extract-http-clients/extract_test.go +1263 -0
- package/plugins/extract-http-clients/main.go +42 -0
- package/plugins/extract-http-clients/options.schema.json +33 -0
- package/plugins/extract-java/README.md +190 -0
- package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
- package/plugins/extract-java/options.schema.json +70 -0
- package/plugins/extract-openapi/describe.go +19 -0
- package/plugins/extract-openapi/describe_test.go +11 -0
- package/plugins/extract-openapi/discover.go +243 -0
- package/plugins/extract-openapi/extract.go +526 -0
- package/plugins/extract-openapi/extract_test.go +545 -0
- package/plugins/extract-openapi/main.go +75 -0
- package/plugins/extract-openapi/options.schema.json +62 -0
- package/plugins/extract-openapi/spec.go +350 -0
- package/plugins/extract-project/README.md +22 -0
- package/plugins/extract-project/describe.go +19 -0
- package/plugins/extract-project/describe_test.go +11 -0
- package/plugins/extract-project/extract.go +221 -0
- package/plugins/extract-project/extract_test.go +109 -0
- package/plugins/extract-project/main.go +41 -0
- package/plugins/extract-project/options.schema.json +48 -0
- package/plugins/extract-proto/ast.go +125 -0
- package/plugins/extract-proto/consumes.go +77 -0
- package/plugins/extract-proto/describe.go +19 -0
- package/plugins/extract-proto/extract.go +293 -0
- package/plugins/extract-proto/extract_test.go +459 -0
- package/plugins/extract-proto/ids.go +89 -0
- package/plugins/extract-proto/ids_test.go +57 -0
- package/plugins/extract-proto/lex.go +285 -0
- package/plugins/extract-proto/main.go +100 -0
- package/plugins/extract-proto/module.go +120 -0
- package/plugins/extract-proto/options.schema.json +51 -0
- package/plugins/extract-proto/parse.go +720 -0
- package/plugins/extract-proto/parse_test.go +307 -0
- package/plugins/extract-proto/provides.go +236 -0
- package/plugins/extract-proto/resolve.go +222 -0
- package/plugins/extract-proto/resolve_test.go +119 -0
- package/plugins/extract-python-kafka/README.md +36 -0
- package/plugins/extract-python-kafka/extract.py +211 -0
- package/plugins/extract-python-kafka/extract_test.py +68 -0
- package/plugins/extract-python-kafka/main.py +59 -0
- package/plugins/extract-python-kafka/options.py +29 -0
- package/plugins/extract-python-kafka/options.schema.json +31 -0
- package/plugins/extract-redis/README.md +11 -0
- package/plugins/extract-redis/describe.go +19 -0
- package/plugins/extract-redis/describe_test.go +11 -0
- package/plugins/extract-redis/extract.go +183 -0
- package/plugins/extract-redis/extract_test.go +168 -0
- package/plugins/extract-redis/keyspaces.go +469 -0
- package/plugins/extract-redis/main.go +44 -0
- package/plugins/extract-redis/options.schema.json +30 -0
- package/plugins/extract-river/describe.go +19 -0
- package/plugins/extract-river/describe_test.go +11 -0
- package/plugins/extract-river/extract.go +507 -0
- package/plugins/extract-river/extract_test.go +132 -0
- package/plugins/extract-river/main.go +42 -0
- package/plugins/extract-river/options.schema.json +21 -0
- package/plugins/extract-rust/Cargo.lock +133 -0
- package/plugins/extract-rust/Cargo.toml +23 -0
- package/plugins/extract-rust/README.md +204 -0
- package/plugins/extract-rust/options.schema.json +65 -0
- package/plugins/extract-rust/rustfmt.toml +1 -0
- package/plugins/extract-sql/ddl.go +893 -0
- package/plugins/extract-sql/ddl_test.go +401 -0
- package/plugins/extract-sql/describe.go +19 -0
- package/plugins/extract-sql/describe_test.go +11 -0
- package/plugins/extract-sql/layout.go +221 -0
- package/plugins/extract-sql/layout_test.go +55 -0
- package/plugins/extract-sql/lineage.go +107 -0
- package/plugins/extract-sql/main.go +141 -0
- package/plugins/extract-sql/maps.go +564 -0
- package/plugins/extract-sql/maps_java.go +117 -0
- package/plugins/extract-sql/maps_rust.go +333 -0
- package/plugins/extract-sql/maps_rust_test.go +70 -0
- package/plugins/extract-sql/maps_test.go +204 -0
- package/plugins/extract-sql/maps_ts.go +398 -0
- package/plugins/extract-sql/maps_ts_test.go +136 -0
- package/plugins/extract-sql/options.schema.json +53 -0
- package/plugins/extract-sql/projection.go +70 -0
- package/plugins/extract-sql/projection_test.go +50 -0
- package/plugins/extract-sql/store.go +420 -0
- package/plugins/extract-sql/store_test.go +233 -0
- package/plugins/extract-sql/view.go +295 -0
- package/plugins/extract-ts/README.md +306 -0
- package/plugins/extract-ts/ast.test.ts +178 -0
- package/plugins/extract-ts/ast.ts +648 -0
- package/plugins/extract-ts/clients.ts +185 -0
- package/plugins/extract-ts/domain.ts +164 -0
- package/plugins/extract-ts/extract.test.ts +208 -0
- package/plugins/extract-ts/extract.ts +200 -0
- package/plugins/extract-ts/flows.ts +783 -0
- package/plugins/extract-ts/graphql.test.ts +73 -0
- package/plugins/extract-ts/graphql.ts +181 -0
- package/plugins/extract-ts/ids.test.ts +21 -0
- package/plugins/extract-ts/ids.ts +52 -0
- package/plugins/extract-ts/lifecycle.ts +138 -0
- package/plugins/extract-ts/main.ts +48 -0
- package/plugins/extract-ts/openapi.test.ts +64 -0
- package/plugins/extract-ts/openapi.ts +99 -0
- package/plugins/extract-ts/operations.ts +93 -0
- package/plugins/extract-ts/options.schema.json +96 -0
- package/plugins/extract-ts/source.test.ts +174 -0
- package/plugins/extract-ts/source.ts +465 -0
- package/plugins/extract-ts/transport.ts +147 -0
- package/plugins/extract-ts/tsconfig.json +13 -0
- package/plugins/extract-ts/wiring.ts +190 -0
- package/plugins/extract-watermill/describe.go +19 -0
- package/plugins/extract-watermill/describe_test.go +11 -0
- package/plugins/extract-watermill/extract.go +1228 -0
- package/plugins/extract-watermill/extract_test.go +234 -0
- package/plugins/extract-watermill/main.go +41 -0
- package/plugins/extract-watermill/options.schema.json +21 -0
- package/plugins/extract-wsdl/describe.go +19 -0
- package/plugins/extract-wsdl/describe_test.go +11 -0
- package/plugins/extract-wsdl/extract.go +148 -0
- package/plugins/extract-wsdl/extract_test.go +45 -0
- package/plugins/extract-wsdl/main.go +53 -0
- package/plugins/extract-wsdl/options.schema.json +59 -0
- package/plugins/fetch-bsr/auth.go +114 -0
- package/plugins/fetch-bsr/bsr.go +240 -0
- package/plugins/fetch-bsr/cache.go +66 -0
- package/plugins/fetch-bsr/describe.go +19 -0
- package/plugins/fetch-bsr/fetch.go +192 -0
- package/plugins/fetch-bsr/fetch_test.go +465 -0
- package/plugins/fetch-bsr/lock.go +71 -0
- package/plugins/fetch-bsr/main.go +117 -0
- package/plugins/fetch-bsr/options.schema.json +42 -0
- package/plugins/fetch-csr/auth.go +95 -0
- package/plugins/fetch-csr/cache.go +69 -0
- package/plugins/fetch-csr/describe.go +19 -0
- package/plugins/fetch-csr/describe_test.go +11 -0
- package/plugins/fetch-csr/fetch.go +254 -0
- package/plugins/fetch-csr/fetch_test.go +494 -0
- package/plugins/fetch-csr/lock.go +95 -0
- package/plugins/fetch-csr/main.go +122 -0
- package/plugins/fetch-csr/options.schema.json +42 -0
- package/plugins/fetch-csr/registry.go +209 -0
- package/plugins/fetch-git/cache.go +65 -0
- package/plugins/fetch-git/describe.go +19 -0
- package/plugins/fetch-git/describe_test.go +11 -0
- package/plugins/fetch-git/fetch.go +181 -0
- package/plugins/fetch-git/fetch_test.go +332 -0
- package/plugins/fetch-git/git.go +169 -0
- package/plugins/fetch-git/lock.go +72 -0
- package/plugins/fetch-git/main.go +127 -0
- package/plugins/fetch-git/offline.go +39 -0
- package/plugins/fetch-git/options.schema.json +42 -0
- package/plugins/fetch-git/pin.go +90 -0
- package/plugins/fetch-git/pin_test.go +74 -0
- package/plugins/gen-backstage/describe.go +17 -0
- package/plugins/gen-backstage/main.go +22 -0
- package/plugins/gen-backstage/options.schema.json +30 -0
- package/plugins/gen-backstage/plugin.go +473 -0
- package/plugins/gen-backstage/plugin_test.go +145 -0
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +162 -0
- package/plugins/gen-markdown/aggregate.go +362 -0
- package/plugins/gen-markdown/canonical.go +93 -0
- package/plugins/gen-markdown/context.go +90 -0
- package/plugins/gen-markdown/coverage_test.go +89 -0
- package/plugins/gen-markdown/describe.go +19 -0
- package/plugins/gen-markdown/describe_test.go +11 -0
- package/plugins/gen-markdown/external.go +71 -0
- package/plugins/gen-markdown/flow.go +277 -0
- package/plugins/gen-markdown/glossary.go +76 -0
- package/plugins/gen-markdown/glossary_test.go +148 -0
- package/plugins/gen-markdown/llms.go +302 -0
- package/plugins/gen-markdown/main.go +22 -0
- package/plugins/gen-markdown/markdown.go +254 -0
- package/plugins/gen-markdown/markdown_test.go +100 -0
- package/plugins/gen-markdown/module.go +107 -0
- package/plugins/gen-markdown/options.schema.json +16 -0
- package/plugins/gen-markdown/plugin.go +39 -0
- package/plugins/gen-markdown/quality_test.go +176 -0
- package/plugins/gen-markdown/redis_test.go +38 -0
- package/plugins/gen-markdown/render.go +344 -0
- package/plugins/gen-markdown/render_test.go +186 -0
- package/plugins/gen-markdown/service.go +456 -0
- package/plugins/gen-markdown/source.go +177 -0
- package/plugins/gen-markdown/store.go +201 -0
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +17 -0
- package/plugins/gen-mermaid/main.go +22 -0
- package/plugins/gen-mermaid/options.schema.json +10 -0
- package/plugins/gen-mermaid/plugin.go +104 -0
- package/plugins/gen-mermaid/plugin_test.go +33 -0
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/pyplugin/catalog.py +209 -0
- package/plugins/pyplugin/celery_conf.py +436 -0
- package/plugins/pyplugin/celery_tasks.py +76 -0
- package/plugins/pyplugin/kafka.py +570 -0
- package/plugins/pyplugin/names.py +38 -0
- package/plugins/pyplugin/protocol.py +79 -0
- package/plugins/pyplugin/source.py +285 -0
- package/plugins/verify-codeowners/describe.go +19 -0
- package/plugins/verify-codeowners/describe_test.go +11 -0
- package/plugins/verify-codeowners/main.go +75 -0
- package/plugins/verify-codeowners/match.go +85 -0
- package/plugins/verify-codeowners/match_test.go +47 -0
- package/plugins/verify-codeowners/options.schema.json +15 -0
- package/plugins/verify-codeowners/owners.go +164 -0
- package/plugins/verify-codeowners/owners_test.go +225 -0
- package/plugins/verify-codeowners/parse.go +90 -0
- package/plugins/verify-codeowners/parse_test.go +62 -0
- package/plugins/verify-otel/describe.go +19 -0
- package/plugins/verify-otel/describe_test.go +11 -0
- package/plugins/verify-otel/main.go +53 -0
- package/plugins/verify-otel/match.go +336 -0
- package/plugins/verify-otel/options.schema.json +30 -0
- package/plugins/verify-otel/otlp.go +200 -0
- package/plugins/verify-otel/verify.go +734 -0
- package/plugins/verify-otel/verify_test.go +460 -0
- package/portolan.json +1073 -0
- package/public/favicon.svg +12 -0
- package/schema/portolan.schema.json +2558 -0
- package/scripts/README.md +53 -0
- package/scripts/build-report.mjs +60 -0
- package/scripts/build-report.test.mjs +44 -0
- package/scripts/builtin-plugins.mjs +49 -0
- package/scripts/catalog-sources.mjs +67 -0
- package/scripts/diff.mjs +315 -0
- package/scripts/diff.test.mjs +151 -0
- package/scripts/forge-comment.mjs +144 -0
- package/scripts/forge-comment.test.mjs +151 -0
- package/scripts/forge-release.mjs +138 -0
- package/scripts/forge-release.test.mjs +106 -0
- package/scripts/forge.mjs +61 -0
- package/scripts/gen-likec4.mjs +854 -0
- package/scripts/gen-likec4.test.mjs +258 -0
- package/scripts/gen.mjs +603 -0
- package/scripts/generated-docs.test.mjs +66 -0
- package/scripts/local-api.mjs +1192 -0
- package/scripts/local-api.test.mjs +387 -0
- package/scripts/manifest.mjs +214 -0
- package/scripts/manifest.test.mjs +148 -0
- package/scripts/output-path.mjs +79 -0
- package/scripts/output-path.test.mjs +45 -0
- package/scripts/package-smoke.mjs +37 -0
- package/scripts/plugin-host.mjs +419 -0
- package/scripts/plugin-host.test.mjs +109 -0
- package/scripts/plugin-wasm-worker.mjs +34 -0
- package/scripts/run-builtin.mjs +81 -0
- package/scripts/schema.mjs +357 -0
- package/scripts/site-docs.mjs +295 -0
- package/scripts/site-docs.test.mjs +181 -0
- package/scripts/vendor-lock.mjs +58 -0
- package/scripts/vendor-lock.test.mjs +69 -0
- package/src/README.md +38 -0
- package/src/app/App.tsx +448 -0
- package/src/app/BranchPicker.test.ts +46 -0
- package/src/app/BranchPicker.tsx +153 -0
- package/src/app/Breadcrumbs.test.ts +38 -0
- package/src/app/Breadcrumbs.tsx +223 -0
- package/src/app/BuildStamp.tsx +146 -0
- package/src/app/CommandPalette.tsx +292 -0
- package/src/app/HashScroll.tsx +45 -0
- package/src/app/Sidebar.tsx +1742 -0
- package/src/app/TopBar.tsx +393 -0
- package/src/app/density.tsx +64 -0
- package/src/app/forge-access.test.ts +27 -0
- package/src/app/forge-access.tsx +80 -0
- package/src/app/panels.tsx +141 -0
- package/src/app/pins.tsx +242 -0
- package/src/app/query-client.ts +24 -0
- package/src/app/responsive.ts +55 -0
- package/src/app/shortcuts.test.ts +39 -0
- package/src/app/shortcuts.tsx +282 -0
- package/src/app/theme.tsx +51 -0
- package/src/app/title.test.ts +14 -0
- package/src/app/title.ts +30 -0
- package/src/app/toast.tsx +59 -0
- package/src/app/tree-keys.test.ts +80 -0
- package/src/app/tree-keys.ts +87 -0
- package/src/app/ui-store.ts +52 -0
- package/src/catalog-error.test.ts +105 -0
- package/src/catalog-profile.test.ts +59 -0
- package/src/catalog-profile.ts +101 -0
- package/src/catalog-stores.test.ts +431 -0
- package/src/catalog.test.ts +1215 -0
- package/src/catalog.ts +3125 -0
- package/src/chat/Cards.tsx +408 -0
- package/src/chat/ChatPanel.tsx +79 -0
- package/src/chat/ChatSettings.tsx +219 -0
- package/src/chat/Composer.tsx +99 -0
- package/src/chat/Conversation.tsx +281 -0
- package/src/chat/Header.tsx +46 -0
- package/src/chat/Message.tsx +49 -0
- package/src/chat/Notice.tsx +195 -0
- package/src/chat/Starter.tsx +67 -0
- package/src/chat/Steps.tsx +45 -0
- package/src/chat/Waiting.tsx +87 -0
- package/src/chat/flags.test.ts +59 -0
- package/src/chat/flags.ts +155 -0
- package/src/chat/linkify.test.ts +30 -0
- package/src/chat/linkify.ts +45 -0
- package/src/chat/prefs.ts +56 -0
- package/src/chat/prompt.test.ts +43 -0
- package/src/chat/prompt.ts +157 -0
- package/src/chat/store.ts +35 -0
- package/src/chat/tools.ts +58 -0
- package/src/chat/transport.ts +70 -0
- package/src/components/AdrRow.tsx +29 -0
- package/src/components/AnchorLink.tsx +67 -0
- package/src/components/ApiReference.tsx +134 -0
- package/src/components/AsyncApiReference.tsx +120 -0
- package/src/components/CatalogStamp.tsx +155 -0
- package/src/components/ChannelRows.tsx +132 -0
- package/src/components/CommandRows.tsx +139 -0
- package/src/components/DiagramSkeleton.tsx +71 -0
- package/src/components/EditorLink.tsx +85 -0
- package/src/components/FieldTree.tsx +305 -0
- package/src/components/FlowTrigger.tsx +34 -0
- package/src/components/Ident.tsx +89 -0
- package/src/components/Integrations.tsx +243 -0
- package/src/components/LifecycleDiagram.tsx +110 -0
- package/src/components/MachineDocs.tsx +42 -0
- package/src/components/Markdown.tsx +173 -0
- package/src/components/MarkdownTable.tsx +206 -0
- package/src/components/Mermaid.tsx +211 -0
- package/src/components/MethodRows.tsx +309 -0
- package/src/components/Overlay.tsx +125 -0
- package/src/components/PageHeader.tsx +189 -0
- package/src/components/RowActions.tsx +97 -0
- package/src/components/SchemaDocument.tsx +89 -0
- package/src/components/Select.tsx +126 -0
- package/src/components/ShapeRows.tsx +118 -0
- package/src/components/SourceDoc.tsx +82 -0
- package/src/components/SourcePreview.tsx +441 -0
- package/src/components/TabRow.tsx +182 -0
- package/src/components/TechIcon.tsx +34 -0
- package/src/components/Toc.tsx +132 -0
- package/src/components/WhatLinksHere.tsx +273 -0
- package/src/components/WsdlReference.tsx +84 -0
- package/src/components/ddd-icons.tsx +107 -0
- package/src/components/kind.tsx +157 -0
- package/src/components/logo.tsx +61 -0
- package/src/components/primitives.tsx +264 -0
- package/src/data.test.ts +18 -0
- package/src/data.ts +202 -0
- package/src/enrich.test.ts +1150 -0
- package/src/enrich.ts +672 -0
- package/src/er/ErCanvas.tsx +533 -0
- package/src/er/RedisSchema.tsx +74 -0
- package/src/er/RelationCard.tsx +267 -0
- package/src/er/StoreHeader.tsx +155 -0
- package/src/er/TableNode.tsx +84 -0
- package/src/er/ViewNode.tsx +83 -0
- package/src/er/layout.test.ts +115 -0
- package/src/er/layout.ts +77 -0
- package/src/er/lineage.test.ts +140 -0
- package/src/er/lineage.ts +107 -0
- package/src/er/markers.tsx +79 -0
- package/src/er/spec.test.ts +344 -0
- package/src/er/spec.ts +426 -0
- package/src/flow/ChainList.tsx +162 -0
- package/src/flow/FlowTable.tsx +123 -0
- package/src/flow/FlowToolbar.tsx +372 -0
- package/src/flow/StepDetail.tsx +380 -0
- package/src/flow/StepRail.tsx +428 -0
- package/src/flow/answers.test.ts +50 -0
- package/src/flow/answers.ts +58 -0
- package/src/flow/chain.test.ts +383 -0
- package/src/flow/chain.ts +353 -0
- package/src/flow/chapters.test.ts +185 -0
- package/src/flow/chapters.ts +225 -0
- package/src/flow/continues.test.ts +102 -0
- package/src/flow/continues.ts +65 -0
- package/src/flow/cross-context.test.ts +97 -0
- package/src/flow/cross-context.ts +35 -0
- package/src/flow/mermaid.test.ts +22 -0
- package/src/flow/mermaid.ts +141 -0
- package/src/flow/outline.test.ts +212 -0
- package/src/flow/outline.ts +170 -0
- package/src/flow/paths.test.ts +129 -0
- package/src/flow/paths.ts +147 -0
- package/src/flow/prefs.test.ts +80 -0
- package/src/flow/prefs.ts +104 -0
- package/src/graph/DependencyGraph.tsx +283 -0
- package/src/graph/DependencyNodes.tsx +162 -0
- package/src/graph/EdgeLabel.tsx +61 -0
- package/src/graph/FocusedEventGraph.tsx +211 -0
- package/src/graph/GraphToolbar.tsx +187 -0
- package/src/graph/RoutedEdge.tsx +80 -0
- package/src/graph/dependency-layout.test.ts +360 -0
- package/src/graph/dependency-layout.ts +444 -0
- package/src/graph/elk.test.ts +70 -0
- package/src/graph/elk.ts +287 -0
- package/src/graph/nodes.tsx +97 -0
- package/src/graph/theme.ts +81 -0
- package/src/graph/useElkFlow.ts +125 -0
- package/src/index.css +1547 -0
- package/src/language/InLanguage.tsx +40 -0
- package/src/language/TermCard.tsx +132 -0
- package/src/language/cards.test.ts +114 -0
- package/src/language/cards.ts +111 -0
- package/src/lib/admonition.test.ts +49 -0
- package/src/lib/admonition.ts +48 -0
- package/src/lib/adr.test.ts +131 -0
- package/src/lib/adr.ts +68 -0
- package/src/lib/api.test.ts +122 -0
- package/src/lib/api.ts +135 -0
- package/src/lib/backlinks.test.ts +330 -0
- package/src/lib/backlinks.ts +567 -0
- package/src/lib/branch-compare.ts +16 -0
- package/src/lib/build-info.test.ts +76 -0
- package/src/lib/build-info.ts +80 -0
- package/src/lib/catalog-diff.test.ts +275 -0
- package/src/lib/catalog-diff.ts +655 -0
- package/src/lib/clipboard.ts +43 -0
- package/src/lib/comparison-memory.test.ts +40 -0
- package/src/lib/comparison-memory.ts +39 -0
- package/src/lib/context-color.ts +27 -0
- package/src/lib/context-map.test.ts +148 -0
- package/src/lib/context-map.ts +399 -0
- package/src/lib/data-model.test.ts +245 -0
- package/src/lib/data-model.ts +399 -0
- package/src/lib/data-problems.test.ts +178 -0
- package/src/lib/data-problems.ts +314 -0
- package/src/lib/derive.test.ts +251 -0
- package/src/lib/derive.ts +515 -0
- package/src/lib/editor-link.test.ts +125 -0
- package/src/lib/editor-link.ts +96 -0
- package/src/lib/editor-prefs.ts +27 -0
- package/src/lib/event-graph.test.ts +237 -0
- package/src/lib/event-graph.ts +327 -0
- package/src/lib/export-canvas.ts +37 -0
- package/src/lib/export-file.test.ts +30 -0
- package/src/lib/export-file.ts +20 -0
- package/src/lib/flow-tree.test.ts +177 -0
- package/src/lib/flow-tree.ts +160 -0
- package/src/lib/forge-refs.test.ts +50 -0
- package/src/lib/forge-refs.ts +60 -0
- package/src/lib/format.test.ts +35 -0
- package/src/lib/format.ts +64 -0
- package/src/lib/github-catalog.test.ts +301 -0
- package/src/lib/github-catalog.ts +610 -0
- package/src/lib/integrations.test.ts +119 -0
- package/src/lib/integrations.ts +133 -0
- package/src/lib/kinds.test.ts +91 -0
- package/src/lib/kinds.ts +204 -0
- package/src/lib/lifecycle.test.ts +93 -0
- package/src/lib/lifecycle.ts +165 -0
- package/src/lib/local-api.ts +194 -0
- package/src/lib/motion-features.ts +11 -0
- package/src/lib/motion.test.ts +120 -0
- package/src/lib/motion.tsx +269 -0
- package/src/lib/palette-recent.test.ts +80 -0
- package/src/lib/palette-recent.ts +90 -0
- package/src/lib/palette.test.ts +365 -0
- package/src/lib/palette.ts +593 -0
- package/src/lib/pins.test.ts +139 -0
- package/src/lib/pins.ts +130 -0
- package/src/lib/problems.test.ts +112 -0
- package/src/lib/project-preview.ts +5 -0
- package/src/lib/proto-problems.test.ts +121 -0
- package/src/lib/proto-problems.ts +65 -0
- package/src/lib/queries.test.ts +113 -0
- package/src/lib/queries.ts +131 -0
- package/src/lib/registry.test.ts +219 -0
- package/src/lib/registry.ts +208 -0
- package/src/lib/scenarios.ts +547 -0
- package/src/lib/setup-info.test.ts +199 -0
- package/src/lib/setup-info.ts +415 -0
- package/src/lib/shape.test.ts +289 -0
- package/src/lib/shape.ts +454 -0
- package/src/lib/sidebar-prefs.test.ts +32 -0
- package/src/lib/sidebar-prefs.ts +75 -0
- package/src/lib/source-code.test.ts +159 -0
- package/src/lib/source-code.ts +286 -0
- package/src/lib/source-doc.test.ts +131 -0
- package/src/lib/source-doc.ts +103 -0
- package/src/lib/source-highlight.test.ts +29 -0
- package/src/lib/source-highlight.ts +24 -0
- package/src/lib/source-link.test.ts +153 -0
- package/src/lib/source-link.ts +201 -0
- package/src/lib/spec-files.test.ts +24 -0
- package/src/lib/spec-files.ts +23 -0
- package/src/lib/table-directive.test.ts +139 -0
- package/src/lib/table-directive.ts +106 -0
- package/src/lib/tech.test.ts +120 -0
- package/src/lib/tech.ts +421 -0
- package/src/lib/terms.test.ts +67 -0
- package/src/lib/terms.ts +179 -0
- package/src/lib/wire-problems.test.ts +263 -0
- package/src/lib/wire-problems.ts +281 -0
- package/src/likec4/C4View.tsx +55 -0
- package/src/likec4/CanvasBridge.tsx +187 -0
- package/src/likec4/FlowView.tsx +235 -0
- package/src/likec4/InteractiveView.tsx +144 -0
- package/src/likec4/canvas-viewport.test.ts +149 -0
- package/src/likec4/canvas-viewport.ts +135 -0
- package/src/likec4/flow-edges.test.ts +51 -0
- package/src/likec4/flow-edges.ts +89 -0
- package/src/likec4/frame-theme.test.ts +76 -0
- package/src/likec4/frame-theme.ts +101 -0
- package/src/likec4/highlight-css.test.ts +49 -0
- package/src/likec4/highlight-css.ts +52 -0
- package/src/likec4/ids.test.ts +151 -0
- package/src/likec4/ids.ts +115 -0
- package/src/likec4/levels.tsx +70 -0
- package/src/likec4/mapping.test.ts +66 -0
- package/src/likec4/mapping.ts +54 -0
- package/src/likec4/reserved.json +141 -0
- package/src/likec4/view-index.ts +78 -0
- package/src/main.tsx +13 -0
- package/src/map/ContextMapGraph.tsx +274 -0
- package/src/map/ContextNode.tsx +70 -0
- package/src/map/FloatingEdge.tsx +86 -0
- package/src/merge.test.ts +1038 -0
- package/src/merge.ts +1004 -0
- package/src/pages/AdrDetail.tsx +242 -0
- package/src/pages/AdrIndex.tsx +114 -0
- package/src/pages/AggregatePage.tsx +727 -0
- package/src/pages/BlockPage.tsx +478 -0
- package/src/pages/CatalogFailure.tsx +111 -0
- package/src/pages/Changes.tsx +313 -0
- package/src/pages/ContextMap.tsx +534 -0
- package/src/pages/ContextPage.tsx +346 -0
- package/src/pages/EnumPage.tsx +282 -0
- package/src/pages/EventPage.tsx +686 -0
- package/src/pages/ExternalPage.tsx +204 -0
- package/src/pages/FlowDetail.tsx +663 -0
- package/src/pages/FlowIndex.tsx +303 -0
- package/src/pages/GraphPage.tsx +217 -0
- package/src/pages/Language.tsx +257 -0
- package/src/pages/ModulePage.tsx +366 -0
- package/src/pages/NotFound.tsx +54 -0
- package/src/pages/Overview.tsx +344 -0
- package/src/pages/Problems.tsx +360 -0
- package/src/pages/RegistryIndex.tsx +118 -0
- package/src/pages/ServicePage.tsx +746 -0
- package/src/pages/Settings.tsx +975 -0
- package/src/pages/StorePage.tsx +114 -0
- package/src/peek/PeekCard.test.ts +35 -0
- package/src/peek/PeekCard.tsx +118 -0
- package/src/peek/PeekLayer.tsx +147 -0
- package/src/peek/model.test.ts +156 -0
- package/src/peek/model.ts +253 -0
- package/src/routes.test.ts +222 -0
- package/src/routes.ts +449 -0
- package/src/selection/DetailPanel.tsx +1407 -0
- package/src/selection/hash.test.ts +73 -0
- package/src/selection/hash.ts +58 -0
- package/src/selection/model.test.ts +199 -0
- package/src/selection/model.ts +473 -0
- package/src/selection/pages.test.ts +115 -0
- package/src/selection/pages.ts +268 -0
- package/src/selection/store.test.ts +62 -0
- package/src/selection/store.ts +46 -0
- package/src/selection/sync.tsx +99 -0
- package/src/table/ColumnsMenu.tsx +72 -0
- package/src/table/DataTable.tsx +780 -0
- package/src/table/Facets.tsx +54 -0
- package/src/table/GroupMenu.tsx +69 -0
- package/src/table/Toolbar.tsx +190 -0
- package/src/table/cells.tsx +117 -0
- package/src/table/compare.test.ts +117 -0
- package/src/table/compare.ts +196 -0
- package/src/table/export.test.ts +88 -0
- package/src/table/export.ts +66 -0
- package/src/table/facet-groups.test.ts +74 -0
- package/src/table/facet-groups.ts +56 -0
- package/src/table/grouping.test.ts +84 -0
- package/src/table/grouping.ts +82 -0
- package/src/table/infer.test.ts +57 -0
- package/src/table/infer.ts +50 -0
- package/src/table/persist.test.ts +76 -0
- package/src/table/persist.ts +112 -0
- package/src/table/sort-url.test.ts +75 -0
- package/src/table/sort-url.ts +52 -0
- package/src/table/types.ts +80 -0
- package/src/table/useTableState.ts +220 -0
- package/src/test-catalog.ts +7 -0
- package/src/testing/estate/data__catalog.json +2847 -0
- package/src/testing/estate/data__flows.json +1724 -0
- package/src/testing/estate/examples__auth__portolan__api.json +212 -0
- package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
- package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
- package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
- package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
- package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
- package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
- package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
- package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
- package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
- package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
- package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
- package/src/testing/estate.ts +49 -0
- package/src/trail/Trail.tsx +81 -0
- package/src/trail/model.test.ts +157 -0
- package/src/trail/model.ts +189 -0
- package/src/trail/record.tsx +32 -0
- package/src/trail/store.test.ts +66 -0
- package/src/trail/store.ts +102 -0
- package/tsconfig.json +35 -0
- package/vite.config.ts +171 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import type { RefObject } from "react";
|
|
3
|
+
import type { DiagramApi } from "likec4/react";
|
|
4
|
+
import type { Flow } from "../catalog";
|
|
5
|
+
import { walkSteps } from "../catalog";
|
|
6
|
+
import { InteractiveView } from "./InteractiveView";
|
|
7
|
+
import { CanvasBridge } from "./CanvasBridge";
|
|
8
|
+
import type { CanvasHandle } from "./CanvasBridge";
|
|
9
|
+
import { flowCrossViewId, flowViewId, fqn, participantFqn } from "./ids";
|
|
10
|
+
import { catalogIdOf } from "./mapping";
|
|
11
|
+
import {
|
|
12
|
+
flowPairing,
|
|
13
|
+
viewBounds,
|
|
14
|
+
viewEdgeIds,
|
|
15
|
+
viewHasNode,
|
|
16
|
+
viewNodeIds,
|
|
17
|
+
} from "./view-index";
|
|
18
|
+
import { offPathFrameIds } from "./flow-edges";
|
|
19
|
+
import {
|
|
20
|
+
buildFrameCss,
|
|
21
|
+
buildFocusedPathCss,
|
|
22
|
+
buildWalkthroughCss,
|
|
23
|
+
} from "./frame-theme";
|
|
24
|
+
import { centredViewport, readableViewport } from "./canvas-viewport";
|
|
25
|
+
import type { Size } from "./canvas-viewport";
|
|
26
|
+
import { flowStepId } from "../selection/model";
|
|
27
|
+
import { useSelectionStore } from "../selection/store";
|
|
28
|
+
|
|
29
|
+
/** A stable empty list, so `useMemo` below is not defeated by a fresh `[]`. */
|
|
30
|
+
const NOTHING: readonly string[] = [];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The flow picture. LikeC4 owns it end to end: the sequence is declared in
|
|
34
|
+
* likec4/views.c4, generated from the same catalog.json the rest of the page
|
|
35
|
+
* reads. Portolan draws nothing here itself — it says which steps are lit,
|
|
36
|
+
* where to look, and what colour the frames should be, and hears which step
|
|
37
|
+
* was clicked and which one playback is on.
|
|
38
|
+
*/
|
|
39
|
+
export function FlowView({
|
|
40
|
+
flow,
|
|
41
|
+
crossOnly,
|
|
42
|
+
variant,
|
|
43
|
+
litSteps = [],
|
|
44
|
+
pathSteps = null,
|
|
45
|
+
focusStep = null,
|
|
46
|
+
canvas,
|
|
47
|
+
onWalkthroughStep,
|
|
48
|
+
}: {
|
|
49
|
+
flow: Flow;
|
|
50
|
+
crossOnly: boolean;
|
|
51
|
+
variant: "diagram" | "sequence";
|
|
52
|
+
/** Catalog step ids to mark; the rest of the sequence is dimmed. */
|
|
53
|
+
litSteps?: readonly string[];
|
|
54
|
+
/**
|
|
55
|
+
* Steps of the path being read, when one is chosen. Everything outside that
|
|
56
|
+
* executable path leaves the picture, matching the focused rail beside it.
|
|
57
|
+
*/
|
|
58
|
+
pathSteps?: readonly string[] | null;
|
|
59
|
+
/** The step the canvas should bring into view, when the rail asks for one. */
|
|
60
|
+
focusStep?: string | null;
|
|
61
|
+
canvas: RefObject<CanvasHandle | null>;
|
|
62
|
+
/** The step playback is on, in catalog terms, or null when it is stopped. */
|
|
63
|
+
onWalkthroughStep: (stepId: string | null) => void;
|
|
64
|
+
}) {
|
|
65
|
+
const viewId = crossOnly ? flowCrossViewId(flow) : flowViewId(flow);
|
|
66
|
+
const pairing = flowPairing(flow, crossOnly);
|
|
67
|
+
|
|
68
|
+
const selection = useSelectionStore((s) => s.selection);
|
|
69
|
+
const source = useSelectionStore((s) => s.source);
|
|
70
|
+
const select = useSelectionStore((s) => s.select);
|
|
71
|
+
const clear = useSelectionStore((s) => s.clear);
|
|
72
|
+
|
|
73
|
+
const onNode = useCallback(
|
|
74
|
+
(likec4Id: string) => select(catalogIdOf(likec4Id), "diagram"),
|
|
75
|
+
[select],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// A clicked arrow is a step, not a relation: the sequence is what this view
|
|
79
|
+
// is about, so the click resolves through the edge-to-step pairing.
|
|
80
|
+
const onEdge = useCallback(
|
|
81
|
+
(edgeId: string) => {
|
|
82
|
+
const stepId = pairing.stepOf.get(edgeId);
|
|
83
|
+
if (stepId) select(flowStepId(flow.slug, stepId), "diagram");
|
|
84
|
+
},
|
|
85
|
+
[pairing, flow.slug, select],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const onCanvasClick = useCallback(() => clear("diagram"), [clear]);
|
|
89
|
+
|
|
90
|
+
const onWalkthroughEdge = useCallback(
|
|
91
|
+
(edgeId: string | null) => {
|
|
92
|
+
onWalkthroughStep(edgeId ? (pairing.stepOf.get(edgeId) ?? null) : null);
|
|
93
|
+
},
|
|
94
|
+
[pairing, onWalkthroughStep],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// A selection is narrower than a path, so it wins: choosing a path lifts the
|
|
98
|
+
// branch you are reading, and clicking a step then narrows to that one step.
|
|
99
|
+
const marked = litSteps.length > 0 ? litSteps : (pathSteps ?? NOTHING);
|
|
100
|
+
|
|
101
|
+
const highlightEdges = useMemo(
|
|
102
|
+
() =>
|
|
103
|
+
marked
|
|
104
|
+
.map((stepId) => pairing.edgeOf.get(stepId))
|
|
105
|
+
.filter((id): id is string => id !== undefined),
|
|
106
|
+
[marked, pairing],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const focusedPathEdges = useMemo(
|
|
110
|
+
() =>
|
|
111
|
+
pathSteps
|
|
112
|
+
? pathSteps
|
|
113
|
+
.map((stepId) => pairing.edgeOf.get(stepId))
|
|
114
|
+
.filter((id): id is string => id !== undefined)
|
|
115
|
+
: null,
|
|
116
|
+
[pathSteps, pairing],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Frames go with their steps. A chosen path is a structural focus, so its
|
|
120
|
+
// unused arrows and empty frames disappear; selecting one step remains a
|
|
121
|
+
// paint-only highlight and never changes the surrounding graph.
|
|
122
|
+
const extraCss = useMemo(() => {
|
|
123
|
+
const theme = [buildFrameCss(), buildWalkthroughCss()].join("\n");
|
|
124
|
+
if (!pathSteps) return theme;
|
|
125
|
+
const allEdges = viewEdgeIds(viewId);
|
|
126
|
+
const onPath = new Set(focusedPathEdges ?? []);
|
|
127
|
+
const edges = allEdges.filter((id) => !onPath.has(id));
|
|
128
|
+
const edgeLabelPositions = allEdges
|
|
129
|
+
.map((id, index) => (onPath.has(id) ? null : index + 1))
|
|
130
|
+
.filter((position): position is number => position !== null);
|
|
131
|
+
const pathStepIds = new Set(
|
|
132
|
+
pathSteps.filter((stepId) => pairing.edgeOf.has(stepId)),
|
|
133
|
+
);
|
|
134
|
+
const participantsById = new Map(
|
|
135
|
+
flow.participants.map((participant) => [
|
|
136
|
+
participant.id,
|
|
137
|
+
participantFqn(participant),
|
|
138
|
+
]),
|
|
139
|
+
);
|
|
140
|
+
const onPathNodes = new Set(
|
|
141
|
+
walkSteps(flow.steps)
|
|
142
|
+
.filter((step) => pathStepIds.has(step.id))
|
|
143
|
+
.flatMap((step) => [step.from, step.to])
|
|
144
|
+
.map((id) => participantsById.get(id) ?? fqn(id)),
|
|
145
|
+
);
|
|
146
|
+
const nodes = viewNodeIds(viewId).filter((id) => !onPathNodes.has(id));
|
|
147
|
+
const frames = offPathFrameIds(allEdges, onPath);
|
|
148
|
+
return [
|
|
149
|
+
theme,
|
|
150
|
+
buildFocusedPathCss(edges, edgeLabelPositions, nodes, frames),
|
|
151
|
+
]
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.join("\n");
|
|
154
|
+
}, [flow, pathSteps, focusedPathEdges, pairing, viewId]);
|
|
155
|
+
|
|
156
|
+
// Lanes are marked only for a selection made somewhere else, and only when
|
|
157
|
+
// this variant of the view actually has that lane.
|
|
158
|
+
const highlightNodes =
|
|
159
|
+
selection &&
|
|
160
|
+
source !== "diagram" &&
|
|
161
|
+
(selection.kind === "service" || selection.kind === "unknown") &&
|
|
162
|
+
viewHasNode(viewId, fqn(selection.id))
|
|
163
|
+
? [fqn(selection.id)]
|
|
164
|
+
: [];
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The opening viewport, applied once the canvas is live.
|
|
168
|
+
*
|
|
169
|
+
* Both variants open at the size their labels were drawn for — fitting a
|
|
170
|
+
* flow of this length puts every label at four pixels, which shows that it
|
|
171
|
+
* is long and nothing else. What differs is where they open. A sequence has
|
|
172
|
+
* a beginning: its top-left corner is step one. A diagram does not — it
|
|
173
|
+
* folds forty-five steps onto nine boxes and has no reading order — so it
|
|
174
|
+
* opens centred on the arrow that draws step one instead.
|
|
175
|
+
*
|
|
176
|
+
* The second `send` is the point of the first one. LikeC4 refits on resize
|
|
177
|
+
* unless the viewport was moved by hand, and the page resizes the canvas
|
|
178
|
+
* itself whenever the rail is dragged — so a viewport set programmatically
|
|
179
|
+
* and left unclaimed would be thrown away the first time the reader touched
|
|
180
|
+
* the divider. Claiming it says: this is where the reader is looking.
|
|
181
|
+
*/
|
|
182
|
+
const onReady = useCallback(
|
|
183
|
+
({ diagram, canvas: box }: { diagram: DiagramApi; canvas: Size }) => {
|
|
184
|
+
if (box.width === 0) return;
|
|
185
|
+
|
|
186
|
+
let viewport;
|
|
187
|
+
if (variant === "sequence") {
|
|
188
|
+
const bounds = viewBounds(viewId, variant);
|
|
189
|
+
if (!bounds) return;
|
|
190
|
+
viewport = readableViewport(bounds, box);
|
|
191
|
+
} else {
|
|
192
|
+
const firstEdge = viewEdgeIds(viewId)[0];
|
|
193
|
+
const start = firstEdge
|
|
194
|
+
? diagram.findDiagramEdge(firstEdge)?.points[0]
|
|
195
|
+
: undefined;
|
|
196
|
+
// No geometry to aim at is not worth a guess: LikeC4's own fit is
|
|
197
|
+
// already on screen, and leaving it beats jumping somewhere arbitrary.
|
|
198
|
+
if (!start) return;
|
|
199
|
+
viewport = centredViewport({ x: start[0], y: start[1] }, box);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
diagram.send({ type: "xyflow.setViewport", viewport, duration: 0 });
|
|
203
|
+
diagram.send({ type: "xyflow.viewportMoved", viewport, manually: true });
|
|
204
|
+
},
|
|
205
|
+
[viewId, variant],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<InteractiveView
|
|
210
|
+
viewId={viewId}
|
|
211
|
+
variant={variant}
|
|
212
|
+
// LikeC4's own controls panel carries a toolbar and a wordmark. The page
|
|
213
|
+
// already has one toolbar, and two is one too many, so the panel is off
|
|
214
|
+
// and every control it held has a place in ours.
|
|
215
|
+
controls={false}
|
|
216
|
+
walkthrough
|
|
217
|
+
highlightNodes={highlightNodes}
|
|
218
|
+
highlightEdges={highlightEdges}
|
|
219
|
+
extraCss={extraCss}
|
|
220
|
+
onNode={onNode}
|
|
221
|
+
onEdge={onEdge}
|
|
222
|
+
onCanvas={onCanvasClick}
|
|
223
|
+
onReady={onReady}
|
|
224
|
+
>
|
|
225
|
+
<CanvasBridge
|
|
226
|
+
handle={canvas}
|
|
227
|
+
activeEdgeId={
|
|
228
|
+
focusStep ? (pairing.edgeOf.get(focusStep) ?? null) : null
|
|
229
|
+
}
|
|
230
|
+
focusEdgeIds={focusedPathEdges}
|
|
231
|
+
onWalkthroughEdge={onWalkthroughEdge}
|
|
232
|
+
/>
|
|
233
|
+
</InteractiveView>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { DiagramApi } from "likec4/react";
|
|
4
|
+
import { ReactLikeC4, isLikeC4ViewId } from "./generated";
|
|
5
|
+
import { useTheme } from "../app/theme";
|
|
6
|
+
import { Ident } from "../components/Ident";
|
|
7
|
+
import { buildHighlightCss } from "./highlight-css";
|
|
8
|
+
import type { Size } from "./canvas-viewport";
|
|
9
|
+
|
|
10
|
+
export interface InteractiveViewProps {
|
|
11
|
+
viewId: string;
|
|
12
|
+
/** Sequence for flows, diagram for element views. */
|
|
13
|
+
variant?: "diagram" | "sequence";
|
|
14
|
+
controls?: boolean;
|
|
15
|
+
walkthrough?: boolean;
|
|
16
|
+
/** LikeC4 element ids to mark; everything else on the canvas is dimmed. */
|
|
17
|
+
highlightNodes?: readonly string[];
|
|
18
|
+
/** LikeC4 edge ids to mark; the other edges are dimmed. */
|
|
19
|
+
highlightEdges?: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* More rules for the same stylesheet — retuned theme tokens, dimmed frames.
|
|
22
|
+
* Anything that paints rather than lays out belongs here rather than in a
|
|
23
|
+
* prop the layout would have to be recomputed for.
|
|
24
|
+
*/
|
|
25
|
+
extraCss?: string;
|
|
26
|
+
onNode?: (likec4Id: string) => void;
|
|
27
|
+
onEdge?: (edgeId: string) => void;
|
|
28
|
+
onCanvas?: () => void;
|
|
29
|
+
/**
|
|
30
|
+
* Once the canvas is live, with the diagram's API and the size of the box it
|
|
31
|
+
* was given. The size is measured here because this is the component that
|
|
32
|
+
* owns the box; nothing inside LikeC4 will say how big it was made.
|
|
33
|
+
*/
|
|
34
|
+
onReady?: (params: { diagram: DiagramApi; canvas: Size }) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Rendered inside the canvas, where LikeC4's own hooks work. For bridges,
|
|
37
|
+
* not for decoration: anything drawn over the picture belongs beside it.
|
|
38
|
+
*/
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A view id that resolves to nothing. This is not an error the reader caused
|
|
44
|
+
* and not one they can fix from here, so it is stated rather than alarmed
|
|
45
|
+
* about: the id in monospace, because that is the thing to go and look for,
|
|
46
|
+
* and one muted line saying why the box is empty.
|
|
47
|
+
*
|
|
48
|
+
* The box keeps its size. A diagram that fails to exist must not also make the
|
|
49
|
+
* page around it change shape.
|
|
50
|
+
*/
|
|
51
|
+
function Missing({ viewId }: { viewId: string }) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex h-full items-center justify-center p-6">
|
|
54
|
+
<div className="max-w-prose rounded-card border border-dashed px-4 py-3 text-center border-line-strong">
|
|
55
|
+
<Ident value={viewId} className="text-ink" />
|
|
56
|
+
<p className="meta mt-1.5">
|
|
57
|
+
no such view in the generated bundle — it was renamed, or the bundle
|
|
58
|
+
is older than the model
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The one place portolan embeds a LikeC4 canvas. The packaged LikeC4View gives
|
|
67
|
+
* no way to hear a click, so the lower-level component is used instead and the
|
|
68
|
+
* clicks are forwarded straight to the selection store by the caller.
|
|
69
|
+
*/
|
|
70
|
+
export function InteractiveView({
|
|
71
|
+
viewId,
|
|
72
|
+
variant = "diagram",
|
|
73
|
+
controls = false,
|
|
74
|
+
walkthrough = false,
|
|
75
|
+
highlightNodes = [],
|
|
76
|
+
highlightEdges = [],
|
|
77
|
+
extraCss = "",
|
|
78
|
+
onNode,
|
|
79
|
+
onEdge,
|
|
80
|
+
onCanvas,
|
|
81
|
+
onReady,
|
|
82
|
+
children,
|
|
83
|
+
}: InteractiveViewProps) {
|
|
84
|
+
const { theme } = useTheme();
|
|
85
|
+
const boxRef = useRef<HTMLDivElement | null>(null);
|
|
86
|
+
|
|
87
|
+
// Keyed on contents rather than array identity: callers build these lists
|
|
88
|
+
// inline, so comparing by reference would rebuild the sheet every render.
|
|
89
|
+
const nodeKey = highlightNodes.join(" ");
|
|
90
|
+
const edgeKey = highlightEdges.join(" ");
|
|
91
|
+
const css = useMemo(() => {
|
|
92
|
+
const rules = buildHighlightCss(
|
|
93
|
+
nodeKey ? nodeKey.split(" ") : [],
|
|
94
|
+
edgeKey ? edgeKey.split(" ") : [],
|
|
95
|
+
);
|
|
96
|
+
return [extraCss, rules].filter(Boolean).join("\n");
|
|
97
|
+
}, [nodeKey, edgeKey, extraCss]);
|
|
98
|
+
|
|
99
|
+
const onInitialized = useCallback(
|
|
100
|
+
({ diagram }: { diagram: DiagramApi }) => {
|
|
101
|
+
const box = boxRef.current?.getBoundingClientRect();
|
|
102
|
+
if (!box) return;
|
|
103
|
+
onReady?.({ diagram, canvas: { width: box.width, height: box.height } });
|
|
104
|
+
},
|
|
105
|
+
[onReady],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!isLikeC4ViewId(viewId)) return <Missing viewId={viewId} />;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div ref={boxRef} className="h-full w-full bg-canvas">
|
|
112
|
+
<ReactLikeC4
|
|
113
|
+
// Remounting on viewId keeps LikeC4's own walkthrough state from
|
|
114
|
+
// leaking across a filter or variant switch. The variant is in the key
|
|
115
|
+
// for a second reason: it is an initial value inside LikeC4, so the
|
|
116
|
+
// only honest way to change it is to start the canvas again. The
|
|
117
|
+
// selection is deliberately absent: re-keying would re-layout.
|
|
118
|
+
key={`${viewId}:${variant}:${theme}`}
|
|
119
|
+
viewId={viewId}
|
|
120
|
+
dynamicViewVariant={variant}
|
|
121
|
+
colorScheme={theme}
|
|
122
|
+
background="dots"
|
|
123
|
+
controls={controls}
|
|
124
|
+
fitView
|
|
125
|
+
pannable
|
|
126
|
+
zoomable
|
|
127
|
+
enableDynamicViewWalkthrough={walkthrough}
|
|
128
|
+
enableNotes
|
|
129
|
+
enableElementDetails={false}
|
|
130
|
+
enableRelationshipDetails={false}
|
|
131
|
+
enableSearch={false}
|
|
132
|
+
injectFontCss={false}
|
|
133
|
+
onNodeClick={(node) => onNode?.(String(node.id))}
|
|
134
|
+
onEdgeClick={(edge) => onEdge?.(String(edge.id))}
|
|
135
|
+
onCanvasClick={() => onCanvas?.()}
|
|
136
|
+
onInitialized={onInitialized}
|
|
137
|
+
style={{ width: "100%", height: "100%" }}
|
|
138
|
+
>
|
|
139
|
+
{css ? <style>{css}</style> : null}
|
|
140
|
+
{children}
|
|
141
|
+
</ReactLikeC4>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
CANVAS_PAD,
|
|
4
|
+
MIN_FOCUS_ZOOM,
|
|
5
|
+
READABLE_ZOOM,
|
|
6
|
+
boundsOf,
|
|
7
|
+
centredViewport,
|
|
8
|
+
focusViewport,
|
|
9
|
+
readableViewport,
|
|
10
|
+
} from "./canvas-viewport";
|
|
11
|
+
|
|
12
|
+
/** Where a point of the diagram lands on screen under a viewport. */
|
|
13
|
+
const project = (
|
|
14
|
+
value: number,
|
|
15
|
+
offset: number,
|
|
16
|
+
zoom: number,
|
|
17
|
+
): number => value * zoom + offset;
|
|
18
|
+
|
|
19
|
+
describe("readableViewport", () => {
|
|
20
|
+
const canvas = { width: 1000, height: 700 };
|
|
21
|
+
|
|
22
|
+
it("opens at the authored size rather than at whatever fits", () => {
|
|
23
|
+
const huge = { x: 0, y: 0, width: 4000, height: 9000 };
|
|
24
|
+
expect(readableViewport(huge, canvas).zoom).toBe(READABLE_ZOOM);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("puts the top of the diagram at the top of the canvas", () => {
|
|
28
|
+
const bounds = { x: 120, y: 60, width: 400, height: 3000 };
|
|
29
|
+
const viewport = readableViewport(bounds, canvas);
|
|
30
|
+
expect(project(bounds.y, viewport.y, viewport.zoom)).toBe(CANVAS_PAD);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("centres a diagram narrower than the canvas", () => {
|
|
34
|
+
const bounds = { x: 40, y: 0, width: 400, height: 300 };
|
|
35
|
+
const viewport = readableViewport(bounds, canvas);
|
|
36
|
+
const left = project(bounds.x, viewport.x, viewport.zoom);
|
|
37
|
+
const right = project(bounds.x + bounds.width, viewport.x, viewport.zoom);
|
|
38
|
+
expect(left).toBeCloseTo(canvas.width - right, 0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("left-aligns a diagram wider than the canvas, so step one is on screen", () => {
|
|
42
|
+
const bounds = { x: 40, y: 0, width: 3000, height: 300 };
|
|
43
|
+
const viewport = readableViewport(bounds, canvas);
|
|
44
|
+
expect(project(bounds.x, viewport.x, viewport.zoom)).toBe(CANVAS_PAD);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("does not centre a diagram that only just overflows", () => {
|
|
48
|
+
// Exactly canvas-wide still needs its padding, so it counts as overflow
|
|
49
|
+
// rather than as a fit with no room to breathe.
|
|
50
|
+
const bounds = { x: 0, y: 0, width: canvas.width, height: 300 };
|
|
51
|
+
expect(readableViewport(bounds, canvas).x).toBe(CANVAS_PAD);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("honours a zoom the caller asks for", () => {
|
|
55
|
+
const bounds = { x: 0, y: 0, width: 400, height: 300 };
|
|
56
|
+
const viewport = readableViewport(bounds, canvas, 2);
|
|
57
|
+
expect(viewport.zoom).toBe(2);
|
|
58
|
+
expect(project(bounds.y, viewport.y, viewport.zoom)).toBe(CANVAS_PAD);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns whole pixels, so nothing lands on a half-device-pixel", () => {
|
|
62
|
+
const bounds = { x: 3, y: 7, width: 333, height: 111 };
|
|
63
|
+
const viewport = readableViewport(bounds, canvas);
|
|
64
|
+
expect(Number.isInteger(viewport.x)).toBe(true);
|
|
65
|
+
expect(Number.isInteger(viewport.y)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("boundsOf", () => {
|
|
70
|
+
it("has nothing to say about a line with no points", () => {
|
|
71
|
+
expect(boundsOf([])).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("boxes a polyline, wherever its ends happen to be", () => {
|
|
75
|
+
// Deliberately not monotonic: an edge routed round a lane doubles back,
|
|
76
|
+
// and the box has to hold the detour rather than just the endpoints.
|
|
77
|
+
expect(
|
|
78
|
+
boundsOf([
|
|
79
|
+
[830, 1769],
|
|
80
|
+
[1245, 1510],
|
|
81
|
+
[1917, 1647],
|
|
82
|
+
]),
|
|
83
|
+
).toEqual({ x: 830, y: 1510, width: 1087, height: 259 });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("centredViewport", () => {
|
|
88
|
+
it("puts the point in the middle of the canvas", () => {
|
|
89
|
+
const vp = centredViewport({ x: 300, y: 200 }, { width: 800, height: 600 });
|
|
90
|
+
expect(300 * vp.zoom + vp.x).toBe(400);
|
|
91
|
+
expect(200 * vp.zoom + vp.y).toBe(300);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("focusViewport", () => {
|
|
96
|
+
const canvas = { width: 703, height: 665 };
|
|
97
|
+
|
|
98
|
+
/** Where the box lands on screen under a viewport. */
|
|
99
|
+
const project = (b: ReturnType<typeof boundsOf>, vp: { x: number; y: number; zoom: number }) => {
|
|
100
|
+
if (!b) throw new Error("no bounds");
|
|
101
|
+
return {
|
|
102
|
+
left: b.x * vp.zoom + vp.x,
|
|
103
|
+
right: (b.x + b.width) * vp.zoom + vp.x,
|
|
104
|
+
top: b.y * vp.zoom + vp.y,
|
|
105
|
+
bottom: (b.y + b.height) * vp.zoom + vp.y,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
it("gets an arrow wider than the canvas wholly on screen", () => {
|
|
110
|
+
// checkout's step 12 in the diagram variant: 1087pt of arrow in a 703px
|
|
111
|
+
// pane. LikeC4's own centring caps the zoom at 1 and leaves it hanging off
|
|
112
|
+
// the right-hand edge; this is the case the helper exists for.
|
|
113
|
+
const bounds = { x: 830, y: 1509, width: 1087, height: 260 };
|
|
114
|
+
const vp = focusViewport(bounds, canvas);
|
|
115
|
+
const at = project(bounds, vp);
|
|
116
|
+
// Within a pixel: the viewport is rounded to whole pixels on purpose, so
|
|
117
|
+
// nothing lands on a half-device-pixel and blurs.
|
|
118
|
+
expect(at.left).toBeCloseTo(CANVAS_PAD, 0);
|
|
119
|
+
expect(canvas.width - at.right).toBeCloseTo(CANVAS_PAD, 0);
|
|
120
|
+
expect(at.top).toBeGreaterThanOrEqual(0);
|
|
121
|
+
expect(at.bottom).toBeLessThanOrEqual(canvas.height);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not magnify a short arrow past the readable size", () => {
|
|
125
|
+
const bounds = { x: 0, y: 0, width: 40, height: 10 };
|
|
126
|
+
expect(focusViewport(bounds, canvas).zoom).toBe(READABLE_ZOOM);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("centres whatever it is shown", () => {
|
|
130
|
+
const bounds = { x: 100, y: 4000, width: 200, height: 80 };
|
|
131
|
+
const vp = focusViewport(bounds, canvas);
|
|
132
|
+
const at = project(bounds, vp);
|
|
133
|
+
// Equal margins either side, to within the whole-pixel rounding.
|
|
134
|
+
expect(Math.abs(at.left - (canvas.width - at.right))).toBeLessThanOrEqual(1);
|
|
135
|
+
expect(Math.abs(at.top - (canvas.height - at.bottom))).toBeLessThanOrEqual(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("stops zooming out at the floor rather than vanishing", () => {
|
|
139
|
+
const monstrous = { x: 0, y: 0, width: 100000, height: 100000 };
|
|
140
|
+
expect(focusViewport(monstrous, canvas).zoom).toBe(MIN_FOCUS_ZOOM);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("survives a zero-height arrow, which is what a straight one is", () => {
|
|
144
|
+
const flat = { x: 0, y: 500, width: 300, height: 0 };
|
|
145
|
+
const vp = focusViewport(flat, canvas);
|
|
146
|
+
expect(vp.zoom).toBe(READABLE_ZOOM);
|
|
147
|
+
expect(Number.isFinite(vp.x) && Number.isFinite(vp.y)).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Where the canvas starts looking, and at what size.
|
|
2
|
+
//
|
|
3
|
+
// Fit-to-view is the wrong opening move for a flow. Fitting a forty-five step
|
|
4
|
+
// sequence into a pane puts every label at three or four pixels, which is a
|
|
5
|
+
// picture of a flow rather than a flow: the reader can see that it is long and
|
|
6
|
+
// nothing else. So the canvas opens at the size the labels were drawn for and
|
|
7
|
+
// shows the beginning, which is where reading starts anyway. Fit-to-view stays
|
|
8
|
+
// one button away, for the reader who wants the shape rather than the words.
|
|
9
|
+
//
|
|
10
|
+
// Pure arithmetic, kept out of the component so the one decision worth arguing
|
|
11
|
+
// about — what "the beginning" means when the diagram is narrower than the
|
|
12
|
+
// pane — is stated once and tested.
|
|
13
|
+
|
|
14
|
+
export interface BBox {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Size {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Viewport {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
zoom: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The authored size. Below roughly 0.7 the 16px labels stop being words. */
|
|
33
|
+
export const READABLE_ZOOM = 1;
|
|
34
|
+
|
|
35
|
+
/** Breathing room between the diagram and the edge of the pane. */
|
|
36
|
+
export const CANVAS_PAD = 24;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The opening viewport: the top of the diagram, at readable zoom.
|
|
40
|
+
*
|
|
41
|
+
* Horizontally it centres when the diagram fits and left-aligns when it does
|
|
42
|
+
* not. Centring a diagram that overflows would open the reader in the middle
|
|
43
|
+
* of the first row with the start scrolled off to the left, and left-aligning
|
|
44
|
+
* one that fits leaves a dead half-canvas on the right — so it is neither rule
|
|
45
|
+
* everywhere, it is each rule where it is the true one.
|
|
46
|
+
*
|
|
47
|
+
* Vertically it always aligns to the top: the first step is the first step
|
|
48
|
+
* whether or not the sequence fits, and centring a short flow would push it
|
|
49
|
+
* down away from the participants header it belongs under.
|
|
50
|
+
*/
|
|
51
|
+
export function readableViewport(
|
|
52
|
+
bounds: BBox,
|
|
53
|
+
canvas: Size,
|
|
54
|
+
zoom: number = READABLE_ZOOM,
|
|
55
|
+
): Viewport {
|
|
56
|
+
const width = bounds.width * zoom;
|
|
57
|
+
const fits = width + CANVAS_PAD * 2 <= canvas.width;
|
|
58
|
+
const x = fits
|
|
59
|
+
? (canvas.width - width) / 2 - bounds.x * zoom
|
|
60
|
+
: CANVAS_PAD - bounds.x * zoom;
|
|
61
|
+
return {
|
|
62
|
+
x: Math.round(x),
|
|
63
|
+
y: Math.round(CANVAS_PAD - bounds.y * zoom),
|
|
64
|
+
zoom,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** How far the canvas may zoom out to get one step wholly on screen. */
|
|
69
|
+
export const MIN_FOCUS_ZOOM = 0.2;
|
|
70
|
+
|
|
71
|
+
/** The box a polyline occupies, or null for a line with no points. */
|
|
72
|
+
export function boundsOf(
|
|
73
|
+
points: readonly (readonly [number, number])[],
|
|
74
|
+
): BBox | null {
|
|
75
|
+
if (points.length === 0) return null;
|
|
76
|
+
let minX = Infinity;
|
|
77
|
+
let minY = Infinity;
|
|
78
|
+
let maxX = -Infinity;
|
|
79
|
+
let maxY = -Infinity;
|
|
80
|
+
for (const [x, y] of points) {
|
|
81
|
+
if (x < minX) minX = x;
|
|
82
|
+
if (y < minY) minY = y;
|
|
83
|
+
if (x > maxX) maxX = x;
|
|
84
|
+
if (y > maxY) maxY = y;
|
|
85
|
+
}
|
|
86
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A viewport that shows one thing whole, as large as it will go.
|
|
91
|
+
*
|
|
92
|
+
* This exists because "bring step 12 into view" has to mean it. LikeC4's own
|
|
93
|
+
* centring caps the zoom at what the viewport already had, which for a step
|
|
94
|
+
* whose arrow is wider than the pane leaves the arrow half off the screen —
|
|
95
|
+
* technically centred, not actually visible. So the zoom comes down as far as
|
|
96
|
+
* it must and no further, and stops at MIN_FOCUS_ZOOM rather than diving to
|
|
97
|
+
* whatever a pathological arrow would need.
|
|
98
|
+
*/
|
|
99
|
+
export function focusViewport(
|
|
100
|
+
bounds: BBox,
|
|
101
|
+
canvas: Size,
|
|
102
|
+
max: number = READABLE_ZOOM,
|
|
103
|
+
): Viewport {
|
|
104
|
+
const room = (span: number, size: number): number =>
|
|
105
|
+
span > 0 ? (size - CANVAS_PAD * 2) / span : max;
|
|
106
|
+
const zoom = Math.max(
|
|
107
|
+
MIN_FOCUS_ZOOM,
|
|
108
|
+
Math.min(max, room(bounds.width, canvas.width), room(bounds.height, canvas.height)),
|
|
109
|
+
);
|
|
110
|
+
return centredViewport(
|
|
111
|
+
{ x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 },
|
|
112
|
+
canvas,
|
|
113
|
+
zoom,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Readable zoom, centred on one point of the diagram.
|
|
119
|
+
*
|
|
120
|
+
* For the variant that has no beginning. A diagram folds a flow's steps onto
|
|
121
|
+
* the handful of boxes that repeat, so there is no top-left corner that means
|
|
122
|
+
* "start here" — but there is still a first step, and its arrow is a place the
|
|
123
|
+
* reader can be put down at a size where the labels are words.
|
|
124
|
+
*/
|
|
125
|
+
export function centredViewport(
|
|
126
|
+
point: { x: number; y: number },
|
|
127
|
+
canvas: Size,
|
|
128
|
+
zoom: number = READABLE_ZOOM,
|
|
129
|
+
): Viewport {
|
|
130
|
+
return {
|
|
131
|
+
x: Math.round(canvas.width / 2 - point.x * zoom),
|
|
132
|
+
y: Math.round(canvas.height / 2 - point.y * zoom),
|
|
133
|
+
zoom,
|
|
134
|
+
};
|
|
135
|
+
}
|