From 7c3d127749f7a42d88917347c7990b49e1f73db6 Mon Sep 17 00:00:00 2001 From: Jerens Lensun <54782057+jerensl@users.noreply.github.com> Date: Sun, 23 Jun 2024 00:03:59 +0800 Subject: [PATCH] style(website): make Playground Mobile Responsive (#2028) Co-authored-by: Ashmit JaiSarita Gupta <43639341+devilkiller-ag@users.noreply.github.com>%0ACo-authored-by: Jonas Lagoni --- modelina-website/package-lock.json | 14 ++ modelina-website/package.json | 1 + .../components/contexts/PlaygroundContext.tsx | 153 +++++++------- .../src/components/playground/Content.tsx | 132 ++++++++----- .../src/components/playground/Playground.tsx | 187 ++++++++++++------ .../src/components/playground/Sidebar.tsx | 49 ++++- modelina-website/src/pages/playground.tsx | 38 +--- modelina-website/tailwind.config.js | 142 ++++++------- 8 files changed, 424 insertions(+), 292 deletions(-) diff --git a/modelina-website/package-lock.json b/modelina-website/package-lock.json index 867424277a..47d7dcbc9f 100644 --- a/modelina-website/package-lock.json +++ b/modelina-website/package-lock.json @@ -13,6 +13,7 @@ "@tailwindcss/line-clamp": "^0.4.0", "@tailwindcss/typography": "^0.5.9", "@tippyjs/react": "^4.2.6", + "clsx": "^2.1.1", "cssnano": "^5.1.14", "js-base64": "^3.7.4", "lodash": "^4.17.21", @@ -1231,6 +1232,14 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7692,6 +7701,11 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", diff --git a/modelina-website/package.json b/modelina-website/package.json index 4806ccb794..6fe2494e0b 100644 --- a/modelina-website/package.json +++ b/modelina-website/package.json @@ -21,6 +21,7 @@ "@tailwindcss/line-clamp": "^0.4.0", "@tailwindcss/typography": "^0.5.9", "@tippyjs/react": "^4.2.6", + "clsx": "^2.1.1", "cssnano": "^5.1.14", "js-base64": "^3.7.4", "lodash": "^4.17.21", diff --git a/modelina-website/src/components/contexts/PlaygroundContext.tsx b/modelina-website/src/components/contexts/PlaygroundContext.tsx index 9ed959ec20..b062694bc7 100644 --- a/modelina-website/src/components/contexts/PlaygroundContext.tsx +++ b/modelina-website/src/components/contexts/PlaygroundContext.tsx @@ -20,6 +20,8 @@ interface LoadedState { } interface PlaygroundContextProps { + showInputEditor: boolean; + setShowInputEditor: Dispatch>; showOptions: boolean; setShowOptions: Dispatch>; showOutputNavigation: boolean; @@ -50,9 +52,13 @@ interface PlaygroundContextProps { setRenderModels: (models: React.ReactNode) => void; } -const PlaygroundContext = createContext(undefined); +const PlaygroundContext = createContext( + undefined +); -export const PlaygroundContextProvider: React.FC<{ children: React.ReactNode; }> = ({ children }) => { +export const PlaygroundContextProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { const defaultConfig: ModelinaOptions = { language: 'typescript', propertyNamingFormat: 'default', @@ -94,15 +100,18 @@ export const PlaygroundContextProvider: React.FC<{ children: React.ReactNode; }> kotlinPackageName: 'asyncapi.models' }; + const [showInputEditor, setShowInputEditor] = useState(true); 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 [input, setInput] = useState( + JSON.stringify(defaultAsyncapiDocument, null, 4) + ); const [models, setModels] = useState([]); const [generatorCode, setGeneratorCode] = useState(''); const [loaded, setLoaded] = useState({ editorLoaded: false, - hasReceivedCode: false, + hasReceivedCode: false }); const [showGeneratorCode, setShowGeneratorCode] = useState(false); const [error, setError] = useState(false); @@ -110,79 +119,89 @@ export const PlaygroundContextProvider: React.FC<{ children: React.ReactNode; }> const [errorMessage, setErrorMessage] = useState('Bad Request'); const [isLoaded, setIsLoaded] = useState(false); const [hasLoadedQuery, setHasLoadedQuery] = useState(false); - const [renderModels, setRenderModels] = React.useState(null); + 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, - ]); + const contextValue = useMemo( + () => ({ + showInputEditor, + setShowInputEditor, + 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 + }), + [ + showInputEditor, + setShowInputEditor, + 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.'); + 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 index 6e6f457185..465ed7bba3 100644 --- a/modelina-website/src/components/playground/Content.tsx +++ b/modelina-website/src/components/playground/Content.tsx @@ -8,17 +8,27 @@ import CustomError from '../CustomError'; import OutputNavigation from './OutputNavigation'; import { OptionsNavigation } from './OptionsNavigation'; import GeneratedModelsComponent from './GeneratedModels'; +import clsx from 'clsx'; interface ContentProps { - setNewConfig: (config: string, configValue: any, updateCode?: boolean) => void; + 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 }) => { +export const Content: FunctionComponent = ({ + setNewConfig, + setNewQuery, + generateNewCode +}) => { const { config, input, + showInputEditor, setInput, models, loaded, @@ -27,63 +37,81 @@ export const Content: FunctionComponent = ({ setNewConfig, setNewQ statusCode, errorMessage, showOptions, - showOutputNavigation, + showOutputNavigation } = usePlaygroundContext(); - const PlaygroundGeneratedContextValue = useMemo(() => ({ - language: config.language, - models: models - }), [config.language, models]); + const PlaygroundGeneratedContextValue = useMemo( + () => ({ + language: config.language, + 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 ? ( - - ) : ( - - - - )} -
+
+ +
+
+
+ { + setInput(change); + generateNewCode(change); + }} + editorDidMount={() => { + setLoaded({ ...loaded, editorLoaded: true }); + }} + language="json" + />
+
+ {error ? ( + + ) : ( + + + + )} +
); }; diff --git a/modelina-website/src/components/playground/Playground.tsx b/modelina-website/src/components/playground/Playground.tsx index fae6a49957..5f269da3af 100644 --- a/modelina-website/src/components/playground/Playground.tsx +++ b/modelina-website/src/components/playground/Playground.tsx @@ -1,11 +1,7 @@ import React, { useEffect, useState } from 'react'; import Router, { withRouter, NextRouter } from 'next/router'; import { encode } from 'js-base64'; -import { - ModelinaQueryOptions, - GenerateMessage, - UpdateMessage -} from '@/types'; +import { ModelinaQueryOptions, GenerateMessage, UpdateMessage } from '@/types'; import { getTypeScriptGeneratorCode } from '@/helpers/GeneratorCode/TypeScriptGenerator'; import { getJavaScriptGeneratorCode } from '@/helpers/GeneratorCode/JavaScriptGenerator'; import { getJavaGeneratorCode } from '@/helpers/GeneratorCode/JavaGenerator'; @@ -44,7 +40,7 @@ const Playground: React.FC = (props) => { isLoaded, setIsLoaded, hasLoadedQuery, - setHasLoadedQuery, + setHasLoadedQuery } = usePlaygroundContext(); // To avoid hydration error @@ -64,16 +60,28 @@ const Playground: React.FC = (props) => { setConfig({ ...config, language: query.language as any }); } if (query.enumKeyNamingFormat !== undefined) { - setConfig({ ...config, enumKeyNamingFormat: query.enumKeyNamingFormat as any }); + setConfig({ + ...config, + enumKeyNamingFormat: query.enumKeyNamingFormat as any + }); } if (query.propertyNamingFormat !== undefined) { - setConfig({ ...config, propertyNamingFormat: query.propertyNamingFormat as any }); + setConfig({ + ...config, + propertyNamingFormat: query.propertyNamingFormat as any + }); } if (query.modelNamingFormat !== undefined) { - setConfig({ ...config, modelNamingFormat: query.modelNamingFormat as any }); + setConfig({ + ...config, + modelNamingFormat: query.modelNamingFormat as any + }); } if (query.showTypeMappingExample !== undefined) { - setConfig({ ...config, showTypeMappingExample: query.showTypeMappingExample === 'true' }); + setConfig({ + ...config, + showTypeMappingExample: query.showTypeMappingExample === 'true' + }); } if (query.indentationType !== undefined) { setConfig({ ...config, indentationType: query.indentationType as any }); @@ -91,37 +99,64 @@ const Playground: React.FC = (props) => { setConfig({ ...config, tsMapType: query.tsMapType as any }); } if (query.tsIncludeDescriptions !== undefined) { - setConfig({ ...config, tsIncludeDescriptions: query.tsIncludeDescriptions === 'true' }); + setConfig({ + ...config, + tsIncludeDescriptions: query.tsIncludeDescriptions === 'true' + }); } if (query.tsIncludeJsonBinPack !== undefined) { - setConfig({ ...config, tsIncludeJsonBinPack: query.tsIncludeJsonBinPack === 'true' }); + setConfig({ + ...config, + tsIncludeJsonBinPack: query.tsIncludeJsonBinPack === 'true' + }); } if (query.tsIncludeExampleFunction !== undefined) { - setConfig({ ...config, tsIncludeExampleFunction: query.tsIncludeExampleFunction === 'true' }); + setConfig({ + ...config, + tsIncludeExampleFunction: query.tsIncludeExampleFunction === 'true' + }); } if (query.csharpArrayType !== undefined) { setConfig({ ...config, csharpArrayType: query.csharpArrayType as any }); } if (query.csharpAutoImplemented !== undefined) { - setConfig({ ...config, csharpAutoImplemented: query.csharpAutoImplemented === 'true' }); + setConfig({ + ...config, + csharpAutoImplemented: query.csharpAutoImplemented === 'true' + }); } if (query.csharpOverwriteHashcode !== undefined) { - setConfig({ ...config, csharpOverwriteHashcode: query.csharpOverwriteHashcode === 'true' }); + setConfig({ + ...config, + csharpOverwriteHashcode: query.csharpOverwriteHashcode === 'true' + }); } if (query.phpIncludeDescriptions !== undefined) { - setConfig({ ...config, phpIncludeDescriptions: query.phpIncludeDescriptions === 'true' }); + setConfig({ + ...config, + phpIncludeDescriptions: query.phpIncludeDescriptions === 'true' + }); } if (query.phpNamespace !== undefined) { setConfig({ ...config, phpNamespace: query.phpNamespace }); } if (query.csharpIncludeJson !== undefined) { - setConfig({ ...config, csharpIncludeJson: query.csharpIncludeJson === 'true' }); + setConfig({ + ...config, + csharpIncludeJson: query.csharpIncludeJson === 'true' + }); } if (query.csharpOverwriteEqual !== undefined) { - setConfig({ ...config, csharpOverwriteEqual: query.csharpOverwriteEqual === 'true' }); + setConfig({ + ...config, + csharpOverwriteEqual: query.csharpOverwriteEqual === 'true' + }); } if (query.csharpIncludeNewtonsoft !== undefined) { - setConfig({ ...config, csharpIncludeNewtonsoft: query.csharpIncludeNewtonsoft === 'true' }); + setConfig({ + ...config, + csharpIncludeNewtonsoft: query.csharpIncludeNewtonsoft === 'true' + }); } if (query.csharpNamespace !== undefined) { setConfig({ ...config, csharpNamespace: query.csharpNamespace }); @@ -136,28 +171,46 @@ const Playground: React.FC = (props) => { setConfig({ ...config, javaPackageName: query.javaPackageName }); } if (query.javaIncludeJackson !== undefined) { - setConfig({ ...config, javaIncludeJackson: query.javaIncludeJackson === 'true' }); + setConfig({ + ...config, + javaIncludeJackson: query.javaIncludeJackson === 'true' + }); } if (query.javaIncludeMarshaling !== undefined) { - setConfig({ ...config, javaIncludeMarshaling: query.javaIncludeMarshaling === 'true' }); + setConfig({ + ...config, + javaIncludeMarshaling: query.javaIncludeMarshaling === 'true' + }); } if (query.javaArrayType !== undefined) { setConfig({ ...config, javaArrayType: query.javaArrayType as any }); } if (query.javaOverwriteHashcode !== undefined) { - setConfig({ ...config, javaOverwriteHashcode: query.javaOverwriteHashcode === 'true' }); + setConfig({ + ...config, + javaOverwriteHashcode: query.javaOverwriteHashcode === 'true' + }); } if (query.javaOverwriteEqual !== undefined) { - setConfig({ ...config, javaOverwriteEqual: query.javaOverwriteEqual === 'true' }); + setConfig({ + ...config, + javaOverwriteEqual: query.javaOverwriteEqual === 'true' + }); } if (query.javaOverwriteToString !== undefined) { - setConfig({ ...config, javaOverwriteToString: query.javaOverwriteToString === 'true' }); + setConfig({ + ...config, + javaOverwriteToString: query.javaOverwriteToString === 'true' + }); } if (query.javaJavaDocs !== undefined) { setConfig({ ...config, javaJavaDocs: query.javaJavaDocs === 'true' }); } if (query.javaJavaxAnnotation !== undefined) { - setConfig({ ...config, javaJavaxAnnotation: query.javaJavaxAnnotation === 'true' }); + setConfig({ + ...config, + javaJavaxAnnotation: query.javaJavaxAnnotation === 'true' + }); } if (query.goPackageName !== undefined) { setConfig({ ...config, goPackageName: query.goPackageName }); @@ -185,7 +238,11 @@ const Playground: React.FC = (props) => { } }, [props.router.isReady, hasLoadedQuery]); - const setNewConfig = (configName: string, configValue: any, updateCode?: boolean) => { + const setNewConfig = ( + configName: string, + configValue: any, + updateCode?: boolean + ) => { setNewQuery(configName, configValue); /* eslint-disable-next-line security/detect-object-injection */ (config as any)[configName] = configValue; @@ -248,32 +305,38 @@ const Playground: React.FC = (props) => { 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); - } + }) + .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 + 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); }); - 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."); + setErrorMessage( + 'Input is not a correct AsyncAPI document, so it cannot be processed.' + ); setStatusCode(400); } }; @@ -284,24 +347,20 @@ const Playground: React.FC = (props) => { return (
- { - isLoaded - ? -
- Loading Modelina Playground. Rendering playground components... -
- : -
-
-
- -
-
- -
-
-
- } + {isLoaded ? ( +
+ Loading Modelina Playground. Rendering playground components... +
+ ) : ( +
+ + +
+ )}
); }; diff --git a/modelina-website/src/components/playground/Sidebar.tsx b/modelina-website/src/components/playground/Sidebar.tsx index 45e91a1424..16f3496de1 100644 --- a/modelina-website/src/components/playground/Sidebar.tsx +++ b/modelina-website/src/components/playground/Sidebar.tsx @@ -1,13 +1,17 @@ import React from 'react'; import { IoOptionsOutline } from 'react-icons/io5'; import { VscListSelection } from 'react-icons/vsc'; +import { LuFileInput, LuFileOutput } from 'react-icons/lu'; import { Tooltip } from './Tooltip'; import { usePlaygroundContext } from '../contexts/PlaygroundContext'; +import clsx from 'clsx'; interface SidebarItem { name: string; title: string; isActive: boolean; + isShow: boolean; + mobileOnly: boolean; onClick: () => void; icon: React.ReactNode; tooltip: React.ReactNode; @@ -16,13 +20,40 @@ interface SidebarItem { interface SidebarProps {} export const Sidebar: React.FunctionComponent = () => { - const { setShowOptions, setShowOutputNavigation } = usePlaygroundContext(); + const { + setShowOptions, + setShowOutputNavigation, + setShowInputEditor, + showInputEditor, + showOptions + } = usePlaygroundContext(); const sidebarItems: SidebarItem[] = [ + // Input/Output Editor + { + name: 'input-editor', + title: 'Input Editor', + isActive: false, + isShow: true, + mobileOnly: true, + onClick: () => { + setShowInputEditor((prevShowOptions) => !prevShowOptions); + setShowOutputNavigation(false); + setShowOptions(false); + }, + icon: showInputEditor ? ( + + ) : ( + + ), + tooltip: `Show ${showInputEditor ? 'Input Editor' : 'Output Editor'}` + }, // Options { name: 'options', title: 'Options', - isActive: false, + isActive: showOptions, + isShow: true, + mobileOnly: false, onClick: () => { setShowOptions((prevShowOptions) => !prevShowOptions); }, @@ -34,8 +65,12 @@ export const Sidebar: React.FunctionComponent = () => { name: 'outputExplorer', title: 'Output', isActive: false, + isShow: showInputEditor, + mobileOnly: false, onClick: () => { - setShowOutputNavigation((prevShowOutputNavigation) => !prevShowOutputNavigation); + setShowOutputNavigation( + (prevShowOutputNavigation) => !prevShowOutputNavigation + ); }, icon: , tooltip: 'Show or hide the list of output models' @@ -55,7 +90,13 @@ export const Sidebar: React.FunctionComponent = () => {