add components

This commit is contained in:
casperlamboo 2017-11-15 00:46:02 +01:00
parent 28c7d06dfd
commit 6b56224b93
7 changed files with 853 additions and 0 deletions

34
package-lock.json generated
View File

@ -1415,6 +1415,16 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"create-react-class": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz",
"integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=",
"requires": {
"fbjs": "0.8.16",
"loose-envify": "1.3.1",
"object-assign": "4.1.1"
}
},
"cryptiles": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
@ -4516,6 +4526,25 @@
"theming": "1.2.1"
}
},
"react-notification-system": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/react-notification-system/-/react-notification-system-0.2.16.tgz",
"integrity": "sha1-m52iCw00eGtgBXxStCUW6hKVN0o=",
"requires": {
"create-react-class": "15.6.2",
"object-assign": "4.1.1",
"prop-types": "15.6.0"
}
},
"react-notification-system-redux": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-notification-system-redux/-/react-notification-system-redux-1.2.0.tgz",
"integrity": "sha1-FPsJFccuTBLZmD/+ByVOLNGexd8=",
"requires": {
"prop-types": "15.6.0",
"react-notification-system": "0.2.16"
}
},
"react-redux": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz",
@ -4734,6 +4763,11 @@
"x-path": "0.0.2"
}
},
"reselect": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz",
"integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc="
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",

View File

@ -33,11 +33,13 @@
"react": "^16.0.0",
"react-addons-update": "^15.6.2",
"react-jss": "^8.0.0",
"react-notification-system-redux": "^1.2.0",
"react-redux": "^5.0.6",
"react-resize-detector": "^1.1.0",
"redux-form": "^7.1.2",
"redux-undo": "^1.0.0-beta9-9-7",
"regenerator-runtime": "^0.11.0",
"reselect": "^3.0.1",
"semver": "^5.4.1",
"shortid": "^2.2.8",
"three": "^0.83.0",

87
src/components/App.js Normal file
View File

@ -0,0 +1,87 @@
import React from 'react';
import PropTypes from 'proptypes';
import injectSheet from 'react-jss';
import { connect } from 'react-redux';
import * as actions from 'doodle3d-core/actions';
import Logo from './Logo.js';
import D2Panel from './D2Panel.js';
import D3Panel from './D3Panel.js';
import SketcherToolbars from './SketcherToolbars.js';
import vlineImageURL from 'img/vline.png';
import btnUndoImageURL from 'img/mainmenu/btnUndo.png';
import btnRedoImageURL from 'img/mainmenu/btnRedo.png';
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
height: '100%'
},
appContainer: {
flexGrow: 1,
display: 'flex',
alignItems: 'stretch',
overflow: 'hidden'
},
vLine: {
backgroundSize: '14px auto',
backgroundImage: `url('${vlineImageURL}')`,
width: '28px'
},
undoMenu: {
display: 'flex',
position: 'absolute',
top: 0,
left: '50%',
marginLeft: '-68px'
},
undo: {
cursor: 'pointer',
backgroundSize: '68px auto',
width: '68px',
height: '58px',
backgroundImage: `url('${btnUndoImageURL}')`
},
redo: {
cursor: 'pointer',
backgroundSize: '71px auto',
width: '71px',
height: '58px',
backgroundImage: `url('${btnRedoImageURL}')`
}
};
class App extends React.Component {
static propTypes = {
undo: PropTypes.func.isRequired,
redo: PropTypes.func.isRequired,
classes: PropTypes.objectOf(PropTypes.string)
};
render() {
const { classes, undo, redo } = this.props;
return (
<div className={classes.container}>
<div className={classes.appContainer}>
<D2Panel />
<div className={classes.vLine} />
<D3Panel />
</div>
<Logo />
<SketcherToolbars />
<div className={classes.undoMenu}>
<div type="button" onTouchTap={undo} className={classes.undo} />
<div type="button" onTouchTap={redo} className={classes.redo} />
</div>
</div>
);
}
}
export default injectSheet(styles)(connect(null, {
undo: actions.undo.undo,
redo: actions.undo.redo
})(App));

237
src/components/D2Panel.js Normal file
View File

