From f173cf7d2d8d6c664d0cbfa6099c3c70730a8331 Mon Sep 17 00:00:00 2001 From: Ashmit JaiSarita Gupta <43639341+devilkiller-ag@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:43:34 +0530 Subject: [PATCH] chore(website): create new playground layout (#1628) --- modelina-website/package-lock.json | 66 ++ modelina-website/package.json | 2 + modelina-website/src/components/CodeBlock.tsx | 2 +- modelina-website/src/components/InfoModal.tsx | 18 +- .../components/contexts/PlaygroundContext.tsx | 185 ++++++ .../src/components/playground/Content.tsx | 89 +++ .../components/playground/GeneratedModels.tsx | 154 ++--- .../playground/OptionsNavigation.tsx | 54 ++ .../playground/OutputNavigation.tsx | 26 + .../src/components/playground/Playground.tsx | 578 ++++++------------ .../playground/PlaygroundOptions.tsx | 130 ++-- .../src/components/playground/Sidebar.tsx | 76 +++ .../src/components/playground/Tooltip.tsx | 23 + .../options/CSharpGeneratorOptions.tsx | 38 +- .../options/CplusplusGeneratorOptions.tsx | 8 +- .../options/DartGeneratorOptions.tsx | 2 +- .../playground/options/GeneralOptions.tsx | 86 +-- .../playground/options/GoGeneratorOptions.tsx | 8 +- .../options/JavaGeneratorOptions.tsx | 40 +- .../options/JavaScriptGeneratorOptions.tsx | 2 +- .../options/KotlinGeneratorOptions.tsx | 8 +- .../options/PhpGeneratorOptions.tsx | 12 +- .../options/PythonGeneratorOptions.tsx | 2 +- .../options/RustGeneratorOptions.tsx | 2 +- .../options/TypeScriptGeneratorOptions.tsx | 76 +-- modelina-website/src/pages/playground.tsx | 38 +- modelina-website/src/styles/globals.css | 21 + 27 files changed, 1048 insertions(+), 698 deletions(-) create mode 100644 modelina-website/src/components/contexts/PlaygroundContext.tsx create mode 100644 modelina-website/src/components/playground/Content.tsx create mode 100644 modelina-website/src/components/playground/OptionsNavigation.tsx create mode 100644 modelina-website/src/components/playground/OutputNavigation.tsx create mode 100644 modelina-website/src/components/playground/Sidebar.tsx create mode 100644 modelina-website/src/components/playground/Tooltip.tsx diff --git a/modelina-website/package-lock.json b/modelina-website/package-lock.json index 5b679dcfec..343dfd77d8 100644 --- a/modelina-website/package-lock.json +++ b/modelina-website/package-lock.json @@ -12,6 +12,7 @@ "@tailwindcss/forms": "^0.5.2", "@tailwindcss/line-clamp": "^0.4.0", "@tailwindcss/typography": "^0.5.9", + "@tippyjs/react": "^4.2.6", "cssnano": "^5.1.14", "js-base64": "^3.7.4", "lodash": "^4.17.21", @@ -20,6 +21,7 @@ "postcss": "^8.4.31", "react": "18.2.0", "react-dom": "18.2.0", + "react-icons": "^4.12.0", "react-markdown": "^8.0.7", "react-syntax-highlighter": "15.5.0", "rehype-raw": "^6.1.1", @@ -389,6 +391,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", @@ -444,6 +455,18 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/@tippyjs/react": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", + "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==", + "dependencies": { + "tippy.js": "^6.3.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -5476,6 +5499,14 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6318,6 +6349,14 @@ "node": ">=0.8" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7064,6 +7103,11 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@rushstack/eslint-patch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", @@ -7109,6 +7153,14 @@ "postcss-selector-parser": "6.0.10" } }, + "@tippyjs/react": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", + "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==", + "requires": { + "tippy.js": "^6.3.1" + } + }, "@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -10531,6 +10583,12 @@ "scheduler": "^0.23.0" } }, + "react-icons": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -11124,6 +11182,14 @@ "thenify": ">= 3.1.0 < 4" } }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/modelina-website/package.json b/modelina-website/package.json index f098125689..4806ccb794 100644 --- a/modelina-website/package.json +++ b/modelina-website/package.json @@ -20,6 +20,7 @@ "@tailwindcss/forms": "^0.5.2", "@tailwindcss/line-clamp": "^0.4.0", "@tailwindcss/typography": "^0.5.9", + "@tippyjs/react": "^4.2.6", "cssnano": "^5.1.14", "js-base64": "^3.7.4", "lodash": "^4.17.21", @@ -28,6 +29,7 @@ "postcss": "^8.4.31", "react": "18.2.0", "react-dom": "18.2.0", + "react-icons": "^4.12.0", "react-markdown": "^8.0.7", "react-syntax-highlighter": "15.5.0", "rehype-raw": "^6.1.1", diff --git a/modelina-website/src/components/CodeBlock.tsx b/modelina-website/src/components/CodeBlock.tsx index a1c3f63734..d834579b03 100644 --- a/modelina-website/src/components/CodeBlock.tsx +++ b/modelina-website/src/components/CodeBlock.tsx @@ -224,7 +224,7 @@ export default function CodeBlock({ className: 'pl-2 float-left left-0 sticky bg-code-editor-dark', style: {} }} - lineNumberProps={(lineNumber: any) => { + linenumberprops={(lineNumber: any) => { const isHighlighted = highlightedLines?.includes(lineNumber) || false; return { className: `${ diff --git a/modelina-website/src/components/InfoModal.tsx b/modelina-website/src/components/InfoModal.tsx index 7b93ff2109..ac84d75c86 100644 --- a/modelina-website/src/components/InfoModal.tsx +++ b/modelina-website/src/components/InfoModal.tsx @@ -5,7 +5,7 @@ interface InfoModalProps { children: React.ReactNode; } -export default function InfoModal(props: InfoModalProps){ +export default function InfoModal(props: InfoModalProps) { const [showModal, setShowModal] = useState(false); @@ -21,24 +21,24 @@ export default function InfoModal(props: InfoModalProps){ const MyModal = () => { return ( - <> -
-
-
+ <> +
+
+

{props.text}

{props.children} -
+
) } - return( + return ( <> - {showModal && } + {showModal && } ) diff --git a/modelina-website/src/components/contexts/PlaygroundContext.tsx b/modelina-website/src/components/contexts/PlaygroundContext.tsx new file mode 100644 index 0000000000..10a4cda926 --- /dev/null +++ b/modelina-website/src/components/contexts/PlaygroundContext.tsx @@ -0,0 +1,185 @@ +'use client'; +import React, { + createContext, + useContext, + Dispatch, + SetStateAction, + useState, + useMemo +} from 'react'; +import { defaultAsyncapiDocument, ModelinaOptions } from '@/types'; + +interface ModelsGeneratorProps { + code: string; + name: string; +} + +interface LoadedState { + editorLoaded: boolean; + hasReceivedCode: boolean; +} + +interface PlaygroundContextProps { + showOptions: boolean; + setShowOptions: Dispatch>; + showOutputNavigation: boolean; + setShowOutputNavigation: Dispatch>; + config: ModelinaOptions; + setConfig: Dispatch>; + input: string; + setInput: Dispatch>; + models: ModelsGeneratorProps[]; + setModels: Dispatch>; + generatorCode: string; + setGeneratorCode: Dispatch>; + loaded: LoadedState; + setLoaded: Dispatch>; + showGeneratorCode: boolean; + setShowGeneratorCode: Dispatch>; + error: boolean; + setError: Dispatch>; + statusCode: number; + setStatusCode: Dispatch>; + errorMessage: string; + setErrorMessage: Dispatch>; + isLoaded: boolean; + setIsLoaded: Dispatch>; + hasLoadedQuery: boolean; + setHasLoadedQuery: Dispatch>; + renderModels: React.ReactNode | null; + setRenderModels: (models: React.ReactNode) => void; +} + +const PlaygroundContext = createContext(undefined); + +export const PlaygroundContextProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { + const defaultConfig: ModelinaOptions = { + language: 'typescript', + propertyNamingFormat: 'default', + modelNamingFormat: 'default', + enumKeyNamingFormat: 'default', + indentationType: 'spaces', + showTypeMappingExample: false, + tsMarshalling: false, + tsModelType: 'class', + tsEnumType: 'enum', + tsModuleSystem: 'CJS', + tsIncludeDescriptions: false, + tsIncludeExampleFunction: false, + tsIncludeJsonBinPack: false, + csharpArrayType: 'Array', + csharpAutoImplemented: false, + csharpOverwriteHashcode: false, + csharpIncludeJson: false, + csharpOverwriteEqual: false, + csharpIncludeNewtonsoft: false, + csharpNamespace: 'asyncapi.models', + csharpNullable: false, + phpIncludeDescriptions: false, + phpNamespace: 'AsyncAPI/Models', + cplusplusNamespace: 'AsyncapiModels', + javaPackageName: 'asyncapi.models', + javaIncludeJackson: false, + javaIncludeMarshaling: false, + javaArrayType: 'Array', + javaOverwriteHashcode: false, + javaOverwriteEqual: false, + javaOverwriteToString: false, + javaJavaDocs: false, + javaJavaxAnnotation: false, + goPackageName: 'asyncapi.models', + kotlinPackageName: 'asyncapi.models' + }; + + const [showOptions, setShowOptions] = useState(true); + const [showOutputNavigation, setShowOutputNavigation] = useState(true); + const [config, setConfig] = useState(defaultConfig); + const [input, setInput] = useState(JSON.stringify(defaultAsyncapiDocument, null, 4)); + const [models, setModels] = useState([]); + const [generatorCode, setGeneratorCode] = useState(''); + const [loaded, setLoaded] = useState({ + editorLoaded: false, + hasReceivedCode: false, + }); + const [showGeneratorCode, setShowGeneratorCode] = useState(false); + const [error, setError] = useState(false); + const [statusCode, setStatusCode] = useState(400); + const [errorMessage, setErrorMessage] = useState('Bad Request'); + const [isLoaded, setIsLoaded] = useState(false); + const [hasLoadedQuery, setHasLoadedQuery] = useState(false); + const [renderModels, setRenderModels] = React.useState(null); + + const contextValue = useMemo(() => ({ + showOptions, + setShowOptions, + showOutputNavigation, + setShowOutputNavigation, + config, + setConfig, + input, + setInput, + models, + setModels, + generatorCode, + setGeneratorCode, + loaded, + setLoaded, + showGeneratorCode, + setShowGeneratorCode, + error, + setError, + statusCode, + setStatusCode, + errorMessage, + setErrorMessage, + isLoaded, + setIsLoaded, + hasLoadedQuery, + setHasLoadedQuery, + renderModels, + setRenderModels + }), [ + showOptions, + setShowOptions, + showOutputNavigation, + setShowOutputNavigation, + config, + setConfig, + input, + setInput, + models, + setModels, + generatorCode, + setGeneratorCode, + loaded, + setLoaded, + showGeneratorCode, + setShowGeneratorCode, + error, + setError, + statusCode, + setStatusCode, + errorMessage, + setErrorMessage, + isLoaded, + setIsLoaded, + hasLoadedQuery, + setHasLoadedQuery, + renderModels, + setRenderModels, + ]); + + return ( + + {children} + + ); +} + +export const usePlaygroundContext = () => { + const context = useContext(PlaygroundContext); + if (!context) { + throw new Error('Playground was unable to load the context to display, please report this problem on GitHub.'); + } + return context; +}; \ No newline at end of file diff --git a/modelina-website/src/components/playground/Content.tsx b/modelina-website/src/components/playground/Content.tsx new file mode 100644 index 0000000000..6e6f457185 --- /dev/null +++ b/modelina-website/src/components/playground/Content.tsx @@ -0,0 +1,89 @@ +'use client'; + +import { FunctionComponent, useMemo } from 'react'; +import { PlaygroundGeneratedContext } from '../contexts/PlaygroundGeneratedContext'; +import { usePlaygroundContext } from '../contexts/PlaygroundContext'; +import MonacoEditorWrapper from '../MonacoEditorWrapper'; +import CustomError from '../CustomError'; +import OutputNavigation from './OutputNavigation'; +import { OptionsNavigation } from './OptionsNavigation'; +import GeneratedModelsComponent from './GeneratedModels'; + +interface ContentProps { + setNewConfig: (config: string, configValue: any, updateCode?: boolean) => void; + setNewQuery: (queryKey: string, queryValue: any) => void; + generateNewCode: (input: string) => void; +} + +export const Content: FunctionComponent = ({ setNewConfig, setNewQuery, generateNewCode }) => { + const { + config, + input, + setInput, + models, + loaded, + setLoaded, + error, + statusCode, + errorMessage, + showOptions, + showOutputNavigation, + } = usePlaygroundContext(); + + const PlaygroundGeneratedContextValue = useMemo(() => ({ + language: config.language, + models: models + }), [config.language, models]); + + return ( +
+ {/* OPTIONS & EDITOR */} +
+ { + showOptions &&
+ +
+ } +
+
+
+ { + setInput(change); + generateNewCode(change); + }} + editorDidMount={() => { + setLoaded({ ...loaded, editorLoaded: true }); + }} + language="json" + /> +
+
+
+
+ + {/* OUTPUT NAVIGATION AND OUTPUTS */} +
+ { + showOutputNavigation &&
+ +
+ } +
+
+ {error ? ( + + ) : ( + + + + )} +
+
+
+
+ ); +}; diff --git a/modelina-website/src/components/playground/GeneratedModels.tsx b/modelina-website/src/components/playground/GeneratedModels.tsx index 6665c4e044..d69deb07c1 100644 --- a/modelina-website/src/components/playground/GeneratedModels.tsx +++ b/modelina-website/src/components/playground/GeneratedModels.tsx @@ -1,127 +1,103 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { PlaygroundGeneratedContext } from '../contexts/PlaygroundGeneratedContext'; import MonacoEditorWrapper from '../MonacoEditorWrapper'; +import { usePlaygroundContext } from '../contexts/PlaygroundContext'; interface GeneratedModelsComponentProps { showAllInOneFile?: boolean; setNewQuery?: (queryKey: string, queryValue: string) => void; } -type GeneratedModelsComponentState = { - selectedModel?: string; -}; -class GeneratedModelsComponent extends React.Component< - GeneratedModelsComponentProps, - GeneratedModelsComponentState -> { - static contextType = PlaygroundGeneratedContext; - declare context: React.ContextType; - constructor(props: GeneratedModelsComponentProps) { - super(props); - this.setNewQuery = this.setNewQuery.bind(this); - this.state = { - selectedModel: '' - }; - } - - setNewQuery(modelName: string) { - if (this.props.setNewQuery) { - this.props.setNewQuery('selectedModel', modelName); - } - this.setState({ ...this.state, selectedModel: modelName }); - } +const GeneratedModelsComponent: React.FC = ({ + showAllInOneFile, + setNewQuery, +}) => { + const context = useContext(PlaygroundGeneratedContext); + const [selectedModel, setSelectedModel] = useState(''); + const { setRenderModels, generatorCode, showGeneratorCode, setShowGeneratorCode } = usePlaygroundContext(); - toShow() { + const toShow = () => { let selectedCode = ''; - let selectedModel = this.state.selectedModel; - if (this.context) { - if (this.props.showAllInOneFile === true) { - //Merge all models together - selectedCode = this.context.models - .map((model: any) => `${model.code}`) - .join('\n\n'); - } else if (this.state.selectedModel !== undefined && this.context.models) { - for (const model of this.context.models) { - if (model.name === this.state.selectedModel) { + let updatedSelectedModel = selectedModel; + + if (context) { + if (showAllInOneFile === true) { + selectedCode = context.models.map((model: any) => `${model.code}`).join('\n\n'); + } else if (selectedModel !== undefined && context.models) { + for (const model of context.models) { + if (model.name === selectedModel) { selectedCode = model.code; break; } } } - if (selectedCode === '' && (this.context.models && this.context.models.length > 0)) { - //If the model is not found default to first one - const defaultModel = this.context.models[0]; + if (selectedCode === '' && (context.models && context.models.length > 0)) { + const defaultModel = context.models[0]; selectedCode = defaultModel.code; - selectedModel = defaultModel.name; + updatedSelectedModel = defaultModel.name; } } - return { selectedCode, selectedModel }; - } - renderModels(selectedModel = '') { - if(this.context?.models){ - return this.context?.models.map((model, index) => { + return { selectedCode, updatedSelectedModel }; + }; + + const renderModels = (selectedModel = '') => { + if (context?.models) { + return context?.models.map((model) => { let backgroundColor; - if (model.name === selectedModel) { - backgroundColor = 'bg-blue-100'; + if (!showGeneratorCode && model.name === selectedModel) { + backgroundColor = 'bg-[#3c4450]'; } else { - backgroundColor = index % 2 === 0 ? 'bg-gray-50' : 'bg-white'; + backgroundColor = ''; } return (
{ - this.setNewQuery(model.name); + setNewQuery?.('selectedModel', model.name); + setSelectedModel(model.name); + setShowGeneratorCode(false); }} - className={`${backgroundColor} px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6`} + className={`${backgroundColor} hover:bg-[#4b5563] px-2 py-2 w-full text-left`} > -
{model.name}
+
{model.name}
); }); - }; - } - - render() { - const { selectedCode, selectedModel } = this.toShow(); - const modelsToRender = this.renderModels(selectedModel); - if (this.props.showAllInOneFile === true) { - return ( -
- -
- ); } + }; + + const { selectedCode, updatedSelectedModel } = toShow(); + + useEffect(() => { + const modelsToRender = renderModels(updatedSelectedModel); + setRenderModels(modelsToRender); + }, [updatedSelectedModel, showGeneratorCode]); + + if (showAllInOneFile === true) { return ( -
-
-
-

- Generated Models -

-

- This list contains all the generated models, select one to show - their generated code -

-
-
-
{modelsToRender}
-
-
-
- -
+
+
); } -} + + return ( +
+
+ +
+
+ ); +}; + export default GeneratedModelsComponent; \ No newline at end of file diff --git a/modelina-website/src/components/playground/OptionsNavigation.tsx b/modelina-website/src/components/playground/OptionsNavigation.tsx new file mode 100644 index 0000000000..e5bcaef587 --- /dev/null +++ b/modelina-website/src/components/playground/OptionsNavigation.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { + PlaygroundTypeScriptConfigContext, + PlaygroundCSharpConfigContext, + PlaygroundDartConfigContext, + PlaygroundGoConfigContext, + PlaygroundJavaConfigContext, + PlaygroundJavaScriptConfigContext, + PlaygroundKotlinConfigContext, + PlaygroundPythonConfigContext, + PlaygroundRustConfigContext, + PlaygroundCplusplusConfigContext, + PlaygroundGeneralConfigContext, + PlaygroundPhpConfigContext +} from '../contexts/PlaygroundConfigContext'; +import { usePlaygroundContext } from "../contexts/PlaygroundContext"; +import PlaygroundOptions from './PlaygroundOptions'; + +interface OptionsNavigationProps { + setNewConfig: (config: string, configValue: any, updateCode?: boolean) => void; +} + +export const OptionsNavigation: React.FunctionComponent = ({ setNewConfig }) => { + const { config } = usePlaygroundContext(); + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} diff --git a/modelina-website/src/components/playground/OutputNavigation.tsx b/modelina-website/src/components/playground/OutputNavigation.tsx new file mode 100644 index 0000000000..700b00c8e6 --- /dev/null +++ b/modelina-website/src/components/playground/OutputNavigation.tsx @@ -0,0 +1,26 @@ +import { usePlaygroundContext } from "../contexts/PlaygroundContext"; +import InfoModal from "../InfoModal"; + +interface OutputProps { } + +const OutputNavigation: React.FunctionComponent = () => { + const { renderModels, showGeneratorCode, setShowGeneratorCode } = usePlaygroundContext(); + return ( +
+ + +
+ +

+ This list contains all the generated models, select one to show their generated code. +

+
+
Generated Models
+
+ + {renderModels} +
+ ) +} + +export default OutputNavigation; \ No newline at end of file diff --git a/modelina-website/src/components/playground/Playground.tsx b/modelina-website/src/components/playground/Playground.tsx index 6c75009929..a078dd7d9c 100644 --- a/modelina-website/src/components/playground/Playground.tsx +++ b/modelina-website/src/components/playground/Playground.tsx @@ -1,33 +1,11 @@ -import React from 'react'; -import MonacoEditorWrapper from '../MonacoEditorWrapper'; +import React, { useEffect, useState } from 'react'; +import Router, { withRouter, NextRouter } from 'next/router'; +import { encode } from 'js-base64'; import { - defaultAsyncapiDocument, - ModelinaOptions, ModelinaQueryOptions, GenerateMessage, UpdateMessage } from '@/types'; -import Router, { withRouter, NextRouter } from 'next/router'; -import { encode } from 'js-base64'; -import GeneratedModelsComponent from './GeneratedModels'; -import PlaygroundOptions from './PlaygroundOptions'; -import Heading from '../typography/Heading'; -import Paragraph from '../typography/Paragraph'; -import { PlaygroundGeneratedContext } from '../contexts/PlaygroundGeneratedContext'; -import { - PlaygroundTypeScriptConfigContext, - PlaygroundCSharpConfigContext, - PlaygroundDartConfigContext, - PlaygroundGoConfigContext, - PlaygroundJavaConfigContext, - PlaygroundJavaScriptConfigContext, - PlaygroundKotlinConfigContext, - PlaygroundPythonConfigContext, - PlaygroundRustConfigContext, - PlaygroundCplusplusConfigContext, - PlaygroundGeneralConfigContext, - PlaygroundPhpConfigContext -} from '../contexts/PlaygroundConfigContext'; import { getTypeScriptGeneratorCode } from '@/helpers/GeneratorCode/TypeScriptGenerator'; import { getJavaScriptGeneratorCode } from '@/helpers/GeneratorCode/JavaScriptGenerator'; import { getJavaGeneratorCode } from '@/helpers/GeneratorCode/JavaGenerator'; @@ -37,456 +15,278 @@ import { getRustGeneratorCode } from '@/helpers/GeneratorCode/RustGenerator'; import { getPythonGeneratorCode } from '@/helpers/GeneratorCode/PythonGenerator'; import { getDartGeneratorCode } from '@/helpers/GeneratorCode/DartGenerator'; import { getCplusplusGeneratorCode } from '@/helpers/GeneratorCode/CplusplusGenerator'; -import CustomError from '../CustomError'; import { getKotlinGeneratorCode } from '@/helpers/GeneratorCode/KotlinGenerator'; import { getPhpGeneratorCode } from '@/helpers/GeneratorCode/PhpGenerator'; +import { usePlaygroundContext } from '../contexts/PlaygroundContext'; +import { Sidebar } from './Sidebar'; +import { Content } from './Content'; interface WithRouterProps { router: NextRouter; } - -interface ModelsGeneratorProps { - code: string; - name: string; -} interface ModelinaPlaygroundProps extends WithRouterProps { maxInputSize?: number; } -type ModelinaPlaygroundState = { - input: string; - models: ModelsGeneratorProps[]; - generatorCode: string; - loaded: { - editorLoaded: boolean; - hasReceivedCode: boolean; - }; - showGeneratorCode: boolean; - error: boolean; - statusCode: number; - errorMessage: string; -}; - -class Playground extends React.Component< - ModelinaPlaygroundProps, - ModelinaPlaygroundState -> { - config: ModelinaOptions = { - language: 'typescript', - propertyNamingFormat: 'default', - modelNamingFormat: 'default', - enumKeyNamingFormat: 'default', - indentationType: 'spaces', - showTypeMappingExample: false, - tsMarshalling: false, - tsModelType: 'class', - tsEnumType: 'enum', - tsModuleSystem: 'CJS', - tsIncludeDescriptions: false, - tsIncludeExampleFunction: false, - tsIncludeJsonBinPack: false, - csharpArrayType: 'Array', - csharpAutoImplemented: false, - csharpOverwriteHashcode: false, - csharpIncludeJson: false, - csharpOverwriteEqual: false, - csharpIncludeNewtonsoft: false, - csharpNamespace: 'asyncapi.models', - csharpNullable: false, - phpIncludeDescriptions: false, - phpNamespace: 'AsyncAPI/Models', - cplusplusNamespace: 'AsyncapiModels', - javaPackageName: 'asyncapi.models', - javaIncludeJackson: false, - javaIncludeMarshaling: false, - javaArrayType: 'Array', - javaOverwriteHashcode: false, - javaOverwriteEqual: false, - javaOverwriteToString: false, - javaJavaDocs: false, - javaJavaxAnnotation: false, - goPackageName: 'asyncapi.models', - kotlinPackageName: 'asyncapi.models' - }; - hasLoadedQuery: boolean = false; - constructor(props: ModelinaPlaygroundProps) { - super(props); - this.state = { - input: JSON.stringify(defaultAsyncapiDocument, null, 4), - models: [], - generatorCode: '', - loaded: { - editorLoaded: false, - hasReceivedCode: false - }, - showGeneratorCode: false, - error: false, - statusCode: 400, - errorMessage: 'Bad Request', - }; - this.setNewConfig = this.setNewConfig.bind(this); - this.setNewQuery = this.setNewQuery.bind(this); - this.generateNewCode = this.generateNewCode.bind(this); - } +const Playground: React.FC = (props) => { + const { + config, + setConfig, + input, + setModels, + setGeneratorCode, + loaded, + setLoaded, + setError, + setStatusCode, + setErrorMessage, + isLoaded, + setIsLoaded, + hasLoadedQuery, + setHasLoadedQuery, + } = usePlaygroundContext(); - setNewConfig(config: string, configValue: any, updateCode?: boolean) { - this.setNewQuery(config, configValue); - /* eslint-disable-next-line security/detect-object-injection */ - (this.config as any)[config] = configValue; - if(updateCode === true || updateCode === undefined) { - this.generateNewCode(this.state.input); - } - } + // To avoid hydration error + const [isMounted, setIsMounted] = useState(false); - /** - * Set a query key and value - */ - setNewQuery(queryKey: string, queryValue: any) { - const newQuery = { - query: { ...this.props.router.query } - }; - if(queryValue === false) { - delete newQuery.query[queryKey]; - } else { - /* eslint-disable-next-line security/detect-object-injection */ - newQuery.query[queryKey] = String(queryValue); - } - Router.push(newQuery, undefined, { scroll: false }); - } + useEffect(() => { + setIsMounted(true); + }, []); - /** - * Tell the socket io server that we want some code - */ - generateNewCode(input: string) { - try { - const message: GenerateMessage = { - ...this.config, - input: encode(JSON.stringify(JSON.parse(input))) - }; - if (message.input.length > (this.props.maxInputSize || 30000)) { - console.error('Input too large, use smaller example'); - this.setState({ ...this.state, error: true, errorMessage: 'Input too large, use smaller example', statusCode: 400 }); - } else { - const generators: { [key: string]: Function } = { - typescript: getTypeScriptGeneratorCode, - javascript: getJavaScriptGeneratorCode, - java: getJavaGeneratorCode, - go: getGoGeneratorCode, - csharp: getCSharpGeneratorCode, - rust: getRustGeneratorCode, - python: getPythonGeneratorCode, - dart: getDartGeneratorCode, - cplusplus: getCplusplusGeneratorCode, - kotlin: getKotlinGeneratorCode, - php: getPhpGeneratorCode - } - const generatorCode = generators[this.config.language](message); - fetch(`${process.env.NEXT_PUBLIC_API_PATH}/generate`, { - body: JSON.stringify(message), - method: 'POST' - }).then(async (res) => { - if (!res.ok) { - throw new Error(res.statusText); - } - const response: UpdateMessage = await res.json(); - this.setState({ - ...this.state, - generatorCode, - models: response.models, - loaded: { - ...this.state.loaded, - hasReceivedCode: true - }, - error: false, - statusCode: 200, - errorMessage: '', - }); - }).catch(error => { - console.error(error); - this.setState({ ...this.state, error: true, errorMessage: "Input is not an correct AsyncAPI document so it cannot be processed.", statusCode: 500 }); - }); - } - } catch (e: any) { - console.error(e); - this.setState({ ...this.state, error: true, errorMessage: "Input is not an correct AsyncAPI document so it cannot be processed.", statusCode: 400 }); - } - } - - render() { - const { loaded } = this.state; + useEffect(() => { const isHardLoaded = loaded.hasReceivedCode; const isSoftLoaded = loaded.editorLoaded; - const isLoaded = isHardLoaded && isSoftLoaded; + setIsLoaded(isHardLoaded && isSoftLoaded); - const query = this.props.router.query as ModelinaQueryOptions; + const query = props.router.query as ModelinaQueryOptions; if (query.language !== undefined) { - this.config.language = query.language as any; + setConfig({ ...config, language: query.language as any }); } if (query.enumKeyNamingFormat !== undefined) { - this.config.enumKeyNamingFormat = query.enumKeyNamingFormat as any; + setConfig({ ...config, enumKeyNamingFormat: query.enumKeyNamingFormat as any }); } if (query.propertyNamingFormat !== undefined) { - this.config.propertyNamingFormat = query.propertyNamingFormat as any; + setConfig({ ...config, propertyNamingFormat: query.propertyNamingFormat as any }); } if (query.modelNamingFormat !== undefined) { - this.config.modelNamingFormat = query.modelNamingFormat as any; + setConfig({ ...config, modelNamingFormat: query.modelNamingFormat as any }); } if (query.showTypeMappingExample !== undefined) { - this.config.showTypeMappingExample = query.showTypeMappingExample === 'true'; + setConfig({ ...config, showTypeMappingExample: query.showTypeMappingExample === 'true' }); } if (query.indentationType !== undefined) { - this.config.indentationType = query.indentationType as any; + setConfig({ ...config, indentationType: query.indentationType as any }); } if (query.tsMarshalling !== undefined) { - this.config.tsMarshalling = query.tsMarshalling === 'true'; + setConfig({ ...config, tsMarshalling: query.tsMarshalling === 'true' }); } if (query.tsModelType !== undefined) { - this.config.tsModelType = query.tsModelType as any; + setConfig({ ...config, tsModelType: query.tsModelType as any }); } if (query.tsEnumType !== undefined) { - this.config.tsEnumType = query.tsEnumType as any; + setConfig({ ...config, tsEnumType: query.tsEnumType as any }); } if (query.tsIncludeDescriptions !== undefined) { - this.config.tsIncludeDescriptions = - query.tsIncludeDescriptions === 'true'; + setConfig({ ...config, tsIncludeDescriptions: query.tsIncludeDescriptions === 'true' }); } if (query.tsIncludeJsonBinPack !== undefined) { - this.config.tsIncludeJsonBinPack = - query.tsIncludeJsonBinPack === 'true'; + setConfig({ ...config, tsIncludeJsonBinPack: query.tsIncludeJsonBinPack === 'true' }); } if (query.tsIncludeExampleFunction !== undefined) { - this.config.tsIncludeExampleFunction = - query.tsIncludeExampleFunction === 'true'; + setConfig({ ...config, tsIncludeExampleFunction: query.tsIncludeExampleFunction === 'true' }); } if (query.csharpArrayType !== undefined) { - this.config.csharpArrayType = query.csharpArrayType as any; + setConfig({ ...config, csharpArrayType: query.csharpArrayType as any }); } if (query.csharpAutoImplemented !== undefined) { - this.config.csharpAutoImplemented = - query.csharpAutoImplemented === 'true'; + setConfig({ ...config, csharpAutoImplemented: query.csharpAutoImplemented === 'true' }); } if (query.csharpOverwriteHashcode !== undefined) { - this.config.csharpOverwriteHashcode = - query.csharpOverwriteHashcode === 'true'; + setConfig({ ...config, csharpOverwriteHashcode: query.csharpOverwriteHashcode === 'true' }); } if (query.phpIncludeDescriptions !== undefined) { - this.config.phpIncludeDescriptions = - query.phpIncludeDescriptions === 'true'; + setConfig({ ...config, phpIncludeDescriptions: query.phpIncludeDescriptions === 'true' }); } if (query.phpNamespace !== undefined) { - this.config.phpNamespace = query.phpNamespace; + setConfig({ ...config, phpNamespace: query.phpNamespace }); } if (query.csharpIncludeJson !== undefined) { - this.config.csharpIncludeJson = - query.csharpIncludeJson === 'true'; + setConfig({ ...config, csharpIncludeJson: query.csharpIncludeJson === 'true' }); } if (query.csharpOverwriteEqual !== undefined) { - this.config.csharpOverwriteEqual = - query.csharpOverwriteEqual === 'true'; + setConfig({ ...config, csharpOverwriteEqual: query.csharpOverwriteEqual === 'true' }); } if (query.csharpIncludeNewtonsoft !== undefined) { - this.config.csharpIncludeNewtonsoft = - query.csharpIncludeNewtonsoft === 'true'; + setConfig({ ...config, csharpIncludeNewtonsoft: query.csharpIncludeNewtonsoft === 'true' }); } if (query.csharpNamespace !== undefined) { - this.config.csharpNamespace = query.csharpNamespace; + setConfig({ ...config, csharpNamespace: query.csharpNamespace }); } if (query.csharpNullable !== undefined) { - this.config.csharpNullable = query.csharpNullable === 'true'; + setConfig({ ...config, csharpNullable: query.csharpNullable === 'true' }); } - if(query.cplusplusNamespace !== undefined) { - this.config.cplusplusNamespace = query.cplusplusNamespace; + if (query.cplusplusNamespace !== undefined) { + setConfig({ ...config, cplusplusNamespace: query.cplusplusNamespace }); } if (query.javaPackageName !== undefined) { - this.config.javaPackageName = query.javaPackageName; + setConfig({ ...config, javaPackageName: query.javaPackageName }); } if (query.javaIncludeJackson !== undefined) { - this.config.javaIncludeJackson = - query.javaIncludeJackson === 'true'; + setConfig({ ...config, javaIncludeJackson: query.javaIncludeJackson === 'true' }); } if (query.javaIncludeMarshaling !== undefined) { - this.config.javaIncludeMarshaling = - query.javaIncludeMarshaling === 'true'; + setConfig({ ...config, javaIncludeMarshaling: query.javaIncludeMarshaling === 'true' }); } if (query.javaArrayType !== undefined) { - this.config.javaArrayType = query.javaArrayType as any; + setConfig({ ...config, javaArrayType: query.javaArrayType as any }); } if (query.javaOverwriteHashcode !== undefined) { - this.config.javaOverwriteHashcode = - query.javaOverwriteHashcode === 'true'; + setConfig({ ...config, javaOverwriteHashcode: query.javaOverwriteHashcode === 'true' }); } if (query.javaOverwriteEqual !== undefined) { - this.config.javaOverwriteEqual = - query.javaOverwriteEqual === 'true'; + setConfig({ ...config, javaOverwriteEqual: query.javaOverwriteEqual === 'true' }); } if (query.javaOverwriteToString !== undefined) { - this.config.javaOverwriteToString = - query.javaOverwriteToString === 'true'; + setConfig({ ...config, javaOverwriteToString: query.javaOverwriteToString === 'true' }); } if (query.javaJavaDocs !== undefined) { - this.config.javaJavaDocs = - query.javaJavaDocs === 'true'; + setConfig({ ...config, javaJavaDocs: query.javaJavaDocs === 'true' }); } if (query.javaJavaxAnnotation !== undefined) { - this.config.javaJavaxAnnotation = - query.javaJavaxAnnotation === 'true'; + setConfig({ ...config, javaJavaxAnnotation: query.javaJavaxAnnotation === 'true' }); } - if(query.goPackageName !== undefined) { - this.config.goPackageName = query.goPackageName; + if (query.goPackageName !== undefined) { + setConfig({ ...config, goPackageName: query.goPackageName }); } + if (query.kotlinPackageName !== undefined) { - this.config.kotlinPackageName = query.kotlinPackageName; + setConfig({ ...config, kotlinPackageName: query.kotlinPackageName }); } - if (this.props.router.isReady && !this.hasLoadedQuery) { - this.hasLoadedQuery = true; - this.generateNewCode(this.state.input); + + if (props.router.isReady && !hasLoadedQuery) { + setHasLoadedQuery(true); + generateNewCode(input); } + }, [props.router.isReady, hasLoadedQuery]); - let loader; - if (!isHardLoaded) { - loader = ( -
- Loading Modelina Playground. Connecting to playground server... -
- ); - } else if (!isSoftLoaded) { - loader = ( -
- Loading Modelina Playground. Rendering playground components... -
- ); - } - return ( -
-
- - Modelina Playground - - - Try out Modelina and see a small fraction of what it can do. Use it - to play around, with small examples, otherwise turn to the CLI or - library instead. - -
- {loader} -
-
-
-
-

- Modelina Options -

-

- Change the generation options, or see the Modelina - configuration you can use directly in your library -

-
+ const setNewConfig = (configName: string, configValue: any, updateCode?: boolean) => { + setNewQuery(configName, configValue); + /* eslint-disable-next-line security/detect-object-injection */ + (config as any)[configName] = configValue; + if (updateCode === true || updateCode === undefined) { + generateNewCode(input); + } + }; -
{ - this.setState({ ...this.state, showGeneratorCode: false }); - }} - className={`${!this.state.showGeneratorCode ? 'bg-blue-100' : 'bg-white' - } px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 basis-3/12`} - > -

