use redux to manage dialog

master
howard 2021-04-16 20:54:01 -07:00
parent 69b2fff6d1
commit 53ad7c97e9
7 changed files with 114 additions and 70 deletions

View File

@ -153,6 +153,7 @@ class Sketch {
activate() { activate() {
console.log('activatee')
window.addEventListener('keydown', this.onKeyPress) window.addEventListener('keydown', this.onKeyPress)
this.canvas.addEventListener('pointerdown', this.onPick) this.canvas.addEventListener('pointerdown', this.onPick)
this.canvas.addEventListener('pointermove', this.onHover) this.canvas.addEventListener('pointermove', this.onHover)

View File

@ -10,8 +10,8 @@ body {
height: 100%; height: 100%;
font-family: sans-serif; font-family: sans-serif;
overflow: hidden; overflow: hidden;
--topNavH: 48px; --topNavH: 3rem;
--sideNavW: 240px; --sideNavW: 15rem;
} }
#c { #c {
@ -38,8 +38,10 @@ body {
.dialog { .dialog {
position: absolute; position: absolute;
top: var(--topNavH); height: var(--topNavH);
left: var(--sideNavW); left:0;
right:0;
top:0;
} }

View File

@ -2,15 +2,15 @@
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import React, { useState } from 'react' import React, { useState } from 'react'
import { createStore, applyMiddleware} from 'redux' import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { reducer } from './reducer' import { reducer } from './reducer'
import logger from 'redux-logger' import logger from 'redux-logger'
import { Tree } from './tree' import { Tree } from './tree'
import { NavBar } from './navBar' import { NavBar } from './navBar'
import { ToolTip} from './toolTip' import { ToolTip } from './toolTip'
import { Dialog } from './dialog'
import './app.css' import './app.css'
@ -31,14 +31,15 @@ const preloadedState = {
const store = createStore(reducer, {}, applyMiddleware(logger)) const store = createStore(reducer, {}, applyMiddleware(logger))
// const store = createStore(reducer, sc.loadState(), applyMiddleware(logger)) // const store = createStore(reducer, sc.loadState(), applyMiddleware(logger))
const App = ({ store }) => { const App = ({ store }) => {
const [dialog, setDialog] = useState()
return <Provider store={store}> return <Provider store={store}>
<NavBar setDialog={setDialog}/>
<NavBar />
<Tree /> <Tree />
<Dialog {...{dialog, setDialog}}/> <ToolTip />
<ToolTip/>
</Provider> </Provider>
}; };

View File

@ -3,64 +3,71 @@
import React, { useEffect, useReducer, useRef, useState } from 'react'; import React, { useEffect, useReducer, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { MdDone, MdClose } from 'react-icons/md' import { MdDone, MdClose } from 'react-icons/md'
import { GiVerticalFlip } from 'react-icons/gi'
import * as Icon from "./icons"; import * as Icon from "./icons";
export const Dialog = ({ dialog, setDialog }) => {
if (!dialog) return null
export const Dialog = () => {
const dialog = useSelector(state => state.ui.dialog)
const dispatch = useDispatch() const dispatch = useDispatch()
const treeEntriesById = useSelector(state => state.treeEntries.byId)
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
const ref = useRef() const ref = useRef()
useEffect(() => { useEffect(() => {
if (!ref.current) return
ref.current.focus() ref.current.focus()
}, []) }, [dialog])
const extrude = () => { const extrude = () => {
let sketch sc.extrude(dialog.target, ref.current.value)
if (sc.activeSketch) {
sketch = sc.activeSketch
} else if (sc.selected.length === 1 && sc.selected[0].userData.type == 'sketch') {
sketch = treeEntriesById[sc.selected[0].name]
} else {
console.log('invalid selection')
return
}
setDialog(null)
sc.extrude(sketch, ref.current.value)
sc.render() sc.render()
forceUpdate()
}
dispatch({ type: "clear-dialog" })
forceUpdate()
}
const [_, forceUpdate] = useReducer(x => x + 1, 0); const [_, forceUpdate] = useReducer(x => x + 1, 0);
return <div className='dialog w-40 h-10 flex items-center bg-gray-700'> switch (dialog.action) {
{/* return <div className='w-40 h-full flex items-center bg-gray-700'> */} case 'extrude':
<input className='w-1/2' type="number" {...useNumField(1)} step="0.1" ref={ref} /> return <>
<Icon.Flip className="btn w-auto h-full p-2" <input className='w-1/2' type="number" defaultValue="1" step="0.1" ref={ref} />
onClick={() => ref.current.value *= -1} <Icon.Flip className="btn w-auto h-full p-2"
/> onClick={() => ref.current.value *= -1}
<MdDone />
className="btn w-auto h-full p-2" <MdDone
onClick={extrude} className="btn w-auto h-full p-2"
/> onClick={extrude}
<MdClose className="btn w-auto h-full p-2" />
onClick={() => setDialog(null)} <MdClose className="btn w-auto h-full p-2"
/> onClick={() => dispatch({ type: "clear-dialog" })}
</div> />
</>
case 'sketch':
return <>
<MdDone
className="btn w-auto h-full p-2"
onClick={() => {
// dispatch({ type: 'update-descendents', sketch})
sc.activeSketch.deactivate()
sc.render()
forceUpdate()
}}
/>
<MdClose className="btn w-auto h-full p-2"
onClick={() => dispatch({ type: "clear-dialog" })}
/>
</>
default:
return null
}
} }
export const useNumField = (initValue = 0) => {
const [value, setValue] = useState(initValue);
const onChange = e => setValue(e.target.value);
return { value, onChange };
}

View File

@ -7,12 +7,12 @@ import { useDispatch, useSelector } from 'react-redux'
import { FaEdit } from 'react-icons/fa' import { FaEdit } from 'react-icons/fa'
import { MdDone, MdSave, MdFolder } from 'react-icons/md' import { MdDone, MdSave, MdFolder } from 'react-icons/md'
import * as Icon from "./icons"; import * as Icon from "./icons";
import { Dialog } from './dialog'
export const NavBar = () => {
export const NavBar = ({ setDialog }) => {
const dispatch = useDispatch() const dispatch = useDispatch()
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId) const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
const treeEntriesById = useSelector(state => state.treeEntries.byId)
const boolOp = (code) => { const boolOp = (code) => {
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
@ -21,11 +21,11 @@ export const NavBar = ({ setDialog }) => {
sc.render() sc.render()
forceUpdate() forceUpdate()
} }
const extrude = () => { setDialog(true) }
const addSketch = () => { const addSketch = () => {
sc.addSketch() sc.addSketch()
console.log(!!sc.activeSketch, 'state') dispatch({ type: 'set-dialog', action: 'sketch' })
forceUpdate() forceUpdate()
} }
@ -45,14 +45,18 @@ export const NavBar = ({ setDialog }) => {
// }, [treeEntriesById]) // }, [treeEntriesById])
const btnz = [ const sketchModeButtons = [
[MdDone, () => { // [MdDone, () => {
// // dispatch({ type: 'update-descendents', sketch})
// sc.activeSketch.deactivate()
// sc.render()
// forceUpdate()
// }, 'Finish'],
[Icon.Extrude, () => {
sc.activeSketch.deactivate() sc.activeSketch.deactivate()
// dispatch({ type: 'update-descendents', sketch}) dispatch({ type: 'set-dialog', action: 'extrude', target: sc.activeSketch })
sc.render() }, 'Extrude [e]'],
forceUpdate()
}, 'Finish'],
[Icon.Extrude, extrude, 'Extrude [e]'],
[Icon.Dimension, () => sc.activeSketch.command('d'), 'Dimension [d]'], [Icon.Dimension, () => sc.activeSketch.command('d'), 'Dimension [d]'],
[Icon.Line, () => sc.activeSketch.command('l'), 'Line [l]'], [Icon.Line, () => sc.activeSketch.command('l'), 'Line [l]'],
[Icon.Arc, () => sc.activeSketch.command('a'), 'Arc [a]'], [Icon.Arc, () => sc.activeSketch.command('a'), 'Arc [a]'],
@ -63,9 +67,12 @@ export const NavBar = ({ setDialog }) => {
] ]
const btnz2 = [ const partModeButtons = [
[FaEdit, addSketch, 'Sketch [s]'], [FaEdit, addSketch, 'Sketch [s]'],
[Icon.Extrude, extrude, 'Extrude [e]'], [Icon.Extrude, () => {
dispatch({ type: 'set-dialog', action: 'extrude', target: treeEntriesById[sc.selected[0].name] })
}, 'Extrude [e]'],
[Icon.Union, () => boolOp('u'), 'Union'], [Icon.Union, () => boolOp('u'), 'Union'],
[Icon.Subtract, () => boolOp('s'), 'Subtract'], [Icon.Subtract, () => boolOp('s'), 'Subtract'],
[Icon.Intersect, () => boolOp('i'), 'Intersect'], [Icon.Intersect, () => boolOp('i'), 'Intersect'],
@ -77,19 +84,26 @@ export const NavBar = ({ setDialog }) => {
const [_, forceUpdate] = useReducer(x => x + 1, 0); const [_, forceUpdate] = useReducer(x => x + 1, 0);
return <div className='topNav flex justify-center items-center bg-gray-700'> return <div className='topNav flex justify-center items-center bg-gray-700'>
<div className='w-40 h-full flex items-center mr-12'>
<Dialog />
</div>
{ {
activeSketchId ? activeSketchId ?
btnz.map(([Icon, fcn, txt, shortcut], idx) => ( sketchModeButtons.map(([Icon, fcn, txt, shortcut], idx) => (
<Icon className="btn w-auto h-full p-3.5" tooltip={txt} <Icon className="btn w-auto h-full p-3.5" tooltip={txt}
onClick={fcn} key={idx} onClick={fcn} key={idx}
/> />
)) ))
: :
btnz2.map(([Icon, fcn, txt, shortcut], idx) => ( partModeButtons.map(([Icon, fcn, txt, shortcut], idx) => (
<Icon className="btn w-auto h-full p-3.5" tooltip={txt} <Icon className="btn w-auto h-full p-3.5" tooltip={txt}
onClick={fcn} key={idx} onClick={fcn} key={idx}
/> />
)) ))
} }
<div className='w-40 h-full flex items-center'>
</div>
</div> </div>
} }

View File

@ -82,9 +82,25 @@ export function treeEntries(state = defaultState, action) {
} }
} }
export function ui(state = {dialog:{}}, action) {
switch (action.type) {
case 'set-dialog':
return update(state, {
dialog: { $set: { target: action.target, action: action.action } },
})
case 'clear-dialog':
return update(state, {
dialog: { $set: {} },
})
default:
return state
}
}
export const reducer = combineReducers({ export const reducer = combineReducers({
ui,
treeEntries treeEntries
}) })

View File

@ -13,7 +13,7 @@ export const Tree = () => {
return <div className='sideNav flex flex-col bg-gray-800'> return <div className='sideNav flex flex-col bg-gray-800'>
{treeEntries.allIds.map((entId, idx) => ( {treeEntries.allIds.map((entId, idx) => (
<TreeEntry key={idx} entId={entId} /> <TreeEntry key={idx} entId={entId}/>
))} ))}
</div> </div>
@ -28,6 +28,8 @@ const treeIcons = {
const TreeEntry = ({ entId }) => { const TreeEntry = ({ entId }) => {
const state = useSelector(state => state.treeEntries) const state = useSelector(state => state.treeEntries)
const treeEntries = useSelector(state => state.treeEntries.byId) const treeEntries = useSelector(state => state.treeEntries.byId)
const dispatch = useDispatch() const dispatch = useDispatch()
@ -54,6 +56,7 @@ const TreeEntry = ({ entId }) => {
sketch.activate() sketch.activate()
sc.clearSelection() sc.clearSelection()
sc.activeSketch = sketch; sc.activeSketch = sketch;
dispatch({ type: 'set-dialog', action: 'sketch' })
sc.render() sc.render()
} }