IDE redesign, initial implementation #362

Merged
Irev-Dev merged 17 commits from kurt/update-ide-panel-toolbar-360 into main 2021-06-15 10:05:03 +02:00
11 changed files with 242 additions and 127 deletions
Showing only changes of commit 3c18a24cb6 - Show all commits

View File

@@ -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",

View File

@@ -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'
Irev-Dev commented 2021-06-14 02:21:24 +02:00 (Migrated from github.com)
Review

image

![image](https://user-images.githubusercontent.com/29681384/121826268-436c6680-ccfa-11eb-9b06-ba4d61270ab5.png)
franknoirot commented 2021-06-14 15:18:09 +02:00 (Migrated from github.com)
Review

Did you do all these snippet images manually? They're very helpful!

Did you do all these snippet images manually? They're very helpful!
Irev-Dev commented 2021-06-15 10:02:26 +02:00 (Migrated from github.com)
Review

:)
Yeah I did.
CContext is super important when trying to look at other's code. You could have figured this out yourself but 20x quicker for me to add a few screen shots.

:) Yeah I did. CContext is super important when trying to look at other's code. You could have figured this out yourself but 20x quicker for me to add a few screen shots.
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 (
<div className="bg-gray-500 flex items-center h-9 w-full cursor-grab">
<div className=" text-gray-500 bg-gray-300 cursor-grab px-2 h-full flex items-center">
@@ -14,9 +28,10 @@ const EditorMenu = () => {
</button>
<div className="w-px h-full bg-gray-300"/>
<div className="flex gap-6 px-6">
<button className="text-gray-100">
File
</button>
<FileDropdown
handleRender={handleRender}
handleStlDownload={handleStlDownload}
/>
<button className="text-gray-100">
Edit
</button>
@@ -29,3 +44,37 @@ const EditorMenu = () => {
}
export default EditorMenu
franknoirot commented 2021-06-14 15:23:05 +02:00 (Migrated from github.com)
Review

With the additional background I added in the latest Figma designs we may want to start aliasing the CAD packages to class names in the next round so several elements can inherit styles.

With the additional background I added in the latest Figma designs we may want to start aliasing the CAD packages to class names in the next round so several elements can inherit styles.
Irev-Dev commented 2021-06-15 10:02:44 +02:00 (Migrated from github.com)
Review

Oh right, yeah that's a good idea.

Oh right, yeah that's a good idea.
function FileDropdown({handleRender, handleStlDownload}) {
return (
<Menu>
<Menu.Button className="text-gray-100">File</Menu.Button>
<Menu.Items className="absolute flex flex-col mt-10 bg-gray-500 rounded shadow-md text-gray-100">
<Menu.Item>
{({ active }) => (
<button
className={`${active && 'bg-gray-600'} px-2 py-1`}
onClick={handleRender}
>
Irev-Dev commented 2021-06-14 02:21:45 +02:00 (Migrated from github.com)
Review

image

![image](https://user-images.githubusercontent.com/29681384/121826279-4ff0bf00-ccfa-11eb-8315-bd8a4cfc0ef7.png)
Save &amp; Render <span className="text-gray-400 pl-4">{
/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ?
<><Svg name="mac-cmd-key" className="h-3 w-3 inline-block text-left" />S</> :
'Ctrl S'
}</span>
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
className={`${active && 'bg-gray-600'} px-2 py-1 text-left`}
onClick={handleStlDownload}
>
Download STL
</button>
)}
</Menu.Item>
</Menu.Items>
</Menu>
)
}

View File

@@ -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))
})
}
})
}
}
})

View File

