three.cad/src/react/navBar.jsx

232 lines
7.4 KiB
React
Raw Normal View History

2021-04-07 12:21:09 +08:00
2021-05-03 07:44:54 +08:00
import React, { useEffect, useReducer, useState } from 'react';
2021-04-07 12:21:09 +08:00
import { useDispatch, useSelector } from 'react-redux'
2021-04-19 15:30:29 +08:00
import { FaEdit, FaLinkedin, FaGithub } from 'react-icons/fa'
2021-04-29 18:42:52 +08:00
import { MdSave, MdFolder, MdInsertDriveFile, MdHelpOutline } from 'react-icons/md'
2021-04-07 12:21:09 +08:00
import * as Icon from "./icons";
2021-05-03 07:44:54 +08:00
2021-04-17 11:54:01 +08:00
import { Dialog } from './dialog'
2021-05-03 07:44:54 +08:00
import { Modal } from './modal'
2021-04-26 14:04:24 +08:00
import { STLExport, saveFile, openFile } from './fileHelpers'
2021-05-04 06:21:21 +08:00
import { QuickStart } from './quickStart';
2021-05-04 12:02:46 +08:00
import { Help } from './help'
const visitedFlagStorage = sessionStorage
2021-04-26 14:04:24 +08:00
const buttonIdx = {
'line': 1,
'arc': 2,
'dimension': 3,
'coincident': 4,
'vertical': 5,
'horizontal': 6,
'tangent': 7,
}
2021-04-17 11:54:01 +08:00
export const NavBar = () => {
2021-04-07 12:21:09 +08:00
const dispatch = useDispatch()
2021-04-19 11:53:02 +08:00
const treeEntries = useSelector(state => state.treeEntries)
2021-04-26 14:04:24 +08:00
const sketchActive = useSelector(state => state.ui.sketchActive)
2021-04-19 03:14:01 +08:00
const fileHandle = useSelector(state => state.ui.fileHandle)
2021-04-19 11:53:02 +08:00
const modified = useSelector(state => state.ui.modified)
2021-04-26 14:04:24 +08:00
const fileName = useSelector(state => state.ui.fileName)
const mode = useSelector(state => state.ui.mode)
2021-04-30 11:29:02 +08:00
const help = useSelector(state => state.ui.help)
2021-04-13 09:37:16 +08:00
const boolOp = (code) => {
2021-04-24 04:13:49 +08:00
if (sce.selected.length != 2 || !sce.selected.every(e => e.userData.type == 'mesh')) {
2021-04-19 15:30:29 +08:00
alert('please first select two bodies for boolean operation')
return
}
2021-04-24 04:13:49 +08:00
const [m1, m2] = sce.selected
2021-04-19 11:53:02 +08:00
2021-04-24 04:13:49 +08:00
const mesh = sce.boolOp(m1, m2, code)
2021-04-19 11:53:02 +08:00
2021-04-24 04:13:49 +08:00
sce.obj3d.add(mesh)
2021-04-19 11:53:02 +08:00
dispatch({
type: 'set-entry-visibility', obj: {
[m1.name]: false,
[m2.name]: false,
[mesh.name]: true,
}
})
dispatch({
type: 'rx-boolean', mesh, deps: [m1.name, m2.name]
})
2021-04-24 04:13:49 +08:00
sce.render()
2021-04-13 09:37:16 +08:00
forceUpdate()
}
2021-04-15 06:46:30 +08:00
2021-04-21 18:54:57 +08:00
2021-04-19 15:30:29 +08:00
const addSketch = () => {
2021-04-24 04:13:49 +08:00
const sketch = sce.addSketch()
2021-04-19 15:30:29 +08:00
if (!sketch) {
alert('please select a plane or 3 points to set the sketch plane')
2021-04-19 15:30:29 +08:00
return
}
2021-04-19 11:53:02 +08:00
dispatch({ type: 'rx-sketch', obj: sketch })
sketch.activate()
2021-04-24 04:13:49 +08:00
sce.render()
2021-04-19 11:53:02 +08:00
2021-04-26 14:04:24 +08:00
dispatch({ type: 'set-dialog', action: 'sketch', target: sketch.obj3d.name })
2021-04-17 11:54:01 +08:00
2021-04-15 06:46:30 +08:00
forceUpdate()
2021-04-13 09:37:16 +08:00
}
2021-04-19 11:53:02 +08:00
const confirmDiscard = () => !modified ? true : confirm('Discard changes? All changes will be lost.')
2021-04-19 03:14:01 +08:00
2021-04-19 11:53:02 +08:00
useEffect(() => {
const onBeforeUnload = (e) => {
if (modified ||
2021-04-24 04:13:49 +08:00
(sce.activeSketch &&
2021-04-19 11:53:02 +08:00
(
2021-04-24 04:13:49 +08:00
sce.activeSketch.hasChanged
|| sce.activeSketch.idOnActivate != id
|| sce.activeSketch.c_idOnActivate != sce.activeSketch.c_id
2021-04-19 11:53:02 +08:00
)
)
) {
e.preventDefault();
e.returnValue = `There are unsaved changes. Are you sure you want to leave?`;
}
}
window.addEventListener('beforeunload', onBeforeUnload)
return () => window.removeEventListener('beforeunload', onBeforeUnload)
}, [modified])
2021-04-17 18:25:43 +08:00
useEffect(() => { // hacky way to handle mounting and unmounting mouse listeners for feature mode
2021-04-19 11:53:02 +08:00
if (!sketchActive) {
2021-04-24 04:13:49 +08:00
sce.canvas.addEventListener('pointermove', sce.onHover)
sce.canvas.addEventListener('pointerdown', sce.onPick)
2021-04-17 18:25:43 +08:00
return () => {
2021-04-24 04:13:49 +08:00
sce.canvas.removeEventListener('pointermove', sce.onHover)
sce.canvas.removeEventListener('pointerdown', sce.onPick)
2021-04-17 18:25:43 +08:00
}
}
2021-04-19 11:53:02 +08:00
}, [sketchActive])
2021-04-07 12:21:09 +08:00
2021-04-17 11:54:01 +08:00
const sketchModeButtons = [
2021-04-26 16:22:13 +08:00
[Icon.Extrude, () => dispatch({ type: 'set-dialog', action: 'extrude', target: sce.activeSketch.obj3d.name }), 'Extrude'],
2021-04-29 18:42:52 +08:00
[Icon.Line, () => sce.activeSketch.command('line'), 'Line (l)'], //1
[Icon.Arc, () => sce.activeSketch.command('arc'), 'Arc (a)'],
[Icon.Dimension, () => sce.activeSketch.command('dimension'), 'Dimension (d)'],
[Icon.Coincident, () => sce.activeSketch.command('coincident'), 'Coincident (c)'],
[Icon.Vertical, () => sce.activeSketch.command('vertical'), 'Vertical (v)'],
[Icon.Horizontal, () => sce.activeSketch.command('horizontal'), 'Horizontal (h)'],
[Icon.Tangent, () => sce.activeSketch.command('tangent'), 'Tangent (t)'], //7
2021-04-26 16:22:13 +08:00
[MdSave, async () => saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName), 'Save'],
2021-04-14 04:39:33 +08:00
]
2021-04-17 11:54:01 +08:00
const partModeButtons = [
2021-04-19 15:30:29 +08:00
[FaEdit, addSketch, 'Sketch'],
2021-04-17 11:54:01 +08:00
[Icon.Extrude, () => {
try {
2021-04-26 14:04:24 +08:00
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.selected[0].name })
} catch (err) {
console.error(err)
2021-04-19 15:30:29 +08:00
alert('please select a sketch from the left pane extrude')
}
2021-04-19 11:53:02 +08:00
}, 'Extrude'],
2021-04-17 11:54:01 +08:00
2021-04-14 04:39:33 +08:00
[Icon.Union, () => boolOp('u'), 'Union'],
2021-04-26 16:22:13 +08:00
[Icon.Subtract, () => boolOp('s'), 'Subtract'],
2021-04-14 04:39:33 +08:00
[Icon.Intersect, () => boolOp('i'), 'Intersect'],
2021-04-19 06:05:33 +08:00
[MdInsertDriveFile, () => {
2021-04-19 11:53:02 +08:00
if (!confirmDiscard()) return
2021-04-24 04:13:49 +08:00
sce.newPart()
2021-04-19 06:05:33 +08:00
dispatch({ type: 'new-part' })
2021-04-24 04:13:49 +08:00
sce.render()
2021-04-19 11:53:02 +08:00
}, 'New'],
2021-04-19 06:05:33 +08:00
[MdSave,
() => {
2021-04-26 14:04:24 +08:00
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName)
2021-04-19 06:05:33 +08:00
}
2021-04-19 11:53:02 +08:00
, 'Save'],
2021-04-19 06:05:33 +08:00
[MdFolder, () => {
2021-04-19 11:53:02 +08:00
if (!confirmDiscard()) return
2021-04-19 06:05:33 +08:00
openFile(dispatch).then(
2021-04-24 04:13:49 +08:00
sce.render
2021-04-19 06:05:33 +08:00
)
}, 'Open'],
[Icon.Stl, () => {
2021-04-24 04:13:49 +08:00
if (sce.selected[0] && sce.selected[0].userData.type == 'mesh') {
2021-04-19 15:30:29 +08:00
STLExport(fileHandle ? fileHandle.name.replace(/\.[^/.]+$/, "") : 'untitled')
} else {
alert('please first select one body to export')
}
}, 'Export to STL'],
2021-04-07 12:21:09 +08:00
]
const [_, forceUpdate] = useReducer(x => x + 1, 0);
2021-05-04 12:02:46 +08:00
const [splash, setSplash] = useState(!visitedFlagStorage.getItem('visited'))
2021-05-03 07:44:54 +08:00
const [modal, setModal] = useState(false)
2021-05-04 12:02:46 +08:00
return <div className='topNav flex justify-center bg-gray-800 text-gray-200 '>
2021-05-03 07:44:54 +08:00
<div className='w-auto h-full flex-1 flex justify-end lg:justify-between'>
<div className='w-100 h-full font-mono text-lg text-gray-200 select-none hidden lg:flex mr-8 items-center'>
<Icon.Logo className='w-auto h-6 mx-1' />
three.cad
</div>
<div className='h-full w-48 flex items-center justify-end'>
<Dialog />
</div>
2021-04-17 11:54:01 +08:00
</div>
<div className='w-auto h-full flex'>
2021-04-22 04:21:26 +08:00
{(sketchActive ? sketchModeButtons : partModeButtons).map(
([Icon, fcn, txt], idx) => (
Icon !== undefined ?
2021-05-04 12:02:46 +08:00
<Icon className={`cursor-pointer fill-current w-auto h-full p-3.5
${idx == buttonIdx[mode] ? 'bg-green-800' : 'hover:bg-gray-600 bg-transparent'}`} tooltip={txt}
2021-04-22 04:21:26 +08:00
onClick={fcn} key={idx}
/> :
<div className="w-12 h-full"></div>
))
2021-04-17 13:58:54 +08:00
}
</div>
2021-05-03 07:44:54 +08:00
<div className='w-auto h-full flex-1 justify-end flex-shrink-1 hidden md:flex'>
2021-05-04 12:02:46 +08:00
<MdHelpOutline className={`cursor-pointer fill-current w-auto h-full p-3
${modal ? 'bg-green-800' : 'hover:bg-gray-600 bg-transparent'}`} onClick={() => {
2021-05-03 07:44:54 +08:00
setModal(true)
2021-04-30 11:29:02 +08:00
}
} />
2021-05-03 07:44:54 +08:00
2021-05-04 12:02:46 +08:00
<a href='https://github.com/twpride/three.cad' className='h-full w-auto'>
<FaGithub className="text-gray-200 cursor-pointer hover:bg-gray-600 bg-transparent w-auto h-full p-3.5"></FaGithub>
2021-04-19 15:30:29 +08:00
</a>
2021-05-04 12:02:46 +08:00
<a href='https://www.linkedin.com/in/howard-hwang-b3000335' className='h-full w-auto'>
<FaLinkedin className="text-gray-200 cursor-pointer hover:bg-gray-600 bg-transparent w-auto h-full p-3.5"></FaLinkedin>
2021-04-19 15:30:29 +08:00
</a>
2021-04-17 11:54:01 +08:00
</div>
2021-05-03 07:44:54 +08:00
{
2021-05-04 12:02:46 +08:00
splash && <Modal {...{ setModal: setSplash, clickOut: false}}>
<Help {...{ setModal: setSplash, setQs: setModal }} />
</Modal>
}
{
modal && <Modal {...{ setModal, id: 'navbar' }}>
<QuickStart {...{ setModal }} />
2021-05-03 07:44:54 +08:00
</Modal>
}
2021-04-17 11:54:01 +08:00
2021-04-07 12:21:09 +08:00
</div>
2021-04-19 03:14:01 +08:00
}