@@ -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
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user