@@ -42,6 +42,7 @@
|
|||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-image-crop": "^8.6.6",
|
"react-image-crop": "^8.6.6",
|
||||||
"react-mosaic-component": "^4.1.1",
|
"react-mosaic-component": "^4.1.1",
|
||||||
|
"react-tabs": "^3.2.2",
|
||||||
"react-three-fiber": "^5.3.19",
|
"react-three-fiber": "^5.3.19",
|
||||||
"rich-markdown-editor": "^11.0.2",
|
"rich-markdown-editor": "^11.0.2",
|
||||||
"styled-components": "^5.2.0",
|
"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 Svg from 'src/components/Svg/Svg'
|
||||||
|
import { useRender } from 'src/components/IdeToolbarNew/useRender'
|
||||||
|
import {makeStlDownloadHandler, PullTitleFromFirstLine} from './helpers'
|
||||||
|
|
||||||
const EditorMenu = () => {
|
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 (
|
return (
|
||||||
<div className="bg-gray-500 flex items-center h-9 w-full cursor-grab">
|
<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">
|
<div className=" text-gray-500 bg-gray-300 cursor-grab px-2 h-full flex items-center">
|
||||||
@@ -14,9 +28,10 @@ const EditorMenu = () => {
|
|||||||
</button>
|
</button>
|
||||||
<div className="w-px h-full bg-gray-300"/>
|
<div className="w-px h-full bg-gray-300"/>
|
||||||
<div className="flex gap-6 px-6">
|
<div className="flex gap-6 px-6">
|
||||||
<button className="text-gray-100">
|
<FileDropdown
|
||||||
File
|
handleRender={handleRender}
|
||||||
</button>
|
handleStlDownload={handleStlDownload}
|
||||||
|
/>
|
||||||
<button className="text-gray-100">
|
<button className="text-gray-100">
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -29,3 +44,37 @@ const EditorMenu = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default EditorMenu
|
export default EditorMenu
|
||||||
|
|
||||||
|
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}) => (
|
const TopButton = ({children, onClick}: {onClick?: () => void, children: React.ReactNode}) => (
|
||||||
<button className="flex bg-gray-500 h-10 justify-center items-center px-4 rounded">
|
<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"/>
|
<div className="rounded-full bg-gray-200 h-6 w-6 mr-4"/>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
const IdeHeader = () => {
|
const IdeHeader = ({handleRender}: {handleRender: () => void}) => {
|
||||||
return (
|
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="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>
|
||||||
<div className="text-gray-200 flex gap-4 mr-4">
|
<div className="text-gray-200 flex gap-4 mr-4">
|
||||||
<TopButton>Render</TopButton>
|
<TopButton onClick={handleRender}>Render</TopButton>
|
||||||
<TopButton>Share</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>
|
<TopButton>Fork</TopButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,3 +59,10 @@ const IdeHeader = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default 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 = () => {
|
const IdeSideBar = () => {
|
||||||
return (
|
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">
|
<button className=" text-gray-300 p-2 pb-4 flex justify-center" aria-label="IDE settings">
|
||||||
<Svg name="big-gear" />
|
<Svg name="big-gear" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { createContext, useEffect } from 'react'
|
import { createContext, useEffect } from 'react'
|
||||||
import IdeContainer from 'src/components/IdeContainer'
|
import IdeContainer from 'src/components/IdeContainer'
|
||||||
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
import { isBrowser } from '@redwoodjs/prerender/browserUtils'
|
||||||
import { useIdeState, makeCodeStoreKey } from 'src/helpers/hooks/useIdeState'
|
import { useIdeState } from 'src/helpers/hooks/useIdeState'
|
||||||
import { copyTextToClipboard } from 'src/helpers/clipboard'
|
import { handleRenderVerbose } from './useRender'
|
||||||
import { requestRender } from 'src/helpers/hooks/useIdeState'
|
import { decode } from 'src/helpers/compress'
|
||||||
import { encode, decode } from 'src/helpers/compress'
|
import { flow } from 'lodash/fp'
|
||||||
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 OutBound from 'src/components/OutBound'
|
import OutBound from 'src/components/OutBound'
|
||||||
import IdeSideBar from 'src/components/IdeSideBar'
|
import IdeSideBar from 'src/components/IdeSideBar'
|
||||||
|
import IdeHeader from 'src/components/IdeHeader'
|
||||||
|
|
||||||
export const githubSafe = (url) =>
|
export const githubSafe = (url) =>
|
||||||
url.includes('github.com')
|
url.includes('github.com')
|
||||||
@@ -65,83 +62,8 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
|||||||
window.location.hash = ''
|
window.location.hash = ''
|
||||||
}, [cadPackage])
|
}, [cadPackage])
|
||||||
function handleRender() {
|
function handleRender() {
|
||||||
thunkDispatch((dispatch, getState) => {
|
return handleRenderVerbose({thunkDispatch, state})
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
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 (
|
return (
|
||||||
<IdeContext.Provider value={{ state, thunkDispatch }}>
|
<IdeContext.Provider value={{ state, thunkDispatch }}>
|
||||||
@@ -150,6 +72,9 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
|||||||
<IdeSideBar />
|
<IdeSideBar />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full flex flex-grow flex-col">
|
<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="py-2 bg-pink-200">
|
||||||
<div className="mx-auto max-w-3xl">
|
<div className="mx-auto max-w-3xl">
|
||||||
We're still working on this. Since you're here, have a look what{' '}
|
We're still working on this. Since you're here, have a look what{' '}
|
||||||
@@ -162,26 +87,6 @@ const IdeToolbarNew = ({ cadPackage }) => {
|
|||||||
.
|
.
|
||||||
</div>
|
</div>
|
||||||
</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 />
|
<IdeContainer />
|
||||||
</div>
|
</div>
|
||||||
</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' |
|
'gear' |
|
||||||
'lightbulb' |
|
'lightbulb' |
|
||||||
'logout' |
|
'logout' |
|
||||||
|
'mac-cmd-key' |
|
||||||
'pencil' |
|
'pencil' |
|
||||||
'plus' |
|
'plus' |
|
||||||
'plus-circle' |
|
'plus-circle' |
|
||||||
@@ -323,6 +324,18 @@ const Svg = ({ name, className: className2 = '', strokeWidth = 2 }: {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</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: (
|
pencil: (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/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')
|
var textArea = document.createElement('textarea')
|
||||||
textArea.value = text
|
textArea.value = text
|
||||||
|
|
||||||
@@ -21,17 +23,28 @@ function fallbackCopyTextToClipboard(text) {
|
|||||||
|
|
||||||
document.body.removeChild(textArea)
|
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) {
|
if (!navigator.clipboard) {
|
||||||
fallbackCopyTextToClipboard(text)
|
fallbackCopyTextToClipboard(text)
|
||||||
|
success(text)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
navigator.clipboard.writeText(text).then(
|
navigator.clipboard.writeText(text).then(
|
||||||
function () {
|
function () {
|
||||||
console.log('Async: Copying to clipboard was successful!')
|
console.log('Async: Copying to clipboard was successful!')
|
||||||
|
success(text)
|
||||||
},
|
},
|
||||||
function (err) {
|
function (err) {
|
||||||
console.error('Async: Could not copy text: ', 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 Seo from 'src/components/Seo/Seo'
|
||||||
import IdeToolbar from 'src/components/IdeToolbarNew'
|
import IdeToolbar from 'src/components/IdeToolbarNew'
|
||||||
import IdeHeader from 'src/components/IdeHeader'
|
import { Toaster } from '@redwoodjs/web/toast'
|
||||||
|
|
||||||
const DevIdePage = ({ cadPackage }) => {
|
const DevIdePage = ({ cadPackage }) => {
|
||||||
return (
|
return (
|
||||||
@@ -10,10 +10,8 @@ const DevIdePage = ({ cadPackage }) => {
|
|||||||
description="new ide in development"
|
description="new ide in development"
|
||||||
lang="en-US"
|
lang="en-US"
|
||||||
/>
|
/>
|
||||||
<IdeHeader />
|
<Toaster timeout={9000} />
|
||||||
<div className="flex-auto">
|
<IdeToolbar cadPackage={cadPackage} />
|
||||||
<IdeToolbar cadPackage={cadPackage} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6827,7 +6827,7 @@ cloudinary@^1.23.0:
|
|||||||
lodash "^4.17.11"
|
lodash "^4.17.11"
|
||||||
q "^1.5.1"
|
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"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||||
@@ -14898,7 +14898,7 @@ prompts@2.4.1, prompts@^2.0.1:
|
|||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.5"
|
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"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
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"
|
prismjs "^1.21.0"
|
||||||
refractor "^3.1.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:
|
react-textarea-autosize@^8.1.1:
|
||||||
version "8.3.3"
|
version "8.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
|
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
|
||||||
|
|||||||
Reference in New Issue
Block a user