diff --git a/app/web/package.json b/app/web/package.json index 92b9ba2..234eea2 100644 --- a/app/web/package.json +++ b/app/web/package.json @@ -42,6 +42,7 @@ "react-helmet": "^6.1.0", "react-image-crop": "^8.6.6", "react-mosaic-component": "^4.1.1", + "react-tabs": "^3.2.2", "react-three-fiber": "^5.3.19", "rich-markdown-editor": "^11.0.2", "styled-components": "^5.2.0", diff --git a/app/web/src/components/EditorMenu/EditorMenu.tsx b/app/web/src/components/EditorMenu/EditorMenu.tsx index aee8553..eb31429 100644 --- a/app/web/src/components/EditorMenu/EditorMenu.tsx +++ b/app/web/src/components/EditorMenu/EditorMenu.tsx @@ -1,6 +1,20 @@ +import { useContext } from 'react' +import { Menu } from '@headlessui/react' + +import { IdeContext } from 'src/components/IdeToolbarNew/IdeToolbarNew' import Svg from 'src/components/Svg/Svg' +import { useRender } from 'src/components/IdeToolbarNew/useRender' +import {makeStlDownloadHandler, PullTitleFromFirstLine} from './helpers' const EditorMenu = () => { + const handleRender = useRender() + const { state, thunkDispatch } = useContext(IdeContext) + const handleStlDownload = makeStlDownloadHandler({ + type: state.objectData?.type, + geometry: state.objectData?.data, + fileName: PullTitleFromFirstLine(state.code || ''), + thunkDispatch, + }) return (
@@ -14,9 +28,10 @@ const EditorMenu = () => {
- + @@ -29,3 +44,37 @@ const EditorMenu = () => { } export default EditorMenu + +function FileDropdown({handleRender, handleStlDownload}) { + return ( + + File + + + {({ active }) => ( + + )} + + + {({ active }) => ( + + )} + + + + ) +} diff --git a/app/web/src/components/EditorMenu/helpers.ts b/app/web/src/components/EditorMenu/helpers.ts new file mode 100644 index 0000000..2ea84e8 --- /dev/null +++ b/app/web/src/components/EditorMenu/helpers.ts @@ -0,0 +1,59 @@ +import { flow, identity } from 'lodash/fp' +import { fileSave } from 'browser-fs-access' +import { MeshBasicMaterial, Mesh, Scene } from 'three' +import { STLExporter } from 'three/examples/jsm/exporters/STLExporter' +import { requestRender } from 'src/helpers/hooks/useIdeState' + +export const PullTitleFromFirstLine = (code: string = '') => { + const firstLine = code.split('\n').filter(identity)[0] || '' + if (!(firstLine.startsWith('//') || firstLine.startsWith('#'))) { + return 'object.stl' + } + return ( + (firstLine.replace(/^(\/\/|#)\s*(.+)/, (_, __, titleWithSpaces) => + titleWithSpaces.replaceAll(/\s/g, '-') + ) || 'object') + '.stl' + ) +} + +export const makeStlDownloadHandler = (({ geometry, fileName, type, thunkDispatch}) => () => { + const makeStlBlobFromGeo = flow( + (geo) => new Mesh(geo, new MeshBasicMaterial()), + (mesh) => new Scene().add(mesh), + (scene) => new STLExporter().parse(scene), + (stl) => + new Blob([stl], { + type: 'text/plain', + }) + ) + const saveFile = (geometry) => { + const blob = makeStlBlobFromGeo(geometry) + fileSave(blob, { + fileName, + extensions: ['.stl'], + }) + } + if (geometry) { + if (type === 'geometry') { + saveFile(geometry) + } else { + thunkDispatch((dispatch, getState) => { + const state = getState() + if (state.ideType === 'openScad') { + thunkDispatch((dispatch, getState) => { + const state = getState() + dispatch({ type: 'setLoading' }) + requestRender({ + state, + dispatch, + code: state.code, + viewerSize: state.viewerSize, + camera: state.camera, + specialCadProcess: 'stl', + }).then((result) => result && saveFile(result.data)) + }) + } + }) + } + } +}) diff --git a/app/web/src/components/IdeHeader/IdeHeader.tsx b/app/web/src/components/IdeHeader/IdeHeader.tsx index b877dab..23110ad 100644 --- a/app/web/src/components/IdeHeader/IdeHeader.tsx +++ b/app/web/src/components/IdeHeader/IdeHeader.tsx @@ -1,23 +1,57 @@ -import Svg from 'src/components/Svg/Svg' +import { Popover } from '@headlessui/react' +import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; +import { copyTextToClipboard } from 'src/helpers/clipboard' +import { encode } from 'src/helpers/compress' -const TopButton = ({children}) => ( - ) -const IdeHeader = () => { +const IdeHeader = ({handleRender}: {handleRender: () => void}) => { return ( -
+
-
- -
- Render - Share + Render + + + + {({open}) => { + const encodedLink = makeEncodedLink('bing bong') + return ( + <> + + Share + + + {open && + + +

Encodes your CodeCad script into a URL so that you can share your work

+ + +
+ +

Any content 2

+
+ + + encoded script + external script + +
+
} + + ) + }} +
Fork
@@ -25,3 +59,10 @@ const IdeHeader = () => { } export default IdeHeader + +const scriptKeyV2 = 'encoded_script_v2' // todo don't leave here + +function makeEncodedLink(code: string): string { + const encodedScript = encode(code) + return `${location.href}#${scriptKeyV2}=${encodedScript}` +} diff --git a/app/web/src/components/IdeSideBar/IdeSideBar.tsx b/app/web/src/components/IdeSideBar/IdeSideBar.tsx index ec0ff3e..da55587 100644 --- a/app/web/src/components/IdeSideBar/IdeSideBar.tsx +++ b/app/web/src/components/IdeSideBar/IdeSideBar.tsx @@ -2,7 +2,10 @@ import Svg from 'src/components/Svg/Svg' const IdeSideBar = () => { return ( -
+
+
+ +
diff --git a/app/web/src/components/IdeToolbarNew/IdeToolbarNew.js b/app/web/src/components/IdeToolbarNew/IdeToolbarNew.js index f3d279c..bb7571a 100644 --- a/app/web/src/components/IdeToolbarNew/IdeToolbarNew.js +++ b/app/web/src/components/IdeToolbarNew/IdeToolbarNew.js @@ -1,16 +1,13 @@ import { createContext, useEffect } from 'react' import IdeContainer from 'src/components/IdeContainer' import { isBrowser } from '@redwoodjs/prerender/browserUtils' -import { useIdeState, makeCodeStoreKey } from 'src/helpers/hooks/useIdeState' -import { copyTextToClipboard } from 'src/helpers/clipboard' -import { requestRender } from 'src/helpers/hooks/useIdeState' -import { encode, decode } from 'src/helpers/compress' -import { flow, identity } from 'lodash/fp' -import { fileSave } from 'browser-fs-access' -import { MeshBasicMaterial, Mesh, Scene } from 'three' -import { STLExporter } from 'three/examples/jsm/exporters/STLExporter' +import { useIdeState } from 'src/helpers/hooks/useIdeState' +import { handleRenderVerbose } from './useRender' +import { decode } from 'src/helpers/compress' +import { flow } from 'lodash/fp' import OutBound from 'src/components/OutBound' import IdeSideBar from 'src/components/IdeSideBar' +import IdeHeader from 'src/components/IdeHeader' export const githubSafe = (url) => url.includes('github.com') @@ -65,83 +62,8 @@ const IdeToolbarNew = ({ cadPackage }) => { window.location.hash = '' }, [cadPackage]) function handleRender() { - thunkDispatch((dispatch, getState) => { - const state = getState() - dispatch({ type: 'setLoading' }) - requestRender({ - state, - dispatch, - code: state.code, - viewerSize: state.viewerSize, - camera: state.camera, - }) - }) - localStorage.setItem(makeCodeStoreKey(state.ideType), state.code) + return handleRenderVerbose({thunkDispatch, state}) } - function handleMakeLink() { - if (isBrowser) { - const encodedScript = encode(state.code) - window.location.hash = `${scriptKeyV2}=${encodedScript}` - copyTextToClipboard(window.location.href) - } - } - const PullTitleFromFirstLine = (code = '') => { - const firstLine = code.split('\n').filter(identity)[0] || '' - if (!(firstLine.startsWith('//') || firstLine.startsWith('#'))) { - return 'object.stl' - } - return ( - (firstLine.replace(/^(\/\/|#)\s*(.+)/, (_, __, titleWithSpaces) => - titleWithSpaces.replaceAll(/\s/g, '-') - ) || 'object') + '.stl' - ) - } - - const handleStlDownload = (({ geometry, fileName, type }) => () => { - const makeStlBlobFromGeo = flow( - (geo) => new Mesh(geo, new MeshBasicMaterial()), - (mesh) => new Scene().add(mesh), - (scene) => new STLExporter().parse(scene), - (stl) => - new Blob([stl], { - type: 'text/plain', - }) - ) - const saveFile = (geometry) => { - const blob = makeStlBlobFromGeo(geometry) - fileSave(blob, { - fileName, - extensions: ['.stl'], - }) - } - if (geometry) { - if (type === 'geometry') { - saveFile(geometry) - } else { - thunkDispatch((dispatch, getState) => { - const state = getState() - if (state.ideType === 'openScad') { - thunkDispatch((dispatch, getState) => { - const state = getState() - dispatch({ type: 'setLoading' }) - requestRender({ - state, - dispatch, - code: state.code, - viewerSize: state.viewerSize, - camera: state.camera, - specialCadProcess: 'stl', - }).then((result) => result && saveFile(result.data)) - }) - } - }) - } - } - })({ - type: state.objectData?.type, - geometry: state.objectData?.data, - fileName: PullTitleFromFirstLine(state.code), - }) return ( @@ -150,6 +72,9 @@ const IdeToolbarNew = ({ cadPackage }) => {
+
We're still working on this. Since you're here, have a look what{' '} @@ -162,26 +87,6 @@ const IdeToolbarNew = ({ cadPackage }) => { .
-
diff --git a/app/web/src/components/IdeToolbarNew/useRender.ts b/app/web/src/components/IdeToolbarNew/useRender.ts new file mode 100644 index 0000000..b754987 --- /dev/null +++ b/app/web/src/components/IdeToolbarNew/useRender.ts @@ -0,0 +1,25 @@ +import { makeCodeStoreKey } from 'src/helpers/hooks/useIdeState' +import { requestRender } from 'src/helpers/hooks/useIdeState' +import { useContext } from 'react' +import { IdeContext } from 'src/components/IdeToolbarNew/IdeToolbarNew' + +export const handleRenderVerbose = ({thunkDispatch, state}) => { + thunkDispatch((dispatch, getState) => { + const state = getState() + dispatch({ type: 'setLoading' }) + requestRender({ + state, + dispatch, + code: state.code, + viewerSize: state.viewerSize, + camera: state.camera, + }) + }) + localStorage.setItem(makeCodeStoreKey(state.ideType), state.code) +} + +export const useRender = () => { + const { state, thunkDispatch } = useContext(IdeContext) + return () => handleRenderVerbose({thunkDispatch, state}) +} + diff --git a/app/web/src/components/Svg/Svg.tsx b/app/web/src/components/Svg/Svg.tsx index 523e9a4..8a8de7b 100644 --- a/app/web/src/components/Svg/Svg.tsx +++ b/app/web/src/components/Svg/Svg.tsx @@ -14,6 +14,7 @@ type SvgNames = 'arrow-down' | 'gear' | 'lightbulb' | 'logout' | + 'mac-cmd-key' | 'pencil' | 'plus' | 'plus-circle' | @@ -323,6 +324,18 @@ const Svg = ({ name, className: className2 = '', strokeWidth = 2 }: { /> ), + 'mac-cmd-key': ( + + < + path + fill="currentColor" + d="M57.254,121.064c-17.735,0-31.45,4.723-40.765,14.035c-19.39,19.391-19.39,50.943,0,70.334 C25.884,214.828,38.375,220,51.66,220c0,0,0,0,0,0c13.284,0,25.774-5.172,35.168-14.566c11.069-11.07,14.037-27.061,14.041-41.016 c0.001-3.531,0.017-10.09,0.017-10.09h18.227c0,0,0.016,6.559,0.018,10.09c0.004,13.955,2.971,29.945,14.041,41.016 C142.565,214.828,155.056,220,168.34,220c13.285,0,25.775-5.172,35.17-14.566c19.391-19.391,19.391-50.943,0-70.334 c-9.314-9.312-23.029-14.035-40.764-14.035c-3.602,0-10.346,0.02-10.346,0.02c0-0.932,0-22.178,0-22.178s6.744,0.029,10.346,0.029 c17.734,0,31.449-4.721,40.762-14.033c19.391-19.392,19.391-50.943,0.002-70.334C194.113,5.174,181.624,0,168.34,0 c-13.285,0-25.773,5.174-35.168,14.566c-10.67,10.672-13.812,25.914-14.029,39.498l-0.193,11.609H101.05l-0.192-11.609 c-0.217-13.584-3.359-28.826-14.03-39.498C77.434,5.174,64.944,0,51.66,0C38.376,0,25.886,5.174,16.49,14.568 C-2.899,33.959-2.899,65.51,16.491,84.902c9.314,9.313,23.028,14.033,40.762,14.033c3.601,0,10.346-0.029,10.346-0.029 s0,21.246,0,22.178C67.6,121.084,60.855,121.064,57.254,121.064z M154.328,35.587c3.727-3.726,8.683-5.779,13.954-5.779 s10.229,2.053,13.957,5.781c7.692,7.693,7.692,20.213-0.002,27.906c-3.384,3.385-10.327,5.248-19.549,5.248 c-4.6,0-14.107,0-14.107,0v-8.688C148.581,60.056,147.566,41.495,154.328,35.587z M148.581,159.945v-8.688c0,0,9.508,0,14.107,0 c9.222,0,16.165,1.863,19.549,5.248c7.694,7.693,7.694,20.213,0.002,27.906c-3.729,3.729-8.686,5.781-13.957,5.781 s-10.228-2.053-13.954-5.779C147.566,178.506,148.581,159.945,148.581,159.945z M93.75,93.75h32.5v32.5h-32.5V93.75z M57.312,68.743 c-9.222,0-16.165-1.863-19.549-5.248c-7.694-7.693-7.694-20.213-0.002-27.906c3.729-3.729,8.686-5.781,13.957-5.781 s10.228,2.053,13.954,5.779c6.762,5.908,5.747,24.469,5.747,24.469v8.688C71.419,68.743,61.911,68.743,57.312,68.743z M71.419,151.258v8.688c0,0,1.015,18.561-5.747,24.469c-3.727,3.727-8.683,5.779-13.954,5.779s-10.229-2.053-13.957-5.781 c-7.692-7.693-7.692-20.213,0.002-27.906c3.384-3.385,10.327-5.248,19.549-5.248C61.911,151.258,71.419,151.258,71.419,151.258z" + /> + + ), pencil: ( toast.success(() => ( +
+

link added to clipboard.

+
+)) + +const makeClipboardCopier = (success: Function) => (text: string) => { if (!navigator.clipboard) { fallbackCopyTextToClipboard(text) + success(text) return } navigator.clipboard.writeText(text).then( function () { console.log('Async: Copying to clipboard was successful!') + success(text) }, function (err) { console.error('Async: Could not copy text: ', err) } ) } + +export const copyTextToClipboard = makeClipboardCopier(clipboardSuccessToast) diff --git a/app/web/src/pages/DevIdePage/DevIdePage.js b/app/web/src/pages/DevIdePage/DevIdePage.js index 8a29492..e6c16a9 100644 --- a/app/web/src/pages/DevIdePage/DevIdePage.js +++ b/app/web/src/pages/DevIdePage/DevIdePage.js @@ -1,6 +1,6 @@ import Seo from 'src/components/Seo/Seo' import IdeToolbar from 'src/components/IdeToolbarNew' -import IdeHeader from 'src/components/IdeHeader' +import { Toaster } from '@redwoodjs/web/toast' const DevIdePage = ({ cadPackage }) => { return ( @@ -10,10 +10,8 @@ const DevIdePage = ({ cadPackage }) => { description="new ide in development" lang="en-US" /> - -
- -
+ +
) } diff --git a/app/yarn.lock b/app/yarn.lock index 46bdf77..bfeda86 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -6827,7 +6827,7 @@ cloudinary@^1.23.0: lodash "^4.17.11" q "^1.5.1" -clsx@^1.0.4, clsx@^1.1.1: +clsx@^1.0.4, clsx@^1.1.0, clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== @@ -14898,7 +14898,7 @@ prompts@2.4.1, prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -15610,6 +15610,14 @@ react-syntax-highlighter@^13.5.0, react-syntax-highlighter@^13.5.3: prismjs "^1.21.0" refractor "^3.1.0" +react-tabs@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-3.2.2.tgz#07bdc3cdb17bdffedd02627f32a93cd4b3d6e4d0" + integrity sha512-/o52eGKxFHRa+ssuTEgSM8qORnV4+k7ibW+aNQzKe+5gifeVz8nLxCrsI9xdRhfb0wCLdgIambIpb1qCxaMN+A== + dependencies: + clsx "^1.1.0" + prop-types "^15.5.0" + react-textarea-autosize@^8.1.1: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"