IDE redesign, initial implementation #362
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<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
|
||||
|
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.
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}
|
||||
>
|
||||
|
|
||||
Save & 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>
|
||||
)
|
||||
}
|
||||
|
||||
59
app/web/src/components/EditorMenu/helpers.ts
Normal file
59
app/web/src/components/EditorMenu/helpers.ts
Normal 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))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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 (
|
||||
<>
|
||||
|
|
||||
<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}`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import Svg from 'src/components/Svg/Svg'
|
||||
|
||||
const IdeSideBar = () => {
|
||||
return (
|
||||
|
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
25
app/web/src/components/IdeToolbarNew/useRender.ts
Normal file
25
app/web/src/components/IdeToolbarNew/useRender.ts
Normal 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})
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user



Did you do all these snippet images manually? They're very helpful!
:)
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.