- Options -

-
-
{ - this.setState({ ...this.state, showGeneratorCode: true }); - }} - className={`${this.state.showGeneratorCode ? 'bg-blue-100' : 'bg-white' - } px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 basis-3/12`} - > -

- Generator code -

+ /** + * Set a query key and value + */ + const setNewQuery = (queryKey: string, queryValue: any) => { + const newQuery = { + query: { ...props.router.query } + }; + + if (queryValue === false) { + delete newQuery.query[queryKey]; + } else { + /* eslint-disable-next-line security/detect-object-injection */ + newQuery.query[queryKey] = String(queryValue); + } + + Router.push(newQuery, undefined, { scroll: false }); + }; + + /** + * Tell the socket io server that we want some code + */ + const generateNewCode = (input: string) => { + try { + const message: GenerateMessage = { + ...config, + input: encode(JSON.stringify(JSON.parse(input))) + }; + + if (message.input.length > (props.maxInputSize || 30000)) { + console.error('Input too large, use a smaller example'); + setError(true); + setErrorMessage('Input too large, use a smaller example'); + setStatusCode(400); + } else { + const generators: { [key: string]: Function } = { + typescript: getTypeScriptGeneratorCode, + javascript: getJavaScriptGeneratorCode, + java: getJavaGeneratorCode, + go: getGoGeneratorCode, + csharp: getCSharpGeneratorCode, + rust: getRustGeneratorCode, + python: getPythonGeneratorCode, + dart: getDartGeneratorCode, + cplusplus: getCplusplusGeneratorCode, + kotlin: getKotlinGeneratorCode, + php: getPhpGeneratorCode + }; + + const generatorCode = generators[config.language](message); + + fetch(`${process.env.NEXT_PUBLIC_API_PATH}/generate`, { + body: JSON.stringify(message), + method: 'POST' + }).then(async (res) => { + if (!res.ok) { + throw new Error(res.statusText); + } + + const response: UpdateMessage = await res.json(); + setGeneratorCode(generatorCode); + setModels(response.models); + setLoaded({ + ...loaded, + hasReceivedCode: true + }); + setError(false); + setStatusCode(200); + setErrorMessage(''); + }).catch(error => { + console.error(error); + setError(true); + setErrorMessage("Input is not a correct AsyncAPI document, so it cannot be processed."); + setStatusCode(500); + }); + } + } catch (e: any) { + console.error(e); + setError(true); + setErrorMessage("Input is not a correct AsyncAPI document, so it cannot be processed."); + setStatusCode(400); + } + }; + + if (!isMounted) { + return null; + } + + return ( +
+ { + isLoaded + ? +
+ Loading Modelina Playground. Rendering playground components... +
+ : +
+
+
+
-
- {this.state.showGeneratorCode ? ( -
- +
+
- ) : ( - - - - - - - - - - - - - - - - - - - - - - - - - - )} -
-
-
- { - this.setState({ ...this.state, input: change }); - this.generateNewCode(change); - }} - editorDidMount={() => { - this.setState({ - loaded: { ...this.state.loaded, editorLoaded: true } - }); - }} - language="json" - />
-
- {this.state.error ? ( - - ) : ( - - - - )} -
-
-
- ); - } -} -export default withRouter(Playground); \ No newline at end of file + } +
+ ); +}; + +export default withRouter(Playground); diff --git a/modelina-website/src/components/playground/PlaygroundOptions.tsx b/modelina-website/src/components/playground/PlaygroundOptions.tsx index 85ec5a1de3..7948f27aca 100644 --- a/modelina-website/src/components/playground/PlaygroundOptions.tsx +++ b/modelina-website/src/components/playground/PlaygroundOptions.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; +import { PlaygroundGeneralConfigContext } from '../contexts/PlaygroundConfigContext'; import TypeScriptGeneratorOptions from './options/TypeScriptGeneratorOptions'; import GeneralOptions from './options/GeneralOptions'; import JavaScriptGeneratorOptions from './options/JavaScriptGeneratorOptions'; @@ -11,79 +12,70 @@ import RustGeneratorOptions from './options/RustGeneratorOptions'; import PythonGeneratorOptions from './options/PythonGeneratorOptions'; import CplusplusGeneratorOptions from './options/CplusplusGeneratorOptions'; import PhpGeneratorOptions from './options/PhpGeneratorOptions'; -import { PlaygroundGeneralConfigContext } from '../contexts/PlaygroundConfigContext'; -interface WithRouterProps { +interface PlaygroundOptionsProps { setNewConfig?: (queryKey: string, queryValue: string) => void; } -interface PlaygroundOptionsState {} -export const defaultState: PlaygroundOptionsState = {}; +const PlaygroundOptions: React.FC = ({ setNewConfig }) => { + const context = useContext(PlaygroundGeneralConfigContext); + const [generatorOptions, setGeneratorOptions] = useState(null); -class PlaygroundOptions extends React.Component< - WithRouterProps, - PlaygroundOptionsState -> { - static contextType = PlaygroundGeneralConfigContext; - declare context: React.ContextType; - constructor(props: any) { - super(props); - this.state = defaultState; - } - - render() { - let generatorOptions; - if (this.context?.language === 'typescript') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'javascript') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'csharp') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'dart') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'go') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'cplusplus') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'java') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'kotlin') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'rust') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'python') { - generatorOptions = ( - - ); - } else if (this.context?.language === 'php') { - generatorOptions = ( - - ); + const handleLanguageChange = () => { + switch (context?.language) { + case 'typescript': + setGeneratorOptions(prevOptions => ); + break; + case 'javascript': + setGeneratorOptions(); + break; + case 'csharp': + setGeneratorOptions(); + break; + case 'dart': + setGeneratorOptions(); + break; + case 'go': + setGeneratorOptions(); + break; + case 'cplusplus': + setGeneratorOptions(); + break; + case 'java': + setGeneratorOptions(); + break; + case 'kotlin': + setGeneratorOptions(); + break; + case 'rust': + setGeneratorOptions(); + break; + case 'python': + setGeneratorOptions(); + break; + case 'php': + setGeneratorOptions(); + break; + default: + setGeneratorOptions(prevOptions => null); + break; } - return ( -
- - {generatorOptions} -
- ); } -} + + useEffect(() => { + handleLanguageChange(); + }, []) + + useEffect(() => { + handleLanguageChange(); + }, [context?.language, setNewConfig]); + + return ( +
+ + {generatorOptions} +
+ ); +}; + export default PlaygroundOptions; diff --git a/modelina-website/src/components/playground/Sidebar.tsx b/modelina-website/src/components/playground/Sidebar.tsx new file mode 100644 index 0000000000..45e91a1424 --- /dev/null +++ b/modelina-website/src/components/playground/Sidebar.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { IoOptionsOutline } from 'react-icons/io5'; +import { VscListSelection } from 'react-icons/vsc'; +import { Tooltip } from './Tooltip'; +import { usePlaygroundContext } from '../contexts/PlaygroundContext'; + +interface SidebarItem { + name: string; + title: string; + isActive: boolean; + onClick: () => void; + icon: React.ReactNode; + tooltip: React.ReactNode; +} + +interface SidebarProps {} + +export const Sidebar: React.FunctionComponent = () => { + const { setShowOptions, setShowOutputNavigation } = usePlaygroundContext(); + const sidebarItems: SidebarItem[] = [ + // Options + { + name: 'options', + title: 'Options', + isActive: false, + onClick: () => { + setShowOptions((prevShowOptions) => !prevShowOptions); + }, + icon: , + tooltip: 'Show or hide all the options' + }, + // Output Explorer + { + name: 'outputExplorer', + title: 'Output', + isActive: false, + onClick: () => { + setShowOutputNavigation((prevShowOutputNavigation) => !prevShowOutputNavigation); + }, + icon: , + tooltip: 'Show or hide the list of output models' + } + ]; + + return ( +
+
+ {sidebarItems.map((item) => ( + + + + ))} +
+
+ ); +}; diff --git a/modelina-website/src/components/playground/Tooltip.tsx b/modelina-website/src/components/playground/Tooltip.tsx new file mode 100644 index 0000000000..7b60b9cde9 --- /dev/null +++ b/modelina-website/src/components/playground/Tooltip.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import Tippy, { TippyProps } from '@tippyjs/react'; + +export const Tooltip: React.FunctionComponent = ({ + placement = 'bottom', + animation = 'shift-away', + className = 'text-xs text-white bg-gray-700 text-center px-1 py-[3px]', + hideOnClick = false, + children, + ...rest +}) => { + return ( + + {children} + + ); +}; diff --git a/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx b/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx index eec5f32676..e9fd42e1a2 100644 --- a/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx +++ b/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx @@ -90,15 +90,15 @@ class CSharpGeneratorOptions extends React.Component< } } - debouncedSetNewConfig = debounce(this.props.setNewConfig || (() => {}), 500); + debouncedSetNewConfig = debounce(this.props.setNewConfig || (() => { }), 500); render() { return (
    -

    +

    CSharp Specific options

    -
  • +
  • In C#, a namespace is used to organize code into logical groups @@ -109,20 +109,20 @@ class CSharpGeneratorOptions extends React.Component< of your code.

    -
  • -
  • +
  • In C#, arrays are used to store collections of elements of the @@ -138,7 +138,7 @@ class CSharpGeneratorOptions extends React.Component< functionality and flexibility for working with collections.

    -
  • -
  • +
  • Auto-implemented properties in C# allow you to define properties @@ -165,7 +165,7 @@ class CSharpGeneratorOptions extends React.Component< boilerplate code you need to write.

    -
  • -
  • +
  • In C#, the GetHashCode() method is used to generate a hash code @@ -190,7 +190,7 @@ class CSharpGeneratorOptions extends React.Component< calculation based on the model's properties.

    -
  • -
  • +
  • The Equals() method in C# is used to compare two objects for @@ -216,7 +216,7 @@ class CSharpGeneratorOptions extends React.Component< comparisons.

    -
  • -
  • +
  • In C#, JSON serialization is the process of converting an object @@ -240,7 +240,7 @@ class CSharpGeneratorOptions extends React.Component< of the models.

    -
  • -
  • +
  • Newtonsoft.Json (Json.NET) is a popular third-party JSON @@ -264,7 +264,7 @@ class CSharpGeneratorOptions extends React.Component< using the Json.NET library.

    -
  • -
  • +
  • In C#, the nullable feature allows you to explicitly indicate @@ -288,7 +288,7 @@ class CSharpGeneratorOptions extends React.Component< flexibility when dealing with optional or unknown data values.

    -