diff --git a/app/web/package.json b/app/web/package.json index 4627cd0..d3f435e 100644 --- a/app/web/package.json +++ b/app/web/package.json @@ -13,7 +13,7 @@ ] }, "dependencies": { - "@headlessui/react": "^1.0.0", + "@headlessui/react": "^1.4.1", "@heroicons/react": "^1.0.4", "@material-ui/core": "^4.11.0", "@monaco-editor/react": "^4.0.11", diff --git a/app/web/src/components/EditorMenu/EditorMenu.tsx b/app/web/src/components/EditorMenu/EditorMenu.tsx index ffe2ef0..9ef9456 100644 --- a/app/web/src/components/EditorMenu/EditorMenu.tsx +++ b/app/web/src/components/EditorMenu/EditorMenu.tsx @@ -34,9 +34,16 @@ const EditorMenu = () => { ))} diff --git a/app/web/src/components/IdeContainer/IdeContainer.tsx b/app/web/src/components/IdeContainer/IdeContainer.tsx index 817528e..0286838 100644 --- a/app/web/src/components/IdeContainer/IdeContainer.tsx +++ b/app/web/src/components/IdeContainer/IdeContainer.tsx @@ -42,19 +42,40 @@ const ELEMENT_MAP = { } const TOOLBAR_MAP = { - Editor: ( + Editor: () => (
), - Viewer: ( + Viewer: (thunkDispatch) => (
- + + thunkDispatch((dispatch) => + dispatch({ + type: 'settingsButtonClicked', + payload: ['Settings', 'viewer'], + }) + ) + } + />
), - Console: ( + Console: (thunkDispatch) => (
- + + thunkDispatch((dispatch) => + dispatch({ + type: 'settingsButtonClicked', + payload: ['Settings', 'console'], + }) + ) + } + />
), } @@ -74,7 +95,7 @@ const IdeContainer = () => { return ( TOOLBAR_MAP[id]} + renderToolbar={() => TOOLBAR_MAP[id](thunkDispatch)} className={`${id.toLowerCase()} ${id.toLowerCase()}-tile`} > {id === 'Viewer' ? ( diff --git a/app/web/src/components/IdeHeader/IdeHeader.tsx b/app/web/src/components/IdeHeader/IdeHeader.tsx index a1f0bf7..7ca76a6 100644 --- a/app/web/src/components/IdeHeader/IdeHeader.tsx +++ b/app/web/src/components/IdeHeader/IdeHeader.tsx @@ -68,32 +68,37 @@ const IdeHeader = ({ return (
- {_projectId ? ( -
- - - - {_projectOwner} - - - +
+
+ + +
- ) : ( -
- )} + {_projectId && ( + <> + + + + {_projectOwner} + + + + + )} +
{!children ? ( + + + + ) +} const IdeSideBar = () => { + const { state, thunkDispatch } = useIdeContext() + + function onTabClick(name) { + return function () { + thunkDispatch({ type: 'settingsButtonClicked', payload: [name] }) + } + } + const selectedTab = React.useMemo( + () => sidebarCombinedConfig.find((item) => item.name === state.sideTray[0]), + [state.sideTray] + ) + return ( -
-
- - - -
- -
+
+
+
+ {sidebarTopConfig.map((topItem, i) => ( + onTabClick(topItem.name)} + onClick={onTabClick(topItem.name)} + key={'tab-' + i} + /> + ))} +
+
+ {sidebarBottomConfig.map((bottomItem, i) => ( + onTabClick(bottomItem.name)} + onClick={onTabClick(bottomItem.name)} + key={'tab-' + (sidebarTopConfig.length + i)} + /> + ))} +
+
+ {selectedTab?.panel && ( +
+

+ {selectedTab.name} +

+ {selectedTab.panel} +
+ )} +
) } diff --git a/app/web/src/components/IdeSideBar/sidebarConfig.tsx b/app/web/src/components/IdeSideBar/sidebarConfig.tsx new file mode 100644 index 0000000..1ac449b --- /dev/null +++ b/app/web/src/components/IdeSideBar/sidebarConfig.tsx @@ -0,0 +1,156 @@ +import React, { ReactNode } from 'react' +import { SvgNames } from 'src/components/Svg/Svg' +import { useIdeContext } from 'src/helpers/hooks/useIdeContext' + +interface SidebarConfigType { + name: string + icon: SvgNames + disabled: boolean + panel: ReactNode | null +} + +export const sidebarTopConfig: SidebarConfigType[] = [ + { + name: 'Files', + icon: 'files', + disabled: false, + panel: ( +
+

+ Coming Soon +

+
+

+ We're working on multi-file support in tandem with the GitHub + integration. +

+
+ ), + }, + { + name: 'GitHub', + icon: 'github', + disabled: false, + panel: ( +
+

+ Coming Soon +

+
+

+ This integration will allow you to sync a project with a GitHub repo + and push changes back to it as a commit! +

+
+ ), + }, + { + name: 'Visibility', + icon: 'eye', + disabled: true, + panel: null, + }, +] + +const DiscordLink = () => ( + + Discord + +) + +const settingsConfig = [ + { + title: 'Editor', + name: 'editor', + open: false, + content: ( +
+

+ Coming Soon +

+
+

+ We're building configuration settings for the Viewer pane now. Join us on if you want to lend a hand! +

+
+ ), + }, + { + title: 'Viewer', + name: 'viewer', + open: false, + content: ( +
+

+ Coming Soon +

+
+

+ We're building configuration settings for the Viewer pane now. Join us on if you want to lend a hand! +

+
+ ), + }, + { + title: 'Console', + name: 'console', + open: false, + content: ( +
+

+ Coming Soon +

+
+

+ We're building configuration settings for the Viewer pane now. Join us on if you want to lend a hand! +

+
+ ), + }, +] + +export const sidebarBottomConfig: SidebarConfigType[] = [ + { + name: 'Settings', + icon: 'gear', + disabled: false, + panel: , + }, +] + +export const sidebarCombinedConfig = [ + ...sidebarTopConfig, + ...sidebarBottomConfig, +] + +function SettingsMenu({ parentName }: { parentName: string }) { + const { state, thunkDispatch } = useIdeContext() + return ( +
+ {settingsConfig.map((item) => ( +
{ + e.preventDefault() + thunkDispatch((dispatch) => + dispatch({ + type: 'settingsButtonClicked', + payload: [parentName, item.name], + }) + ) + }} + > + + {item.title} + + {item.content} +
+ ))} +
+ ) +} diff --git a/app/web/src/components/IdeWrapper/IdeWrapper.tsx b/app/web/src/components/IdeWrapper/IdeWrapper.tsx index 1a52123..7b52242 100644 --- a/app/web/src/components/IdeWrapper/IdeWrapper.tsx +++ b/app/web/src/components/IdeWrapper/IdeWrapper.tsx @@ -30,16 +30,18 @@ const IdeWrapper = ({ cadPackage }: Props) => { } return ( -
+
-
- -
-
- - + +
+
+ +
+
+ +
diff --git a/app/web/src/components/PanelToolbar/PanelToolbar.tsx b/app/web/src/components/PanelToolbar/PanelToolbar.tsx index c8f8826..67c67a0 100644 --- a/app/web/src/components/PanelToolbar/PanelToolbar.tsx +++ b/app/web/src/components/PanelToolbar/PanelToolbar.tsx @@ -1,4 +1,4 @@ -import { useContext } from 'react' +import { MouseEventHandler, useContext } from 'react' import { MosaicWindowContext } from 'react-mosaic-component' import Svg from 'src/components/Svg/Svg' import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessage/OpenscadStaticImageMessage' @@ -6,9 +6,11 @@ import OpenscadStaticImageMessage from 'src/components/OpenscadStaticImageMessag const PanelToolbar = ({ panelName, showTopGradient, + onClick, }: { panelName: 'Viewer' | 'Console' showTopGradient?: boolean + onClick?: MouseEventHandler }) => { const { mosaicWindowActions } = useContext(MosaicWindowContext) return ( @@ -19,9 +21,13 @@ const PanelToolbar = ({
{panelName === 'Viewer' && } diff --git a/app/web/src/components/ProjectProfile/ProjectProfile.tsx b/app/web/src/components/ProjectProfile/ProjectProfile.tsx index 3d4c83b..399021c 100644 --- a/app/web/src/components/ProjectProfile/ProjectProfile.tsx +++ b/app/web/src/components/ProjectProfile/ProjectProfile.tsx @@ -70,12 +70,6 @@ const ProjectProfile = ({ <>
- - - {}} projectTitle={project?.title} diff --git a/app/web/src/components/Svg/Svg.tsx b/app/web/src/components/Svg/Svg.tsx index 38d4d66..7e47e7c 100644 --- a/app/web/src/components/Svg/Svg.tsx +++ b/app/web/src/components/Svg/Svg.tsx @@ -9,12 +9,15 @@ export type SvgNames = | 'dots-vertical' | 'drag-grid' | 'exclamation-circle' + | 'eye' | 'favicon' + | 'files' | 'flag' | 'floppy-disk' | 'fork' | 'fork-new' | 'gear' + | 'github' | 'lightbulb' | 'logout' | 'mac-cmd-key' @@ -176,6 +179,23 @@ const Svg = ({ /> ), + eye: ( + + + + + ), favicon: ( @@ -275,6 +295,22 @@ const Svg = ({ ), + files: ( + + + + + ), flag: ( ), + github: ( + + + + ), lightbulb: (
- - - {}} projectOwner={user?.userName} diff --git a/app/web/src/helpers/hooks/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts index 8945841..0e64da2 100644 --- a/app/web/src/helpers/hooks/useIdeState.ts +++ b/app/web/src/helpers/hooks/useIdeState.ts @@ -115,6 +115,7 @@ export interface State { viewerSize: { width: number; height: number } isLoading: boolean threeInstance: RootState + sideTray: string[] // could probably be an array of a union type } const code = '' @@ -146,103 +147,133 @@ export const initialState: State = { viewerSize: { width: 0, height: 0 }, isLoading: false, threeInstance: null, + sideTray: [], } -export const useIdeState = (): [State, (actionOrThunk: any) => any] => { - const reducer = (state: State, { type, payload }): State => { - switch (type) { - case 'initIde': - return { - ...state, - code: - payload.code || - // localStorage.getItem(makeCodeStoreKey(payload.cadPackage)) || - initCodeMap[payload.cadPackage] || - '', - ideType: payload.cadPackage, - } - case 'updateCode': - return { ...state, code: payload } - case 'healthyRender': - const customizerParams: CadhubParams[] = payload?.customizerParams - ?.length - ? payload.customizerParams - : state.customizerParams - const currentParameters = {} - customizerParams.forEach((param) => { - currentParameters[param.name] = - typeof state?.currentParameters?.[param.name] !== 'undefined' - ? state?.currentParameters?.[param.name] - : param.initial - }) - return { - ...state, - objectData: { - ...state.objectData, - type: payload.objectData?.type, - data: payload.objectData?.data, - }, - customizerParams, - currentParameters, - consoleMessages: payload.message - ? [...state.consoleMessages, payload.message] - : payload.message, - isLoading: false, - } - case 'errorRender': - return { - ...state, - consoleMessages: payload.message - ? [...state.consoleMessages, payload.message] - : payload.message, - isLoading: false, - } - case 'setCurrentCustomizerParams': - if (!Object.keys(payload || {}).length) return state - return { - ...state, - currentParameters: payload, - } - case 'setLayout': - return { - ...state, - layout: payload.message, - } - case 'updateCamera': - return { - ...state, - camera: payload.camera, - } - case 'updateViewerSize': - return { - ...state, - viewerSize: payload.viewerSize, - } - case 'setLoading': - return { - ...state, - isLoading: true, - } - case 'resetLoading': - return { - ...state, - isLoading: false, - } - case 'resetLayout': - return { - ...state, - layout: initialLayout, - } - case 'setThreeInstance': - return { - ...state, - threeInstance: payload, - } - default: - return state - } - } +const reducer = (state: State, { type, payload }): State => { + switch (type) { + case 'initIde': + return { + ...state, + code: + payload.code || + // localStorage.getItem(makeCodeStoreKey(payload.cadPackage)) || + initCodeMap[payload.cadPackage] || + '', + ideType: payload.cadPackage, + } + case 'updateCode': + return { ...state, code: payload } + case 'healthyRender': + const customizerParams: CadhubParams[] = payload?.customizerParams?.length + ? payload.customizerParams + : state.customizerParams + const currentParameters = {} + customizerParams.forEach((param) => { + currentParameters[param.name] = + typeof state?.currentParameters?.[param.name] !== 'undefined' + ? state?.currentParameters?.[param.name] + : param.initial + }) + return { + ...state, + objectData: { + ...state.objectData, + type: payload.objectData?.type, + data: payload.objectData?.data, + }, + customizerParams, + currentParameters, + consoleMessages: payload.message + ? [...state.consoleMessages, payload.message] + : payload.message, + isLoading: false, + } + case 'errorRender': + return { + ...state, + consoleMessages: payload.message + ? [...state.consoleMessages, payload.message] + : payload.message, + isLoading: false, + } + case 'setCurrentCustomizerParams': + if (!Object.keys(payload || {}).length) return state + return { + ...state, + currentParameters: payload, + } + case 'setLayout': + return { + ...state, + layout: payload.message, + } + case 'updateCamera': + return { + ...state, + camera: payload.camera, + } + case 'updateViewerSize': + return { + ...state, + viewerSize: payload.viewerSize, + } + case 'setLoading': + return { + ...state, + isLoading: true, + } + case 'resetLoading': + return { + ...state, + isLoading: false, + } + case 'resetLayout': + return { + ...state, + layout: initialLayout, + } + case 'setThreeInstance': + return { + ...state, + threeInstance: payload, + } + case 'settingsButtonClicked': + const isReClick = + state.sideTray.length && + state.sideTray.length === payload.length && + state.sideTray.every((original, index) => original === payload[index]) + const payloadInOriginal = + payload.length && state.sideTray.indexOf(payload[0]) + const isAncestorClick = + state.sideTray.length && + state.sideTray.length > payload.length && + payloadInOriginal >= 0 && + payload.every( + (incoming, i) => incoming === state.sideTray[i + payloadInOriginal] + ) + + if (isReClick) { + return { + ...state, + sideTray: state.sideTray.slice(0, -1), + } + } else if (isAncestorClick) { + return { + ...state, + sideTray: state.sideTray.slice(0, payload.length * -1 - 1), + } + } + return { + ...state, + sideTray: payload, + } + default: + return state + } +} +export const useIdeState = (): [State, (actionOrThunk: any) => any] => { const [state, dispatch] = useReducer(reducer, initialState) mutableState = state const getState = (): State => mutableState diff --git a/app/web/src/index.css b/app/web/src/index.css index e130ac7..b3ce904 100644 --- a/app/web/src/index.css +++ b/app/web/src/index.css @@ -18,17 +18,30 @@ font-family: 'Fira Sans', ui-sans-serif, system-ui, -apple-system, system-ui, "Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; } - /* custom scrollbar */ .ch-scrollbar::-webkit-scrollbar { - @apply w-3; + @apply w-3; } - + .ch-scrollbar::-webkit-scrollbar-track { @apply bg-ch-gray-700; } - + .ch-scrollbar::-webkit-scrollbar-thumb { - @apply bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-60; + @apply bg-ch-pink-800 bg-opacity-30 hover:bg-opacity-60; + } + +} + +@layer components { + .tabToggle { + @apply text-ch-gray-300 p-3 mb-1 flex justify-center; + } + .tabToggle.active { + @apply bg-ch-pink-800 text-ch-pink-300 bg-opacity-30; + } + .tabToggle.disabled { + @apply text-ch-gray-550 cursor-not-allowed; + } } diff --git a/app/yarn.lock b/app/yarn.lock index 2d29d4f..20e5d23 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -2007,10 +2007,10 @@ resolved "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz" integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== -"@headlessui/react@^1.0.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.2.0.tgz" - integrity sha512-19DkLz8gDgbi+WvkoTzi9vs0NK9TJf94vbYhMzB4LYJo03Kili0gmvXT9CiKZoxXZ7YAvy/b1U1oQKEnjWrqxw== +"@headlessui/react@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.4.1.tgz#0a8dbb20e1d63dcea55bfc3ab1b87637aaac7777" + integrity sha512-gL6Ns5xQM57cZBzX6IVv6L7nsam8rDEpRhs5fg28SN64ikfmuuMgunc+Rw5C1cMScnvFM+cz32ueVrlSFEVlSg== "@heroicons/react@^1.0.4": version "1.0.4"