2017-11-15 00:46:02 +01:00
|
|
|
import React from 'react';
|
|
|
|
import injectSheet from 'react-jss';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import * as CAL from 'cal';
|
2017-11-15 00:53:19 +01:00
|
|
|
import * as toolNames from '../constants/d2Tools';
|
2017-11-29 18:12:01 +01:00
|
|
|
import { PIPETTE } from '../constants/contextTools.js';
|
2017-11-15 00:53:19 +01:00
|
|
|
import { CANVAS_SIZE } from '../constants/d2Constants';
|
2017-11-15 15:13:49 +01:00
|
|
|
import createRAFOnce from '../utils/rafOnce.js';
|
|
|
|
import Grid from '../d2/Grid.js';
|
|
|
|
import BaseTool from '../d2/tools/BaseTool.js';
|
|
|
|
import TransformTool from '../d2/tools/TransformTool.js';
|
|
|
|
import EraserTool from '../d2/tools/EraserTool.js';
|
|
|
|
import BrushTool from '../d2/tools/BrushTool.js';
|
|
|
|
import PolygonTool from '../d2/tools/PolygonTool.js';
|
|
|
|
import CircleTool from '../d2/tools/CircleTool.js';
|
2017-11-29 18:12:01 +01:00
|
|
|
import PipetteTool from '../d2/tools/PipetteTool.js';
|
2017-11-15 15:13:49 +01:00
|
|
|
import TextTool from '../d2/tools/TextTool.js';
|
|
|
|
import BucketTool from '../d2/tools/BucketTool.js';
|
|
|
|
import PhotoGuideTool from '../d2/tools/PhotoGuideTool.js';
|
2017-11-15 00:53:19 +01:00
|
|
|
import { PIXEL_RATIO } from '../constants/general';
|
2017-11-15 15:13:49 +01:00
|
|
|
import ShapesManager from '../d2/ShapesManager.js';
|
|
|
|
import EventGroup from '../d2/EventGroup.js';
|
|
|
|
import ReactResizeDetector from 'react-resize-detector';
|
2017-11-22 13:26:05 +01:00
|
|
|
import { load as loadPattern } from '../d2/Shape.js';
|
2017-11-15 00:46:02 +01:00
|
|
|
// 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 = {
|
2017-11-29 18:12:01 +01:00
|
|
|
[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,
|
|
|
|
[PIPETTE]: PipetteTool
|
2017-11-15 00:46:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO the same as 3d
|
|
|
|
const styles = {
|
|
|
|
container: {
|
|
|
|
flexGrow: 1,
|
|
|
|
width: '100%',
|
|
|
|
overflow: 'hidden'
|
|
|
|
},
|
2017-11-15 15:13:49 +01:00
|
|
|
canvasContainer: {
|
2017-11-15 00:46:02 +01:00
|
|
|
'& 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);
|
2017-11-22 13:26:05 +01:00
|
|
|
loadPattern.then(() => {
|
|
|
|
this.activeNeedRender = true;
|
|
|
|
this.inactiveNeedRender = true;
|
|
|
|
this.renderRequest();
|
|
|
|
});
|
2017-11-15 00:46:02 +01:00
|
|
|
|
|
|
|
this.DOM = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2017-11-15 15:13:49 +01:00
|
|
|
const { canvasContainer } = this.refs;
|
2017-11-15 00:46:02 +01:00
|
|
|
this.container = canvasContainer;
|
|
|
|
|
2017-11-15 15:13:49 +01:00
|
|
|
this.container.appendChild(this.sceneInactive.image);
|
|
|
|
this.container.appendChild(this.sceneActive.image);
|
2017-11-15 00:46:02 +01:00
|
|
|
|
2017-11-15 15:13:49 +01:00
|
|
|
this.sceneActive.onClick = (event) => event.stopPropagation();
|
2017-11-15 00:46:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-11-15 15:13:49 +01:00
|
|
|
resizeHandler = (width, height) => {
|
2017-11-15 00:46:02 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
render() {
|
|
|
|
// debug('this.props.state: ', this.props.state);
|
|
|
|
const { state, classes } = this.props;
|
|
|
|
this.update(state);
|
|
|
|
this.renderCanvas();
|
|
|
|
return (
|
|
|
|
<div className={classes.container}>
|
2017-11-15 15:13:49 +01:00
|
|
|
<ReactResizeDetector handleWidth handleHeight onResize={this.resizeHandler} />
|
|
|
|
<div className={classes.canvasContainer} ref="canvasContainer" />
|
2017-11-15 00:46:02 +01:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap the component to inject dispatch and state into it
|
|
|
|
export default injectSheet(styles)(connect(state => ({
|
|
|
|
state: state.sketcher.present
|
|
|
|
}))(D2Panel));
|