@ -0,0 +1,237 @@
import React from 'react';
import injectSheet from 'react-jss';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as CAL from 'cal';
import * as toolNames from 'doodle3d-core/constants/d2Tools';
import { CANVAS_SIZE } from 'doodle3d-core/constants/d2Constants';
import createRAFOnce from 'src/js/utils/rafOnce.js';
import Grid from 'src/js/design/Grid.js';
import BaseTool from 'src/js/design/tools/BaseTool.js';
import TransformTool from 'src/js/design/tools/TransformTool.js';
import EraserTool from 'src/js/design/tools/EraserTool.js';
import BrushTool from 'src/js/design/tools/BrushTool.js';
import PolygonTool from 'src/js/design/tools/PolygonTool.js';
import CircleTool from 'src/js/design/tools/CircleTool.js';
import TextTool from 'src/js/design/tools/TextTool.js';
import BucketTool from 'src/js/design/tools/BucketTool.js';
import PhotoGuideTool from 'src/js/design/tools/PhotoGuideTool.js';
import { PIXEL_RATIO } from 'doodle3d-core/constants/general';
import ShapesManager from 'src/js/design/ShapesManager.js';
import EventGroup from 'src/js/design/EventGroup.js';
// import createDebug from 'debug';
// const debug = createDebug('d3d:d2');
const rafOnce = createRAFOnce();
const CANVAS_WIDTH = CANVAS_SIZE * 2;
const CANVAS_HEIGHT = CANVAS_SIZE * 2;
const tools = {
[toolNames.TRANSFORM]: TransformTool,
[toolNames.ERASER]: EraserTool,
[toolNames.TEXT]: TextTool,
[toolNames.BUCKET]: BucketTool,
[toolNames.PHOTO_GUIDE]: PhotoGuideTool,
[toolNames.BRUSH]: BrushTool,
[toolNames.POLYGON]: PolygonTool,
[toolNames.CIRCLE]: CircleTool
};
// TODO the same as 3d
const styles = {
container: {
flexGrow: 1,
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
overflow: 'hidden'
},
canvas: {
flexGrow: 1,
margin: '0px',
overflow: 'hidden',
'& canvas': {
position: 'absolute'
}
}
};
class D2Panel extends React.Component {
static propTypes = {
state: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
classes: PropTypes.objectOf(PropTypes.string)
};
activeNeedRender = false;
inactiveNeedRender = false;
componentWillMount() {
// Scene space
this.sceneActive = new EventGroup({
autoClearCanvas: true,
autoDrawCanvas: true,
drawWhenBlurred: true
});
this.sceneInactive = new CAL.Group({
autoClearCanvas: true,
autoDrawCanvas: true,
drawWhenBlurred: true
});
// Objects Container Space
this.objectContainerActive = new EventGroup();
this.sceneActive.add(this.objectContainerActive);
this.objectContainerInactive = new CAL.Group();
this.sceneInactive.add(this.objectContainerInactive);
// Grid
this.objectContainerInactive.add(new Grid(new CAL.Color(0xdddddd)));
this.shapesManager = new ShapesManager(this.objectContainerActive, this.objectContainerInactive);
window.addEventListener('resize', this.resizeHandler);
this.DOM = null;
}
componentDidMount() {
const { canvasContainer, activeScene, inactiveScene } = this.refs;
console.log('canvasContainer: ', canvasContainer);
this.container = canvasContainer;
this.sceneActive.setCanvas(activeScene);
this.sceneInactive.setCanvas(inactiveScene);
this.resizeHandler();
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeHandler);
}
updateTool(newState) {
this.switchTool(newState.d2.tool);
if (this.tool) {
const toolNeedRender = this.tool.update(newState);
if (toolNeedRender) this.activeNeedRender = true;
}
}
switchTool(toolName) {
if (this.state && toolName === this.state.d2.tool) return;
// cleanup & remove previous tool
if (this.tool) {
this.tool.destroy();
this.objectContainerActive.remove(this.tool);
}
// initialize new tool
// The tool itself is added to the active object container (Objects Container Space), which is
// influenced by zooming and panning, but the tool also needs to add objects to the container that
// isn't; the active scene (Scene Space). For example the wheelContainer, because it needs a
// mouse position, regardless of zoom level.
// Each tool also get's the renderRequest callback to request re-renders,
// even when it won't change the state
const ToolClass = tools[toolName] ? tools[toolName] : BaseTool;
this.tool = new ToolClass(this.props.dispatch, this.sceneActive, this.renderRequest);
this.objectContainerActive.add(this.tool);
}
update(newState) {
if (this.state === newState) return;
this.updateTool(newState);
const shapesNeedRender = this.shapesManager.update(newState);
if (shapesNeedRender.active) this.activeNeedRender = true;
if (shapesNeedRender.inactive) this.inactiveNeedRender = true;
// Update Objects Container Space with zoom & panning
const newCanvasMatrix = newState.d2.canvasMatrix;
if (this.state && newCanvasMatrix !== this.state.d2.canvasMatrix) {
this.objectContainerActive.copyMatrix(newCanvasMatrix);
this.objectContainerInactive.copyMatrix(newCanvasMatrix);
this.activeNeedRender = true;
this.inactiveNeedRender = true;
}
const selection = (this.state) ? this.state.selection : null;
const newSelection = newState.selection;
if (!this.state || newSelection !== selection) {
const newSelectedObjects = newSelection.objects;
if (!selection || selection.objects !== newSelectedObjects) {
const selected = newSelectedObjects.map((object) => object.id);
this.shapesManager.updateTransparent(selected);
this.activeNeedRender = true;
this.inactiveNeedRender = true;
}
}
const dragSelect = (this.state) ? this.state.d2.transform.dragSelect : null;
const newDragSelect = newState.d2.transform.dragSelect;
if (!dragSelect || dragSelect !== newDragSelect) {
this.activeNeedRender = true;
}
this.state = newState;
}
resizeHandler = () => {
const { offsetWidth: width, offsetHeight: height } = this.container;
this.sceneActive.setSize(width, height, PIXEL_RATIO);
this.sceneInactive.setSize(width, height, PIXEL_RATIO);
this.sceneInactive.x = this.sceneActive.x = Math.round(width / 2 * PIXEL_RATIO);
this.sceneInactive.y = this.sceneActive.y = Math.round(height / 2 * PIXEL_RATIO);
const scale = Math.min(width * PIXEL_RATIO / CANVAS_WIDTH, height * PIXEL_RATIO / CANVAS_HEIGHT);
this.sceneInactive.scale = this.sceneActive.scale = scale;
this.inactiveNeedRender = this.activeNeedRender = true;
this.renderRequest();
};
renderRequest = () => {
this.activeNeedRender = true;
rafOnce(this.renderCanvas);
};
renderCanvas = () => {
if (this.activeNeedRender) {
this.sceneActive.cycle();
this.activeNeedRender = false;
}
if (this.inactiveNeedRender) {
this.sceneInactive.cycle();
this.inactiveNeedRender = false;
}
};
stopPropagation(event) {
// stop propagation to prefent popup to close immidiatly
event.stopPropagation();
}
render() {
// debug('this.props.state: ', this.props.state);
const { state, classes } = this.props;
this.update(state);
this.renderCanvas();
return (
<div className={classes.container}>
<div className={classes.canvas} ref="canvasContainer">
<canvas ref="inactiveScene" />
<canvas onClick={this.stopPropagation} ref="activeScene" />
</div>
</div>
);
}
}
// Wrap the component to inject dispatch and state into it
export default injectSheet(styles)(connect(state => ({
state: state.sketcher.present
}))(D2Panel));