@@ -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}) => (
<button className="flex bg-gray-500 h-10 justify-center items-center px-4 rounded">
const TopButton = ({children, onClick}: {onClick?: () => void, children: React.ReactNode}) => (
<button onClick={onClick} className="flex bg-gray-500 h-10 justify-center items-center px-4 rounded">
<div className="rounded-full bg-gray-200 h-6 w-6 mr-4"/>
{children}
</button>
)
const IdeHeader = () => {
const IdeHeader = ({handleRender}: {handleRender: () => void}) => {
return (
<div className="h-16 bg-gray-900 flex justify-between items-center">
<div className="h-16 w-full bg-gray-900 flex justify-between items-center">
<div className="bg-gray-700 pr-48 h-full">
<div className="w-16 h-full flex items-center justify-center bg-gray-900">
<Svg className="w-12" name="favicon" />
</div>
</div>
<div className="text-gray-200 flex gap-4 mr-4">
<TopButton>Render</TopButton>
<TopButton>Share</TopButton>
<TopButton onClick={handleRender}>Render</TopButton>
<Popover className="relative outline-none w-full h-full">
{({open}) => {
const encodedLink = makeEncodedLink('bing bong')
return (
<>
Irev-Dev commented 2021-06-14 02:34:39 +02:00 (Migrated from github.com)
Review

image

![image](https://user-images.githubusercontent.com/29681384/121826642-1d47c600-ccfc-11eb-89d1-a0ac63141405.png)
<Popover.Button className="h-full w-full outline-none">
<TopButton>Share</TopButton>
</Popover.Button>
{open && <Popover.Panel className="absolute z-10 mt-4 right-0">
<Tabs
className="bg-gray-300 rounded-md shadow-md overflow-hidden text-gray-700"
selectedTabClassName="bg-gray-200"
>
<TabPanel className="p-4">
<p className="text-sm pb-4 border-b border-gray-700">Encodes your CodeCad script into a URL so that you can share your work</p>
<input value={encodedLink.replace(/^.+:\/\//g, '')} readOnly className="p-1 mt-4 text-xs rounded-t border border-gray-700 w-full" />
<button className="w-full bg-gray-700 py-1 rounded-b text-gray-300" onClick={() => copyTextToClipboard(encodedLink)} >Copy URL</button>
</TabPanel>
<TabPanel>
<h2 className="h-32">Any content 2</h2>
</TabPanel>
<TabList className="flex whitespace-nowrap text-gray-700 border-t border-gray-700">
<Tab className="p-3 px-5">encoded script</Tab>
<Tab className="p-3 px-5">external script</Tab>
</TabList>
</Tabs>
</Popover.Panel>}
</>
)
}}
</Popover>
<TopButton>Fork</TopButton>
</div>
</div>
@@ -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}`
}

View File

@@ -2,7 +2,10 @@ import Svg from 'src/components/Svg/Svg'
const IdeSideBar = () => {
return (
Irev-Dev commented 2021-06-14 02:35:11 +02:00 (Migrated from github.com)
Review

image

![image](https://user-images.githubusercontent.com/29681384/121826656-2fc1ff80-ccfc-11eb-998b-55c254eeaae6.png)
<div className="h-full flex flex-col justify-end">
<div className="h-full flex flex-col justify-between">
<div className="w-16 h-16 flex items-center justify-center bg-gray-900">
<Svg className="w-12" name="favicon" />
</div>
<button className=" text-gray-300 p-2 pb-4 flex justify-center" aria-label="IDE settings">
<Svg name="big-gear" />
</button>

View File

@@ -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 (
<IdeContext.Provider value={{ state, thunkDispatch }}>
@@ -150,6 +72,9 @@ const IdeToolbarNew = ({ cadPackage }) => {
<IdeSideBar />
</div>
<div className="h-full flex flex-grow flex-col">
<nav className="flex">
<IdeHeader handleRender={handleRender} />
</nav>
<div className="py-2 bg-pink-200">
<div className="mx-auto max-w-3xl">
We're still working on this. Since you're here, have a look what{' '}
@@ -162,26 +87,6 @@ const IdeToolbarNew = ({ cadPackage }) => {
.
</div>
</div>
<nav className="flex">
<button
onClick={handleRender}
className="border-2 px-2 text-gray-700 text-sm m-1"
>
Render
</button>
<button
onClick={handleMakeLink}
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
>
Copy link
</button>
<button
onClick={handleStlDownload}
className="border-2 text-gray-700 px-2 text-sm m-1 ml-2"
>
Download STL
</button>
</nav>
<IdeContainer />
</div>
</div>

View File

@@ -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})
}

View File

@@ -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 }: {
/>
</svg>
),
'mac-cmd-key': (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 220 220"
>
<
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"
/>
</svg>
),
pencil: (
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -1,4 +1,6 @@
function fallbackCopyTextToClipboard(text) {
import { toast } from '@redwoodjs/web/toast'
function fallbackCopyTextToClipboard(text: string) {
var textArea = document.createElement('textarea')
textArea.value = text
@@ -21,17 +23,28 @@ function fallbackCopyTextToClipboard(text) {
document.body.removeChild(textArea)
}
export function copyTextToClipboard(text) {
const clipboardSuccessToast = (text: string) => toast.success(() => (
<div className="overflow-hidden">
<p>link added to clipboard.</p>
</div>
))
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)

View File

@@ -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"
/>
<IdeHeader />
<div className="flex-auto">
<IdeToolbar cadPackage={cadPackage} />
</div>
<Toaster timeout={9000} />
<IdeToolbar cadPackage={cadPackage} />
</div>
)
}

View File

@@ -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"