From 42842644e81fcdc29a841093bb755b8b75704b8e Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Tue, 3 Oct 2023 17:09:13 +0200 Subject: [PATCH] feat: enable navigation for v3 (#796) --- apps/studio/package.json | 2 +- apps/studio/src/components/Content.tsx | 7 +- .../Modals/Generator/template-parameters.json | 2 +- apps/studio/src/components/Navigation.tsx | 47 ++- apps/studio/src/components/Navigationv3.tsx | 391 ++++++++++++++++++ apps/studio/src/components/Sidebar.tsx | 2 +- .../src/components/Template/HTMLWrapper.tsx | 9 +- .../src/components/Visualiser/Visualiser.tsx | 8 +- apps/studio/src/react-app-env.d.ts | 4 +- apps/studio/src/services/parser.service.ts | 11 +- apps/studio/src/state/documents.state.ts | 4 +- package-lock.json | 20 +- 12 files changed, 453 insertions(+), 54 deletions(-) create mode 100644 apps/studio/src/components/Navigationv3.tsx diff --git a/apps/studio/package.json b/apps/studio/package.json index ed9425eea..a6f93ce51 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -26,7 +26,7 @@ "@asyncapi/avro-schema-parser": "^3.0.2", "@asyncapi/converter": "^1.3.1", "@asyncapi/openapi-schema-parser": "^3.0.4", - "@asyncapi/parser": "^2.1.0-next-major-spec.5", + "@asyncapi/parser": "^3.0.0-next-major-spec.2", "@asyncapi/react-component": "^1.0.0-next.54", "@asyncapi/specs": "^6.0.0-next-major-spec.6", "@ebay/nice-modal-react": "^1.2.10", diff --git a/apps/studio/src/components/Content.tsx b/apps/studio/src/components/Content.tsx index 40c68a8d7..cea67ccc6 100644 --- a/apps/studio/src/components/Content.tsx +++ b/apps/studio/src/components/Content.tsx @@ -1,6 +1,7 @@ import SplitPane from './SplitPane'; import { Editor } from './Editor/Editor'; import { Navigation } from './Navigation'; +import { Navigationv3 } from './Navigationv3'; import { Template } from './Template'; import { VisualiserTemplate } from './Visualiser'; @@ -16,7 +17,7 @@ export const Content: FunctionComponent = () => { // eslint-disabl const document = useDocumentsState(state => state.documents['asyncapi']?.document) || null; const v3Enabled = useSettingsState(state => state.editor.v3support) || false; const isV3 = document?.version() === '3.0.0' && v3Enabled; - const navigationEnabled = isV3 ? false : show.primarySidebar; + const navigationEnabled = show.primarySidebar; const editorEnabled = show.primaryPanel; const viewEnabled = show.secondaryPanel; const viewType = secondaryPanelType; @@ -42,7 +43,9 @@ export const Content: FunctionComponent = () => { // eslint-disabl localStorage.setItem(splitPosLeft, String(size)); }, 100)} > - + { + isV3 ? : + } ); diff --git a/apps/studio/src/components/Modals/Generator/template-parameters.json b/apps/studio/src/components/Modals/Generator/template-parameters.json index 77321670a..afd6f0c7e 100644 --- a/apps/studio/src/components/Modals/Generator/template-parameters.json +++ b/apps/studio/src/components/Modals/Generator/template-parameters.json @@ -1 +1 @@ -{"@asyncapi/dotnet-nats-template":{"title":".NET Nats Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"version":{"description":"Version of the generated library","type":"string","default":"0.0.1"},"projectName":{"description":"Name of the generated library","type":"string","default":"AsyncapiNatsClient"},"repositoryUrl":{"description":"Repository url for the project file, often needed for release pipelines","type":"string"},"targetFramework":{"description":"The project target framework","type":"string","default":"netstandard2.0;netstandard2.1"},"packageVersion":{"description":"PackageVersion of the generated library","type":"string"},"assemblyVersion":{"description":"AssemblyVersion of the generated library","type":"string"},"fileVersion":{"description":"FileVersion of the generated library","type":"string"},"serializationLibrary":{"description":"Which serialization library should the models use? `newtonsoft` or `json` (system.text.json)","type":"string","default":"json"}},"required":[],"additionalProperties":false},"supportedProtocols":["nats"]},"@asyncapi/go-watermill-template":{"title":"GO Lang Watermill Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"moduleName":{"description":"name of the go module to be generated","type":"string","default":"go-async-api"}},"required":[],"additionalProperties":false},"supportedProtocols":["amqp"]},"@asyncapi/html-template":{"title":"HTML website","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sidebarOrganization":{"description":"Defines how the sidebar should be organized. 'byTags' to categorize operations by tags in the root of the document, `byTagsNoRoot` does the same but for pub/sub tags.","type":"string"},"baseHref":{"description":"Sets the base URL for links and forms.","type":"string"},"version":{"description":"Override the version of your application provided under `info.version` location in the specification file.","type":"string"},"singleFile":{"description":"If set this parameter to true generate single html file with scripts and styles inside","type":"boolean","default":false},"outFilename":{"description":"The name of the output HTML file","type":"string","default":"index.html"},"pdf":{"description":"Set to `true` to get index.pdf generated next to your index.html","type":"boolean","default":false},"pdfTimeout":{"description":"The timeout (in ms) used to generate the pdf","type":"number","default":30000},"favicon":{"description":"URL/Path of the favicon","type":"string","default":""},"config":{"description":"Stringified JSON or a path to a JSON file to override the default React component config. The config override is merged with the default config using the [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) algorithm.","type":"string","default":""}},"required":[],"additionalProperties":false}},"@asyncapi/java-spring-cloud-stream-template":{"title":"Java Spring Cloud Stream Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"actuator":{"description":"If present, it adds the dependencies for spring-boot-starter-web, spring-boot-starter-actuator and micrometer-registry-prometheus.","type":"boolean","default":false},"artifactId":{"description":"The Maven artifact id. Alternatively you can set the specification extension info.x-artifact-id","type":"string","default":"project-name"},"artifactType":{"description":"The type of project to generate, application or library. The default is application. When generating an application, the pom.xml file will contain the complete set of dependencies required to run an app, and it will contain an Application class with a main function. Otherwise the pom file will include only the dependencies required to compile a library.","type":"string","default":"application"},"binder":{"description":"The name of the binder implementation, one of kafka, rabbit or solace. Default: kafka. If you need other binders to be supported, please let us know!","type":"string","default":"kafka"},"dynamicType":{"description":"When using channels with parameters, i.e. dynamic topics where the topic could be different for each message, this determines whether to use the StreamBridge or a message header. StreamBridge can be used with all binders, but some binders such as Solace can use the topic set in a header for better performance. Possible values are streamBridge and header. Default is streamBridge.","type":"string","default":"streamBridge"},"groupId":{"description":"The Maven group id. Alternatively you can set the specification extension info.x-group-id","type":"string","default":"com.company"},"host":{"description":"The host connection property. Currently this only works with the Solace binder. Example: tcp://myhost.com:55555.","type":"string","default":"tcp://localhost:55555"},"javaPackage":{"description":"The Java package of the generated classes. Alternatively you can set the specification extension info.x-java-package","type":"string"},"msgVpn":{"description":"The message vpn connection property. Currently this only works with the Solace binder.","type":"string","default":"default"},"parametersToHeaders":{"description":"If true, this will create headers on the incoming messages for each channel parameter. Currently this only works with messages originating from Solace (using the solace_destination header) and RabbitMQ (using the amqp_receivedRoutingKey header.)","type":"boolean","default":false},"password":{"description":"The client password connection property. Currently this only works with the Solace binder.","type":"string","default":"default"},"reactive":{"description":"If true, this will generate reactive style functions using the Flux class. Defalt: false.","type":"boolean","default":false},"solaceSpringCloudVersion":{"description":"The version of the solace-spring-cloud-bom dependency used when generating an application. Alternatively you can set the specification extension info.x-solace-spring-cloud-version.","type":"string","default":"2.1.0"},"springBootVersion":{"description":"The version of Spring Boot used when generating an application. Alternatively you can set the specification extension info.x-spring-booot-version. Example: 2.2.6.RELEASE.","type":"string","default":"2.4.7"},"springCloudVersion":{"description":"The version of the spring-cloud-dependencies BOM dependency used when generating an application. Alternatively you can set the specification extension info.x-spring-cloud-version. Example: Hoxton.RELEASE.","type":"string","default":"2020.0.3"},"springCloudStreamVersion":{"description":"The version of the spring-cloud-stream dependency specified in the Maven file, when generating a library. When generating an application, the spring-cloud-dependencies BOM is used instead. Example: 3.0.1.RELEASE","type":"string","default":"3.1.3"},"username":{"description":"The client username connection property. Currently this only works with the Solace binder","type":"string","default":"default"},"view":{"description":"The view that the template uses. By default it is the client view, which means that when the document says publish, we subscribe. In the case of the provider view, when the document says publish, we publish. Values are client or provider. The default is client.","type":"string","default":"client"},"useServers":{"description":"This option works when binder is kafka. By default it is set to false. When set to true, it will concatenate all the urls in the servers section as a list of brokers for kafka.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/java-spring-template":{"title":"Java Spring Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"inverseOperations":{"description":"Generate application that will publish messages to `publish` operation of channels and read messages from `subscribe` operation of channels. Literally this flag just swap `publish` and `subscribe` operations in channels.","type":"boolean","default":false},"disableEqualsHashCode":{"description":"Disable generation of equals and hashCode methods for model classes.","type":"boolean","default":false},"listenerPollTimeout":{"description":"Only for Kafka. Timeout to use when polling the consumer.","type":"number","default":3000},"listenerConcurrency":{"description":"Only for Kafka. Number of threads to run in the listener containers.","type":"number","default":3},"connectionTimeout":{"description":"Only for MQTT. This value, measured in seconds, defines the maximum time interval the client will wait for the network connection to the MQTT server to be established. The default timeout is 30 seconds. A value of 0 disables timeout processing meaning the client will wait until the network connection is made successfully or fails.","type":"number","default":30},"disconnectionTimeout":{"description":"Only for MQTT. The completion timeout in milliseconds when disconnecting. The default disconnect completion timeout is 5000 milliseconds.","type":"number","default":5000},"completionTimeout":{"description":"Only for MQTT. The completion timeout in milliseconds for operations. The default completion timeout is 30000 milliseconds.","type":"number","default":30000},"mqttClientId":{"description":"Only for MQTT. Provides the client identifier for the MQTT server. This parameter overrides the value of the clientId if it's set in the AsyncAPI file.","type":"string"},"asyncapiFileDir":{"description":"Parameter of @asyncapi/generator-hooks#createAsyncapiFile, allows to specify where original AsyncAPI file will be stored.","type":"string","default":"src/main/resources/api/"},"javaPackage":{"description":"The Java package of the generated classes. Alternatively you can set the specification extension info.x-java-package","type":"string","default":"com.asyncapi"},"addTypeInfoHeader":{"description":"Only for Kafka. Add type information to the message header","type":"boolean","default":true}},"required":[],"additionalProperties":false},"supportedProtocols":["kafka","amqp","mqtt"]},"@asyncapi/java-template":{"title":"Java Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"},"user":{"description":"Username for the server to generate code for","type":"string","default":"app"},"password":{"description":"Password for the server to generate code for","type":"string","default":"passw0rd"},"package":{"description":"Java package name for generated code","type":"string","default":"com.asyncapi"},"mqTopicPrefix":{"description":"MQ topic prefix. Used for ibmmq protocols. Default will work with dev MQ instance","type":"string","default":"dev//"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["ibmmq","ibmmq-secure","kafka","kafka-secure"]},"@asyncapi/markdown-template":{"title":"Markdown Documentation","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"frontMatter":{"description":"The name of a JSON or YAML formatted file containing values to provide the YAML frontmatter for static-site or documentation generators. The file may contain {{title}} and {{version}} replaceable tags.","type":"string"},"outFilename":{"description":"The name of the output markdown file","type":"string","default":"asyncapi.md"},"toc":{"description":"Include a Table-of-Contents in the output markdown.","type":"boolean","default":true},"version":{"description":"Override the version of your application provided under `info.version` location in the specification file.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/nodejs-template":{"title":"NodeJS Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"},"securityScheme":{"description":"Name of the security scheme. Only scheme with X509 and Kafka protocol is supported for now.","type":"string"},"certFilesDir":{"description":"Directory where application certificates are located. This parameter is needed when you use X509 security scheme and your cert files are not located in the root of your application.","type":"string","default":"./"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["amqp","mqtt","mqtts","kafka","kafka-secure","ws"]},"@asyncapi/nodejs-ws-template":{"title":"NodeJS WebSocket Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["ws"]},"@asyncapi/python-paho-template":{"title":"Python Paho Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"view":{"description":"The view that the template uses. By default it is the client view, which means that when the document says publish, we subscribe. In the case of the provider view, when the document says publish, we publish. Values are client or provider. The default is client.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/ts-nats-template":{"title":"Typescript Nats Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"generateTestClient":{"description":"Generate the test client","type":"boolean","default":false},"promisifyReplyCallback":{"description":"Use promises as callbacks for reply operation","type":"boolean","default":false}},"required":[],"additionalProperties":false},"supportedProtocols":["nats"]}} \ No newline at end of file +{"@asyncapi/dotnet-nats-template":{"title":".NET Nats Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"version":{"description":"Version of the generated library","type":"string","default":"0.0.1"},"projectName":{"description":"Name of the generated library","type":"string","default":"AsyncapiNatsClient"},"repositoryUrl":{"description":"Repository url for the project file, often needed for release pipelines","type":"string"},"targetFramework":{"description":"The project target framework","type":"string","default":"netstandard2.0;netstandard2.1"},"packageVersion":{"description":"PackageVersion of the generated library","type":"string"},"assemblyVersion":{"description":"AssemblyVersion of the generated library","type":"string"},"fileVersion":{"description":"FileVersion of the generated library","type":"string"},"serializationLibrary":{"description":"Which serialization library should the models use? `newtonsoft` or `json` (system.text.json)","type":"string","default":"json"}},"required":[],"additionalProperties":false},"supportedProtocols":["nats"]},"@asyncapi/go-watermill-template":{"title":"GO Lang Watermill Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"moduleName":{"description":"name of the go module to be generated","type":"string","default":"go-async-api"}},"required":[],"additionalProperties":false},"supportedProtocols":["amqp"]},"@asyncapi/html-template":{"title":"HTML website","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"sidebarOrganization":{"description":"Defines how the sidebar should be organized. 'byTags' to categorize operations by tags in the root of the document, `byTagsNoRoot` does the same but for pub/sub tags.","type":"string"},"baseHref":{"description":"Sets the base URL for links and forms.","type":"string"},"version":{"description":"Override the version of your application provided under `info.version` location in the specification file.","type":"string"},"singleFile":{"description":"If set this parameter to true generate single html file with scripts and styles inside","type":"boolean","default":false},"outFilename":{"description":"The name of the output HTML file","type":"string","default":"index.html"},"pdf":{"description":"Set to `true` to get index.pdf generated next to your index.html","type":"boolean","default":false},"pdfTimeout":{"description":"The timeout (in ms) used to generate the pdf","type":"number","default":30000},"favicon":{"description":"URL/Path of the favicon","type":"string","default":""},"config":{"description":"Stringified JSON or a path to a JSON file to override the default React component config. The config override is merged with the default config using the [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) algorithm.","type":"string","default":""}},"required":[],"additionalProperties":false}},"@asyncapi/java-spring-cloud-stream-template":{"title":"Java Spring Cloud Stream Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"actuator":{"description":"If present, it adds the dependencies for spring-boot-starter-web, spring-boot-starter-actuator and micrometer-registry-prometheus.","type":"boolean","default":false},"artifactId":{"description":"The Maven artifact id. Alternatively you can set the specification extension info.x-artifact-id","type":"string","default":"project-name"},"artifactType":{"description":"The type of project to generate, application or library. The default is application. When generating an application, the pom.xml file will contain the complete set of dependencies required to run an app, and it will contain an Application class with a main function. Otherwise the pom file will include only the dependencies required to compile a library.","type":"string","default":"application"},"binder":{"description":"The name of the binder implementation, one of kafka, rabbit or solace. Default: kafka. If you need other binders to be supported, please let us know!","type":"string","default":"kafka"},"dynamicType":{"description":"When using channels with parameters, i.e. dynamic topics where the topic could be different for each message, this determines whether to use the StreamBridge or a message header. StreamBridge can be used with all binders, but some binders such as Solace can use the topic set in a header for better performance. Possible values are streamBridge and header. Default is streamBridge.","type":"string","default":"streamBridge"},"groupId":{"description":"The Maven group id. Alternatively you can set the specification extension info.x-group-id","type":"string","default":"com.company"},"host":{"description":"The host connection property. Currently this only works with the Solace binder. Example: tcp://myhost.com:55555.","type":"string","default":"tcp://localhost:55555"},"javaPackage":{"description":"The Java package of the generated classes. Alternatively you can set the specification extension info.x-java-package","type":"string"},"msgVpn":{"description":"The message vpn connection property. Currently this only works with the Solace binder.","type":"string","default":"default"},"parametersToHeaders":{"description":"If true, this will create headers on the incoming messages for each channel parameter. Currently this only works with messages originating from Solace (using the solace_destination header) and RabbitMQ (using the amqp_receivedRoutingKey header.)","type":"boolean","default":false},"password":{"description":"The client password connection property. Currently this only works with the Solace binder.","type":"string","default":"default"},"reactive":{"description":"If true, this will generate reactive style functions using the Flux class. Defalt: false.","type":"boolean","default":false},"solaceSpringCloudVersion":{"description":"The version of the solace-spring-cloud-bom dependency used when generating an application. Alternatively you can set the specification extension info.x-solace-spring-cloud-version.","type":"string","default":"2.1.0"},"springBootVersion":{"description":"The version of Spring Boot used when generating an application. Alternatively you can set the specification extension info.x-spring-booot-version. Example: 2.2.6.RELEASE.","type":"string","default":"2.4.7"},"springCloudVersion":{"description":"The version of the spring-cloud-dependencies BOM dependency used when generating an application. Alternatively you can set the specification extension info.x-spring-cloud-version. Example: Hoxton.RELEASE.","type":"string","default":"2020.0.3"},"springCloudStreamVersion":{"description":"The version of the spring-cloud-stream dependency specified in the Maven file, when generating a library. When generating an application, the spring-cloud-dependencies BOM is used instead. Example: 3.0.1.RELEASE","type":"string","default":"3.1.3"},"username":{"description":"The client username connection property. Currently this only works with the Solace binder","type":"string","default":"default"},"view":{"description":"The view that the template uses. By default it is the client view, which means that when the document says publish, we subscribe. In the case of the provider view, when the document says publish, we publish. Values are client or provider. The default is client.","type":"string","default":"client"},"useServers":{"description":"This option works when binder is kafka. By default it is set to false. When set to true, it will concatenate all the urls in the servers section as a list of brokers for kafka.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/java-spring-template":{"title":"Java Spring Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"inverseOperations":{"description":"Generate application that will publish messages to `publish` operation of channels and read messages from `subscribe` operation of channels. Literally this flag just swap `publish` and `subscribe` operations in channels.","type":"boolean","default":false},"disableEqualsHashCode":{"description":"Disable generation of equals and hashCode methods for model classes.","type":"boolean","default":false},"listenerPollTimeout":{"description":"Only for Kafka. Timeout to use when polling the consumer.","type":"number","default":3000},"listenerConcurrency":{"description":"Only for Kafka. Number of threads to run in the listener containers.","type":"number","default":3},"connectionTimeout":{"description":"Only for MQTT. This value, measured in seconds, defines the maximum time interval the client will wait for the network connection to the MQTT server to be established. The default timeout is 30 seconds. A value of 0 disables timeout processing meaning the client will wait until the network connection is made successfully or fails.","type":"number","default":30},"disconnectionTimeout":{"description":"Only for MQTT. The completion timeout in milliseconds when disconnecting. The default disconnect completion timeout is 5000 milliseconds.","type":"number","default":5000},"completionTimeout":{"description":"Only for MQTT. The completion timeout in milliseconds for operations. The default completion timeout is 30000 milliseconds.","type":"number","default":30000},"mqttClientId":{"description":"Only for MQTT. Provides the client identifier for the MQTT server. This parameter overrides the value of the clientId if it's set in the AsyncAPI file.","type":"string"},"asyncapiFileDir":{"description":"Parameter of @asyncapi/generator-hooks#createAsyncapiFile, allows to specify where original AsyncAPI file will be stored.","type":"string","default":"src/main/resources/api/"},"javaPackage":{"description":"The Java package of the generated classes. Alternatively you can set the specification extension info.x-java-package","type":"string","default":"com.asyncapi"},"addTypeInfoHeader":{"description":"Only for Kafka. Add type information to the message header","type":"boolean","default":true},"springBoot2":{"description":"Generate template files for the Spring Boot version 2. For kafka protocol it will also force to use spring-kafka 2.9.9","type":"boolean","default":false}},"required":[],"additionalProperties":false},"supportedProtocols":["kafka","amqp","mqtt"]},"@asyncapi/java-template":{"title":"Java Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"},"user":{"description":"Username for the server to generate code for","type":"string","default":"app"},"password":{"description":"Password for the server to generate code for","type":"string","default":"passw0rd"},"package":{"description":"Java package name for generated code","type":"string","default":"com.asyncapi"},"mqTopicPrefix":{"description":"MQ topic prefix. Used for ibmmq protocols. Default will work with dev MQ instance","type":"string","default":"dev//"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["ibmmq","ibmmq-secure","kafka","kafka-secure"]},"@asyncapi/markdown-template":{"title":"Markdown Documentation","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"frontMatter":{"description":"The name of a JSON or YAML formatted file containing values to provide the YAML frontmatter for static-site or documentation generators. The file may contain {{title}} and {{version}} replaceable tags.","type":"string"},"outFilename":{"description":"The name of the output markdown file","type":"string","default":"asyncapi.md"},"toc":{"description":"Include a Table-of-Contents in the output markdown.","type":"boolean","default":true},"version":{"description":"Override the version of your application provided under `info.version` location in the specification file.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/nodejs-template":{"title":"NodeJS Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"},"securityScheme":{"description":"Name of the security scheme. Only scheme with X509 and Kafka protocol is supported for now.","type":"string"},"certFilesDir":{"description":"Directory where application certificates are located. This parameter is needed when you use X509 security scheme and your cert files are not located in the root of your application.","type":"string","default":"./"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["amqp","mqtt","mqtts","kafka","kafka-secure","ws"]},"@asyncapi/nodejs-ws-template":{"title":"NodeJS WebSocket Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"server":{"description":"The server you want to use in the code.","type":"string"},"asyncapiFileDir":{"description":"Custom location of the AsyncAPI file that you provided as an input in generation. By default it is located in the root of the output directory","type":"string"}},"required":["server"],"additionalProperties":false},"supportedProtocols":["ws"]},"@asyncapi/python-paho-template":{"title":"Python Paho Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"view":{"description":"The view that the template uses. By default it is the client view, which means that when the document says publish, we subscribe. In the case of the provider view, when the document says publish, we publish. Values are client or provider. The default is client.","type":"string"}},"required":[],"additionalProperties":false}},"@asyncapi/ts-nats-template":{"title":"Typescript Nats Project","schema":{"$schema":"http://json-schema.org/draft-07/schema#","type":"object","properties":{"generateTestClient":{"description":"Generate the test client","type":"boolean","default":false},"promisifyReplyCallback":{"description":"Use promises as callbacks for reply operation","type":"boolean","default":false}},"required":[],"additionalProperties":false},"supportedProtocols":["nats"]}} \ No newline at end of file diff --git a/apps/studio/src/components/Navigation.tsx b/apps/studio/src/components/Navigation.tsx index fb92ca8d1..381c28cef 100644 --- a/apps/studio/src/components/Navigation.tsx +++ b/apps/studio/src/components/Navigation.tsx @@ -5,14 +5,14 @@ import React, { useEffect, useState } from 'react'; import { useServices } from '../services'; import { useDocumentsState, useFilesState } from '../state'; -import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/cjs'; +import type { AsyncAPIDocumentInterface } from '@asyncapi/parser/cjs'; interface NavigationProps { className?: string; } interface NavigationSectionProps { - document: AsyncAPIDocument; + document: AsyncAPIDocumentInterface; rawSpec: string; hash: string; } @@ -36,8 +36,9 @@ const ServersNavigation: React.FunctionComponent = ({ Servers
    - {Object.entries(document.servers() || {}).map(([serverName, server]) => ( -
  • { + const serverName = server.id(); + return
  • = ({ {serverName}
  • - ))} + })}
); @@ -72,11 +73,11 @@ const OperationsNavigation: React.FunctionComponent = ({ }) => { const { navigationSvc } = useServices(); - const operations = Object.entries(document.channels() || {}).map( - ([channelName, channel]) => { + const operations = document.operations().all().map( + (operation) => { const channels: React.ReactNode[] = []; - - if (channel.hasPublish()) { + const channelName = operation.channels().all()[0].address() || 'unknown'; + if (operation.isReceive()) { channels.push(
  • = ({
  • , ); } - if (channel.hasSubscribe()) { + if (operation.isSend()) { channels.push(
  • = ({ }) => { const { navigationSvc } = useServices(); - const messages = Object.keys(document.components()?.messages() || {}).map( - messageName => ( -
  • { + const messageName = message.id(); + return
  • = ({ > {messageName}
  • - ), + }, ); return ( @@ -202,9 +204,10 @@ const SchemasNavigation: React.FunctionComponent = ({ }) => { const { navigationSvc } = useServices(); - const schemas = Object.keys(document.components()?.schemas() || {}).map( - schemaName => ( -
  • { + const schemaName = schema.id(); + return
  • = ({ > {schemaName}
  • - ), + } ); return ( @@ -271,7 +274,7 @@ export const Navigation: React.FunctionComponent = ({ ); } - const components = document.hasComponents() && document.components(); + const components = document.components(); return (
      @@ -290,7 +293,7 @@ export const Navigation: React.FunctionComponent = ({ Information
    - {document.hasServers() && ( + {!document.servers().isEmpty() && (
  • = ({ hash={hash} />
  • - {components && components.hasMessages() && ( + {!components.messages().isEmpty() && (
  • = ({ />
  • )} - {components && components.hasSchemas() && ( + {!components.schemas().isEmpty() && (
  • = ({ + document, + hash, +}) => { + const { navigationSvc } = useServices(); + + return ( + <> +
    + navigationSvc.scrollTo('/servers', 'servers') + } + > + Servers +
    +
      + {document.servers().all().map((server) => { + const serverName = server.id(); + return
    • + navigationSvc.scrollTo( + `/servers/${serverName.replace(/\//g, '~1')}`, + `server-${serverName}`, + ) + } + > +
      +
      + + {server.protocolVersion() + ? `${server.protocol()} ${server.protocolVersion()}` + : server.protocol()} + +
      + {serverName} +
      +
    • + })} +
    + + ); +}; + +const ChannelsNavigation: React.FunctionComponent = ({ + document, + hash, +}) => { + const { navigationSvc } = useServices(); + + const channels = document.channels().all().map( + (channel) => { + return
  • + navigationSvc.scrollTo( + `/channels/${(channel.id() ?? '').replace(/\//g, '~1')}`, + `channels-${channel.id()}`, + ) + } + > +
    +
    + + {channel.id()} + +
    + {channel.address()} +
    +
  • + }, + ); + + return ( + <> +
    + navigationSvc.scrollTo( + '/channels', + 'channels', + ) + } + > + Channels +
    +
      {channels}
    + + ); +}; + +const OperationsNavigation: React.FunctionComponent = ({ + document, + hash, +}) => { + const { navigationSvc } = useServices(); + + const operations = document.operations().all().map( + (operation) => { + const operations: React.ReactNode[] = []; + if (operation.isReceive()) { + operations.push( +
  • + navigationSvc.scrollTo( + `/operations/${(operation.id() ?? '').replace(/\//g, '~1')}`, + `operation-receive-${operation.id()}`, + ) + } + > +
    +
    + + Receive + +
    + {operation.id()} +
    +
  • + ); + } + if (operation.isSend()) { + operations.push( +
  • + navigationSvc.scrollTo( + `/operations/${(operation.id() ?? '').replace(/\//g, '~1')}`, + `operation-send-${operation.id()}`, + ) + } + > +
    +
    + + Send + +
    + {operation.id()} +
    +
  • , + ); + } + + return operations; + }, + ); + + return ( + <> +
    + navigationSvc.scrollTo( + '/operations', + 'operations', + ) + } + > + Operations +
    +
      {operations}
    + + ); +}; + +const MessagesNavigation: React.FunctionComponent = ({ + document, + hash, +}) => { + const { navigationSvc } = useServices(); + + const messages = document.components().messages().all().map( + message => { + const messageName = message.id(); + return
  • + navigationSvc.scrollTo( + `/components/messages/${messageName.replace(/\//g, '~1')}`, + `message-${messageName}`, + ) + } + > + {messageName} +
  • + }, + ); + + return ( + <> +
    + navigationSvc.scrollTo( + '/components/messages', + 'messages', + ) + } + > + Messages +
    +
      {messages}
    + + ); +}; + +const SchemasNavigation: React.FunctionComponent = ({ + document, + hash, +}) => { + const { navigationSvc } = useServices(); + + const schemas = document.components().schemas().all().map( + schema => { + const schemaName = schema.id(); + return
  • + navigationSvc.scrollTo( + `/components/schemas/${schemaName.replace(/\//g, '~1')}`, + `schema-${schemaName}`, + ) + } + > + {schemaName} +
  • + } + ); + + return ( + <> +
    + navigationSvc.scrollTo( + '/components/schemas', + 'schemas', + ) + } + > + Schemas +
    +
      {schemas}
    + + ); +}; + +export const Navigationv3: React.FunctionComponent = ({ + className = '', +}) => { + const [hash, setHash] = useState(window.location.hash); + + const { navigationSvc } = useServices(); + const rawSpec = useFilesState(state => state.files['asyncapi']?.content); + const document = useDocumentsState(state => state.documents['asyncapi']?.document); + + useEffect(() => { + const fn = () => { + // remove `#` char + const h = window.location.hash.startsWith('#') ? window.location.hash.substring(1) : window.location.hash; + setHash(h); + }; + fn(); + window.addEventListener('hashchange', fn); + return () => { + window.removeEventListener('hashchange', fn); + }; + }, []); + + if (!rawSpec || !document) { + return ( +
    + Empty or invalid document. Please fix errors/define AsyncAPI document. +
    + ); + } + + const components = document.components(); + return ( +
    +
      +
    • +
      + navigationSvc.scrollTo( + '/info', + 'introduction', + ) + } + > + Information +
      +
    • + {!document.servers().isEmpty() && ( +
    • + +
    • + )} +
    • + +
    • +
    • + +
    • + {!components.messages().isEmpty() && ( +
    • + +
    • + )} + {!components.schemas().isEmpty() && ( +
    • + +
    • + )} +
    +
    + ); +}; \ No newline at end of file diff --git a/apps/studio/src/components/Sidebar.tsx b/apps/studio/src/components/Sidebar.tsx index b4cfaba4f..dd117a943 100644 --- a/apps/studio/src/components/Sidebar.tsx +++ b/apps/studio/src/components/Sidebar.tsx @@ -69,7 +69,7 @@ export const Sidebar: FunctionComponent = () => { onClick: () => updateState('primarySidebar'), icon: , tooltip: 'Navigation', - enabled: !isV3 + enabled: true }, // editor { diff --git a/apps/studio/src/components/Template/HTMLWrapper.tsx b/apps/studio/src/components/Template/HTMLWrapper.tsx index 8f2726d54..afb944cbe 100644 --- a/apps/studio/src/components/Template/HTMLWrapper.tsx +++ b/apps/studio/src/components/Template/HTMLWrapper.tsx @@ -4,7 +4,7 @@ import { AsyncApiComponentWP } from '@asyncapi/react-component'; import { useServices } from '../../services'; import { appState, useDocumentsState, useSettingsState, useOtherState, otherState } from '../../state'; -import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/cjs'; +import { OldAsyncAPIDocument as AsyncAPIDocument, convertToOldAPI } from '@asyncapi/parser/cjs'; interface HTMLWrapperProps {} @@ -12,6 +12,7 @@ export const HTMLWrapper: React.FunctionComponent = () => { const [parsedSpec, setParsedSpec] = useState(null); const { navigationSvc } = useServices(); const document = useDocumentsState(state => state.documents['asyncapi']?.document) || null; + const autoRendering = useSettingsState(state => state.templates.autoRendering); const templateRerender = useOtherState(state => state.templateRerender); @@ -21,13 +22,15 @@ export const HTMLWrapper: React.FunctionComponent = () => { useEffect(() => { if (autoRendering || parsedSpec === null) { - setParsedSpec(document); + const oldDocument = document !== null ? convertToOldAPI(document) : null; + setParsedSpec(oldDocument); } }, [document]); // eslint-disable-line useEffect(() => { if (templateRerender) { - setParsedSpec(document); + const oldDocument = document !== null ? convertToOldAPI(document) : null; + setParsedSpec(oldDocument); otherState.setState({ templateRerender: false }); } }, [templateRerender]); // eslint-disable-line diff --git a/apps/studio/src/components/Visualiser/Visualiser.tsx b/apps/studio/src/components/Visualiser/Visualiser.tsx index 90555cf3e..357d2c632 100644 --- a/apps/studio/src/components/Visualiser/Visualiser.tsx +++ b/apps/studio/src/components/Visualiser/Visualiser.tsx @@ -4,7 +4,7 @@ import { FlowDiagram } from './FlowDiagram'; import { useDocumentsState, useSettingsState, useOtherState, otherState } from '../../state'; -import type { OldAsyncAPIDocument as AsyncAPIDocument } from '@asyncapi/parser/cjs'; +import { OldAsyncAPIDocument as AsyncAPIDocument, convertToOldAPI } from '@asyncapi/parser/cjs'; import type { FunctionComponent } from 'react'; interface VisualiserProps {} @@ -17,13 +17,15 @@ export const Visualiser: FunctionComponent = () => { useEffect(() => { if (autoRendering || parsedSpec === null) { - setParsedSpec(document); + const oldDocument = document !== null ? convertToOldAPI(document) : null; + setParsedSpec(oldDocument); } }, [document]); // eslint-disable-line useEffect(() => { if (templateRerender) { - setParsedSpec(document); + const oldDocument = document !== null ? convertToOldAPI(document) : null; + setParsedSpec(oldDocument); otherState.setState({ templateRerender: false }); } }, [templateRerender]); // eslint-disable-line diff --git a/apps/studio/src/react-app-env.d.ts b/apps/studio/src/react-app-env.d.ts index 8265dfcfb..f4ce82809 100644 --- a/apps/studio/src/react-app-env.d.ts +++ b/apps/studio/src/react-app-env.d.ts @@ -1,7 +1,7 @@ /// import type * as monacoAPI from 'monaco-editor/esm/vs/editor/editor.api'; -import type { OldAsyncAPIDocument as AsyncAPIDocument, ParseOutput } from '@asyncapi/parser/cjs'; +import type { AsyncAPIDocumentInterface, ParseOutput } from '@asyncapi/parser/cjs'; declare global { interface Window { @@ -9,7 +9,7 @@ declare global { monaco: typeof monacoAPI; Editor: monacoAPI.editor.IStandaloneCodeEditor; MonacoEnvironment: monacoAPI.Environment | undefined; - ParsedSpec?: AsyncAPIDocument; + ParsedSpec?: AsyncAPIDocumentInterface; ParsedExtras?: ParseOutput['extras']; } } diff --git a/apps/studio/src/services/parser.service.ts b/apps/studio/src/services/parser.service.ts index 0fd3a5650..a1138238c 100644 --- a/apps/studio/src/services/parser.service.ts +++ b/apps/studio/src/services/parser.service.ts @@ -1,6 +1,6 @@ import { AbstractService } from './abstract.service'; -import { Parser, convertToOldAPI, DiagnosticSeverity } from '@asyncapi/parser/cjs'; +import { Parser, DiagnosticSeverity } from '@asyncapi/parser/cjs'; import { OpenAPISchemaParser } from '@asyncapi/openapi-schema-parser'; import { AvroSchemaParser } from '@asyncapi/avro-schema-parser'; import { untilde } from '@asyncapi/parser/cjs/utils'; @@ -17,8 +17,8 @@ export class ParserService extends AbstractService { override async onInit() { this.parser = new Parser({ schemaParsers: [ - OpenAPISchemaParser(), - AvroSchemaParser(), + OpenAPISchemaParser() as any, + AvroSchemaParser() as any, ], __unstable: { resolver: { @@ -42,12 +42,9 @@ export class ParserService extends AbstractService { const { document, diagnostics: _diagnostics, extras } = await this.parser.parse(spec, options); diagnostics = _diagnostics; if (document) { - // This is needed as we are still using the old Parser API - // @todo: migrate to Parser API v2 - const oldDocument = convertToOldAPI(document); this.updateDocument(uri, { uri, - document: oldDocument, + document, diagnostics: this.createDiagnostics(diagnostics), extras, valid: true, diff --git a/apps/studio/src/state/documents.state.ts b/apps/studio/src/state/documents.state.ts index e6bbb03ab..276e89b16 100644 --- a/apps/studio/src/state/documents.state.ts +++ b/apps/studio/src/state/documents.state.ts @@ -1,6 +1,6 @@ import create from 'zustand'; -import type { OldAsyncAPIDocument as AsyncAPIDocument, Diagnostic, ParseOutput } from '@asyncapi/parser/cjs'; +import type { AsyncAPIDocumentInterface, Diagnostic, ParseOutput } from '@asyncapi/parser/cjs'; export type DocumentDiagnostics = { original: Diagnostic[]; @@ -13,7 +13,7 @@ export type DocumentDiagnostics = { export type Document = { uri: string; - document?: AsyncAPIDocument; + document?: AsyncAPIDocumentInterface; extras?: ParseOutput['extras']; diagnostics: DocumentDiagnostics; valid?: boolean; diff --git a/package-lock.json b/package-lock.json index 532f3cf76..18ad827b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -500,7 +500,7 @@ "@asyncapi/avro-schema-parser": "^3.0.2", "@asyncapi/converter": "^1.3.1", "@asyncapi/openapi-schema-parser": "^3.0.4", - "@asyncapi/parser": "^2.1.0-next-major-spec.5", + "@asyncapi/parser": "^3.0.0-next-major-spec.2", "@asyncapi/react-component": "^1.0.0-next.54", "@asyncapi/specs": "^6.0.0-next-major-spec.6", "@ebay/nice-modal-react": "^1.2.10", @@ -634,11 +634,11 @@ } }, "apps/studio/node_modules/@asyncapi/parser": { - "version": "2.1.0-next-major-spec.5", - "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-2.1.0-next-major-spec.5.tgz", - "integrity": "sha512-j18xG8jKnIcgva9jWKJVCZNaPZhfPi8FBzKVeK9QTXxpm5tbq4wAuTWrTbEVDutc+fJgMdq3tMHlSricLanhHw==", + "version": "3.0.0-next-major-spec.2", + "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-3.0.0-next-major-spec.2.tgz", + "integrity": "sha512-/gJgCYNYlUSDJhySK3IagjiyFfnwEsAZd5rTe396CB+HxJ6yDWDOZYdHzkFgU2RnCULSGzVOWZGx8t3PK+yuVg==", "dependencies": { - "@asyncapi/specs": "^6.0.0-next-major-spec.2", + "@asyncapi/specs": "^6.0.0-next-major-spec.6", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.16.1", @@ -35359,7 +35359,7 @@ "@asyncapi/nodejs-template": "^1.0.0", "@asyncapi/nodejs-ws-template": "^0.9.33", "@asyncapi/openapi-schema-parser": "^3.0.4", - "@asyncapi/parser": "^2.1.0-next-major-spec.5", + "@asyncapi/parser": "^3.0.0-next-major-spec.2", "@asyncapi/python-paho-template": "^0.2.13", "@asyncapi/react-component": "^1.0.0-next.54", "@asyncapi/specs": "^6.0.0-next-major-spec.6", @@ -35421,11 +35421,11 @@ }, "dependencies": { "@asyncapi/parser": { - "version": "2.1.0-next-major-spec.5", - "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-2.1.0-next-major-spec.5.tgz", - "integrity": "sha512-j18xG8jKnIcgva9jWKJVCZNaPZhfPi8FBzKVeK9QTXxpm5tbq4wAuTWrTbEVDutc+fJgMdq3tMHlSricLanhHw==", + "version": "3.0.0-next-major-spec.2", + "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-3.0.0-next-major-spec.2.tgz", + "integrity": "sha512-/gJgCYNYlUSDJhySK3IagjiyFfnwEsAZd5rTe396CB+HxJ6yDWDOZYdHzkFgU2RnCULSGzVOWZGx8t3PK+yuVg==", "requires": { - "@asyncapi/specs": "^6.0.0-next-major-spec.2", + "@asyncapi/specs": "^6.0.0-next-major-spec.6", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.16.1",