264
src/components/D3Panel.js Normal file
View File

@ -0,0 +1,264 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as THREE from 'three';
import injectSheet from 'react-jss';
import { CANVAS_SIZE } from 'doodle3d-core/constants/d2Constants';
import ShapesManager from 'doodle3d-core/d3/ShapesManager';
import { getSelectedObjectsSelector, getBoundingBox } from 'doodle3d-core/utils/selectionUtils';
import createRAFOnce from 'src/js/utils/rafOnce.js';
import { hasExtensionsFor } from 'doodle3d-core/utils/WebGLSupport.js';
import { PIXEL_RATIO } from 'doodle3d-core/constants/general.js';
import * as toolsNames from 'doodle3d-core/constants/d3Tools.js';
import { EventScene, EventObject3D } from 'src/js/preview/EventScene.js';
import HeightTransformer from 'src/js/preview/transformers/HeightTransformer.js';
import TwistTransformer from 'src/js/preview/transformers/TwistTransformer.js';
import SculptTransformer from 'src/js/preview/transformers/SculptTransformer.js';
import StampTransformer from 'src/js/preview/transformers/StampTransformer.js';
import SelectionBox from 'src/js/preview/SelectionBox.js';
import ToonShaderRenderChain from 'doodle3d-core/d3/ToonShaderRenderChain';
import RenderChain from 'doodle3d-core/d3/RenderChain';
import BaseTransformer from 'src/js/preview/transformers/BaseTransformer.js';
import Camera from 'src/js/preview/Camera.js';
// import createDebug from 'debug';
// const debug = createDebug('d3d:d3');
const rafOnce = createRAFOnce();
const CANVAS_WIDTH = CANVAS_SIZE * 2;
const CANVAS_HEIGHT = CANVAS_SIZE * 2;
const tools = {
[toolsNames.HEIGHT]: HeightTransformer,
[toolsNames.SCULPT]: SculptTransformer,
[toolsNames.TWIST]: TwistTransformer,
[toolsNames.STAMP]: StampTransformer
};
// TODO the same as 3d
const styles = {
container: {
flexGrow: 1,
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
overflow: 'hidden'
},
canvas: {
flexGrow: 1,
margin: '0px',
overflow: 'hidden',
'& canvas': {
position: 'absolute'
}
}
};
class D3Panel extends React.Component {
static propTypes = {
state: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
classes: PropTypes.objectOf(PropTypes.string)
};
needRender = false;
componentWillMount() {
this.createScene();
if (hasExtensionsFor.toonShaderPreview) {
this.renderChain = new ToonShaderRenderChain(this.renderer, this.scene, this.camera, {
UI: this.UIContainer,
shapes: this.shapesManager,
boundingBox: this.selectionBox,
plane: this.plane
});
} else {
this.renderer.setClearColor(0xffffff);
this.renderChain = new RenderChain(this.renderer, this.scene, this.camera);
}
window.addEventListener('resize', this.resizeHandler);
this.DOM = null;
}
componentDidMount() {
this.container = this.refs.canvasContainer;
this.resizeHandler();
this.container.appendChild(this.renderer.domElement);
this.renderScene(); // immidiatly render because when THREE.JS inits, a black screen is generated
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeHandler);
this.scene.setCanvas(null);
}
createScene() {
this.renderer = new THREE.WebGLRenderer();
this.scene = new EventScene(this.renderer.domElement);
this.camera = new Camera(50, 1, 1, 10000);
this.scene.add(this.camera);
// this.grid = new THREE.GridHelper(200, 20);
// this.grid.setColors(0xe0e0e0, 0xeeeeee);
// this.scene.add(this.grid);
const geometryPlane = new THREE.PlaneGeometry(CANVAS_WIDTH, CANVAS_HEIGHT);
const materialPlane = new THREE.MeshBasicMaterial({
color: 0xcccccc,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.5
});
this.plane = new THREE.Mesh(geometryPlane, materialPlane);
this.plane.rotation.x = Math.PI / 2;
this.plane.position.y = -0.01;
this.plane.name = 'bed-plane';
this.scene.add(this.plane);
const directionalLight = new THREE.PointLight(0xffffff, 0.6);
this.camera.add(directionalLight);
const ambientLight = new THREE.AmbientLight(0x505050);
this.scene.add(ambientLight);
this.shapesManager = new ShapesManager({ toonShader: hasExtensionsFor.toonShaderPreview });
this.UIContainer = new EventObject3D();
this.UIContainer.matrixAutoUpdate = false;
this.UIContainer.name = 'ui-container';
this.scene.add(this.shapesManager, this.UIContainer);
this.selectionBox = new SelectionBox();
this.scene.add(this.selectionBox);
}
updateTool(newState) {
this.switchTool(newState.d3.tool);
if (this.tool) {
const toolNeedRender = this.tool.update(newState);
if (toolNeedRender) this.activeNeedRender = true;
}
}
switchTool(toolName) {
if (this.state && toolName === this.state.d3.tool) return;
// cleanup & remove previous tool
if (this.tool) {
this.tool.destroy();
this.UIContainer.remove(this.tool);
}
// initialize new tool
// The tool itself is added to the active object container (Objects Container Space), which is
// influenced by zooming and panning, but the tool also needs to add objects to the container that
// isn't; the active scene (Scene Space). For example the wheelContainer, because it needs a
// mouse position, regardless of zoom level.
// Each tool also get's the renderRequest callback to request re-renders,
// even when it won't change the state
const ToolClass = tools[toolName] ? tools[toolName] : BaseTransformer;
this.tool = new ToolClass(this.props.dispatch, this.scene, this.camera, this.renderer.domElement);
this.UIContainer.add(this.tool);
this.needRender = true;
}
update(newState) {
if (this.state === newState) return;
if (!this.state || newState.activeSpace !== this.state.activeSpace) {
const spaceMatrix = newState.spaces[newState.activeSpace].matrix;
this.UIContainer.matrix.copy(spaceMatrix);
this.UIContainer.updateMatrixWorld(true);
this.selectionBox.matrix.copy(spaceMatrix);
this.selectionBox.updateMatrixWorld(true);
}
const selection = (this.state) ? this.state.selection : null;
const newSelection = newState.selection;
this.updateTool(newState);
const meshNeedRender = this.shapesManager.update(newState);
if (meshNeedRender) this.needRender = true;
const hasSelections = newSelection.objects.length > 0;
if (!this.state || newSelection !== selection) {
const newSelectedObjects = newSelection.objects;
if (!selection || selection.objects !== newSelectedObjects) {
const selected = newSelectedObjects.map((object) => object.id);
this.shapesManager.updateTransparent(selected);
this.needRender = true;
this.selectionBox.visible = hasSelections;
}
if (hasSelections) {
this.selectionBox.updateTransform(newSelection.transform);
this.needRender = true;
}
}
if (!this.state || this.state.d3.camera !== newState.d3.camera) {
this.camera.update(newState.d3.camera);
this.needRender = true;
}
// NOTE: if there is a selection, any state change will trigger this:
if (hasSelections) {
const objectsById = newState.objectsById;
const selectedShapeDatas = getSelectedObjectsSelector(newSelection.objects, objectsById);
const boundingBox = getBoundingBox(selectedShapeDatas, newSelection.transform);
// TODO: add selected shape datas and boundingbox diff checking
this.selectionBox.updateBoundingBox(boundingBox);
}
this.state = newState;
}
resizeHandler = () => {
const { offsetWidth: width, offsetHeight: height } = this.container;
// set renderer size
this.renderChain.setSize(width, height, PIXEL_RATIO);
this.renderRequest();
};
renderRequest = () => {
this.needRender = true;
rafOnce(this.renderScene);
};
render() {
const { state, classes } = this.props;
this.update(state);
this.renderScene();
return (
<div className={classes.container}>
<div className={classes.canvas} ref="canvasContainer"/>
</div>
);
}
renderScene = () => {
if (this.needRender) {
this.scene.updateMatrixWorld();
this.renderChain.render(true);
this.needRender = false;
}
};
}
// Wrap the component to inject dispatch and state into it
export default injectSheet(styles)(connect(state => ({
state: state.sketcher.present
}))(D3Panel));

29
src/components/Logo.js Normal file
View File

@ -0,0 +1,29 @@
import React from 'react';
import doodleSignImageURL from 'img/doodle3d-sign.png';
import injectSheet from 'react-jss';
const styles = {
container: {
position: 'absolute',
top: '0px',
right: '15%',
width: '19%',
pointerEvents: 'none', // enable clicking through logo
'& img': {
width: '100%',
maxWidth: '290px'
}
},
'@media (max-width: 555px)': {
container: {
display: 'none'
}
}
};
const Logo = ({ classes }) => (
<div className={classes.container}>
<img src={doodleSignImageURL} />
</div>
);
export default injectSheet(styles)(Logo);

View File

@ -0,0 +1,200 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as actions from 'doodle3d-core/actions/index.js';
import { connect } from 'react-redux';
import * as contextTools from 'doodle3d-core/constants/contextTools';
import * as d2Tools from 'doodle3d-core/constants/d2Tools';
import { createSelector } from 'reselect';
import Menu from 'src/js/components/Menu.js';
import SubMenu from 'src/js/components/SubMenu.js';
import MenuItem from 'src/js/components/MenuItem.js';
// import createDebug from 'debug';
// const debug = createDebug('d3d:sketchertoolbars');
const TOOLBAR2D = 'toolbar2d';
const TOOLBAR3D = 'toolbar3d';
const CONTEXT = 'context';
class SketcherToolbars extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
toolbar2d: PropTypes.object, // TODO: specify further
toolbar3d: PropTypes.object, // TODO: specify further
context: PropTypes.object // TODO: specify further
};
onSelect2D = ({ value }) => {
const { dispatch } = this.props;
dispatch(actions.d2ChangeTool(value));
};
onSelect3D = ({ value }) => {
this.props.dispatch(actions.d3ChangeTool(value));
};
onSelectContext = ({ value }) => {
switch (value) {
case contextTools.DUPLICATE:
this.props.dispatch(actions.duplicateSelection(value));
break;
case contextTools.DELETE:
this.props.dispatch(actions.deleteSelection(value));
break;
case contextTools.UNION:
this.props.dispatch(actions.union(value));
break;
case contextTools.INTERSECT:
this.props.dispatch(actions.intersect(value));
break;
default:
const { dispatch } = this.props;
dispatch(actions.contextChangeTool(value));
break;
}
};
onOpen = ({ menuValue }) => {
this.props.dispatch(actions.menuOpen(menuValue));
};
onClose = ({ menuValue }) => {
this.props.dispatch(actions.menuClose(menuValue));
};
renderToolbar = (value, onSelect, data) => {
return (
<div id={`${value}-container`}>
<Menu
id={value}
value={value}
className="toolbar"
onSelect={onSelect}
onOpen={this.onOpen}
onClose={this.onClose}
selectedValue={data.selected}
>
{renderChildren(data.children)}
</Menu>
</div>
);
};
render() {
const { toolbar2d, toolbar3d, context } = this.props;
return (
<div>
<div id="sketcher-toolbars">
{this.renderToolbar(TOOLBAR2D, this.onSelect2D, toolbar2d)}
{this.renderToolbar(TOOLBAR3D, this.onSelect3D, toolbar3d)}
</div>
{context.children.length > 0 && <div className="centerer">
{this.renderToolbar(CONTEXT, this.onSelectContext, context)}
</div>}
</div>
);
}
}
function renderChildren(children) {
const components = [];
for (const child of children) {
let component;
if (child.children.length > 0) {
component = (
<SubMenu
id={child.value}
value={child.value}
key={child.value}
selectedValue={child.selected}
open={child.open}
svg={child.svg}
selectOnOpen={child.selectOnOpen}
toggleBehavior={child.toggleBehavior}
>
{renderChildren(child.children)}
</SubMenu>
);
} else {
component = (
<MenuItem
id={child.value}
value={child.value}
key={child.value}
svg={child.svg}
disabled={child.disabled}
/>
);
}
components.push(component);
}
return components;
}
function filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, menus) {
const showUnion = activeTool === d2Tools.TRANSFORM && allObjectsAreFilled && numSelectedObjects >= 2;
const showIntersect = activeTool === d2Tools.TRANSFORM && numSelectedObjects >= 1;
return {
...menus,
children: menus.children.filter(({ value }) => {
switch (value) {
case contextTools.ADVANCED:
return showUnion || showIntersect;
case contextTools.UNION:
return showUnion;
case contextTools.INTERSECT:
return showIntersect;
case contextTools.DUPLICATE:
case contextTools.DELETE:
case contextTools.FILL_TOGGLE:
return numSelectedObjects > 0;
case contextTools.ALIGN:
return numSelectedObjects > 1;
case contextTools.COLOR_PICKER:
if (activeTool === d2Tools.ERASER) return false;
if (activeTool === d2Tools.TRANSFORM && numSelectedObjects === 0) return false;
return true;
case contextTools.ERASER_SIZE:
return activeTool === d2Tools.ERASER;
case contextTools.BRUSH_SIZE:
return activeTool === d2Tools.BRUSH;
default:
return true;
}
}).map(child => {
return filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, child);
})
};
}
function nestChildren(menus, target) {
// const targetStr = JSON.stringify(target);
const children = target.children.map(value => nestChildren(menus, menus[value]));
const { selected } = target;
return {
svg: selected && children.find(({ value }) => value === selected).svg,
...target,
selected,
children: children.filter(({ hide }) => !hide)
};
}
const getMenus = createSelector([
state => state.sketcher.present.menus,
state => state.sketcher.present.selection.objects.length,
state => state.sketcher.present.selection.objects.every(({ id }) => state.sketcher.present.objectsById[id].fill),
state => state.sketcher.present.d2.tool
], (menus, numSelectedObjects, allObjectsAreFilled, activeTool) => ({
toolbar2d: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[TOOLBAR2D])),
toolbar3d: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[TOOLBAR3D])),
context: filterMenus(numSelectedObjects, allObjectsAreFilled, activeTool, nestChildren(menus, menus[CONTEXT]))
}));
export default connect(getMenus)(SketcherToolbars);