mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Merge pull request #45 from fsih/colorChanger
Selection updates color indicators, and color indicators update selection
This commit is contained in:
commit
582ab61665
38 changed files with 658 additions and 144 deletions
|
@ -1,5 +1,5 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
sudo: false
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
node_js:
|
node_js:
|
||||||
- 6
|
- 6
|
||||||
|
@ -10,6 +10,8 @@ env:
|
||||||
global:
|
global:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
install:
|
install:
|
||||||
|
- sudo apt-get update && sudo apt-get install -y libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev pkg-config
|
||||||
|
- npm install canvas
|
||||||
- npm --production=false install
|
- npm --production=false install
|
||||||
- npm --production=false update
|
- npm --production=false update
|
||||||
before_deploy:
|
before_deploy:
|
||||||
|
|
|
@ -6,6 +6,8 @@ import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
||||||
import Label from './forms/label.jsx';
|
import Label from './forms/label.jsx';
|
||||||
import Input from './forms/input.jsx';
|
import Input from './forms/input.jsx';
|
||||||
|
|
||||||
|
import {MIXED} from '../helper/style-path';
|
||||||
|
|
||||||
import styles from './paint-editor.css';
|
import styles from './paint-editor.css';
|
||||||
|
|
||||||
const BufferedInput = BufferedInputHOC(Input);
|
const BufferedInput = BufferedInputHOC(Input);
|
||||||
|
@ -21,7 +23,8 @@ const FillColorIndicatorComponent = props => (
|
||||||
<Label text={props.intl.formatMessage(messages.fill)}>
|
<Label text={props.intl.formatMessage(messages.fill)}>
|
||||||
<BufferedInput
|
<BufferedInput
|
||||||
type="text"
|
type="text"
|
||||||
value={props.fillColor}
|
value={props.fillColor === MIXED ? 'mixed' :
|
||||||
|
props.fillColor === null ? 'transparent' : props.fillColor} // @todo Don't use text
|
||||||
onSubmit={props.onChangeFillColor}
|
onSubmit={props.onChangeFillColor}
|
||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
@ -29,7 +32,7 @@ const FillColorIndicatorComponent = props => (
|
||||||
);
|
);
|
||||||
|
|
||||||
FillColorIndicatorComponent.propTypes = {
|
FillColorIndicatorComponent.propTypes = {
|
||||||
fillColor: PropTypes.string.isRequired,
|
fillColor: PropTypes.string,
|
||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
onChangeFillColor: PropTypes.func.isRequired
|
onChangeFillColor: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,8 @@ import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
||||||
import Label from './forms/label.jsx';
|
import Label from './forms/label.jsx';
|
||||||
import Input from './forms/input.jsx';
|
import Input from './forms/input.jsx';
|
||||||
|
|
||||||
|
import {MIXED} from '../helper/style-path';
|
||||||
|
|
||||||
import styles from './paint-editor.css';
|
import styles from './paint-editor.css';
|
||||||
|
|
||||||
const BufferedInput = BufferedInputHOC(Input);
|
const BufferedInput = BufferedInputHOC(Input);
|
||||||
|
@ -21,7 +23,9 @@ const StrokeColorIndicatorComponent = props => (
|
||||||
<Label text={props.intl.formatMessage(messages.stroke)}>
|
<Label text={props.intl.formatMessage(messages.stroke)}>
|
||||||
<BufferedInput
|
<BufferedInput
|
||||||
type="text"
|
type="text"
|
||||||
value={props.strokeColor}
|
// @todo Don't use text
|
||||||
|
value={props.strokeColor === MIXED ? 'mixed' :
|
||||||
|
props.strokeColor === null ? 'transparent' : props.strokeColor}
|
||||||
onSubmit={props.onChangeStrokeColor}
|
onSubmit={props.onChangeStrokeColor}
|
||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
@ -31,7 +35,7 @@ const StrokeColorIndicatorComponent = props => (
|
||||||
StrokeColorIndicatorComponent.propTypes = {
|
StrokeColorIndicatorComponent.propTypes = {
|
||||||
intl: intlShape,
|
intl: intlShape,
|
||||||
onChangeStrokeColor: PropTypes.func.isRequired,
|
onChangeStrokeColor: PropTypes.func.isRequired,
|
||||||
strokeColor: PropTypes.string.isRequired
|
strokeColor: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(StrokeColorIndicatorComponent);
|
export default injectIntl(StrokeColorIndicatorComponent);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
import BufferedInputHOC from './forms/buffered-input-hoc.jsx';
|
||||||
import Input from './forms/input.jsx';
|
import Input from './forms/input.jsx';
|
||||||
|
|
||||||
import {MAX_STROKE_WIDTH} from '../reducers/stroke-width';
|
import {MAX_STROKE_WIDTH} from '../reducers/stroke-width';
|
||||||
|
|
||||||
import styles from './paint-editor.css';
|
import styles from './paint-editor.css';
|
||||||
|
@ -15,7 +16,7 @@ const StrokeWidthIndicatorComponent = props => (
|
||||||
max={MAX_STROKE_WIDTH}
|
max={MAX_STROKE_WIDTH}
|
||||||
min="0"
|
min="0"
|
||||||
type="number"
|
type="number"
|
||||||
value={props.strokeWidth}
|
value={props.strokeWidth ? props.strokeWidth : 0}
|
||||||
onSubmit={props.onChangeStrokeWidth}
|
onSubmit={props.onChangeStrokeWidth}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +24,7 @@ const StrokeWidthIndicatorComponent = props => (
|
||||||
|
|
||||||
StrokeWidthIndicatorComponent.propTypes = {
|
StrokeWidthIndicatorComponent.propTypes = {
|
||||||
onChangeStrokeWidth: PropTypes.func.isRequired,
|
onChangeStrokeWidth: PropTypes.func.isRequired,
|
||||||
strokeWidth: PropTypes.number.isRequired
|
strokeWidth: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StrokeWidthIndicatorComponent;
|
export default StrokeWidthIndicatorComponent;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import paper from 'paper';
|
||||||
import log from '../../log/log';
|
import log from '../../log/log';
|
||||||
import BroadBrushHelper from './broad-brush-helper';
|
import BroadBrushHelper from './broad-brush-helper';
|
||||||
import SegmentBrushHelper from './segment-brush-helper';
|
import SegmentBrushHelper from './segment-brush-helper';
|
||||||
import {styleCursorPreview} from './style-path';
|
import {MIXED, styleCursorPreview} from '../../helper/style-path';
|
||||||
import {clearSelection} from '../../helper/selection';
|
import {clearSelection} from '../../helper/selection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,11 +27,18 @@ class Blobbiness {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {function} updateCallback call when the drawing has changed to let listeners know
|
* @param {function} updateCallback call when the drawing has changed to let listeners know
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
*/
|
*/
|
||||||
constructor (updateCallback) {
|
constructor (updateCallback, clearSelectedItems) {
|
||||||
this.broadBrushHelper = new BroadBrushHelper();
|
this.broadBrushHelper = new BroadBrushHelper();
|
||||||
this.segmentBrushHelper = new SegmentBrushHelper();
|
this.segmentBrushHelper = new SegmentBrushHelper();
|
||||||
this.updateCallback = updateCallback;
|
this.updateCallback = updateCallback;
|
||||||
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
|
|
||||||
|
// The following are stored to check whether these have changed and the cursor preview needs to be redrawn.
|
||||||
|
this.strokeColor = null;
|
||||||
|
this.brushSize = null;
|
||||||
|
this.fillColor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +52,18 @@ class Blobbiness {
|
||||||
* @param {?number} options.strokeWidth Width of the brush outline.
|
* @param {?number} options.strokeWidth Width of the brush outline.
|
||||||
*/
|
*/
|
||||||
setOptions (options) {
|
setOptions (options) {
|
||||||
this.options = options;
|
const oldFillColor = this.options ? this.options.fillColor : 'black';
|
||||||
|
const oldStrokeColor = this.options ? this.options.strokeColor : null;
|
||||||
|
const oldStrokeWidth = this.options ? this.options.strokeWidth : null;
|
||||||
|
// If values are mixed, it means the color was set by a selection contained multiple values.
|
||||||
|
// In this case keep drawing with the previous values if any. (For stroke width, null indicates
|
||||||
|
// mixed, because stroke width is required to be a number)
|
||||||
|
this.options = {
|
||||||
|
...options,
|
||||||
|
fillColor: options.fillColor === MIXED ? oldFillColor : options.fillColor,
|
||||||
|
strokeColor: options.strokeColor === MIXED ? oldStrokeColor : options.strokeColor,
|
||||||
|
strokeWidth: options.strokeWidth === null ? oldStrokeWidth : options.strokeWidth
|
||||||
|
};
|
||||||
this.resizeCursorIfNeeded();
|
this.resizeCursorIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +251,7 @@ class Blobbiness {
|
||||||
// Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset
|
// Eraser didn't hit anything selected, so assume they meant to erase from all instead of from subset
|
||||||
// and deselect the selection
|
// and deselect the selection
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
items = paper.project.getItems({
|
items = paper.project.getItems({
|
||||||
match: function (item) {
|
match: function (item) {
|
||||||
return blob.isMergeable(lastPath, item) && blob.touches(lastPath, item);
|
return blob.isMergeable(lastPath, item) && blob.touches(lastPath, item);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
// Broadbrush based on http://paperjs.org/tutorials/interaction/working-with-mouse-vectors/
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {stylePath} from './style-path';
|
import {stylePath} from '../../helper/style-path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broad brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
* Broad brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {stylePath} from './style-path';
|
import {stylePath} from '../../helper/style-path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Segment brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
* Segment brush functions to add as listeners on the mouse. Call them when the corresponding mouse event happens
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
const stylePath = function (path, options) {
|
|
||||||
if (options.isEraser) {
|
|
||||||
path.fillColor = 'white';
|
|
||||||
} else {
|
|
||||||
path.fillColor = options.fillColor;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const styleCursorPreview = function (path, options) {
|
|
||||||
if (options.isEraser) {
|
|
||||||
path.fillColor = 'white';
|
|
||||||
path.strokeColor = 'cornflowerblue';
|
|
||||||
path.strokeWidth = 1;
|
|
||||||
} else {
|
|
||||||
path.fillColor = options.fillColor;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
stylePath,
|
|
||||||
styleCursorPreview
|
|
||||||
};
|
|
|
@ -6,6 +6,7 @@ import Modes from '../modes/modes';
|
||||||
import Blobbiness from './blob/blob';
|
import Blobbiness from './blob/blob';
|
||||||
import {changeBrushSize} from '../reducers/brush-mode';
|
import {changeBrushSize} from '../reducers/brush-mode';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
|
import {clearSelectedItems} from '../reducers/selected-items';
|
||||||
import {clearSelection} from '../helper/selection';
|
import {clearSelection} from '../helper/selection';
|
||||||
import BrushModeComponent from '../components/brush-mode.jsx';
|
import BrushModeComponent from '../components/brush-mode.jsx';
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ class BrushMode extends React.Component {
|
||||||
'deactivateTool',
|
'deactivateTool',
|
||||||
'onScroll'
|
'onScroll'
|
||||||
]);
|
]);
|
||||||
this.blob = new Blobbiness(this.props.onUpdateSvg);
|
this.blob = new Blobbiness(this.props.onUpdateSvg, this.props.clearSelectedItems);
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isBrushModeActive) {
|
if (this.props.isBrushModeActive) {
|
||||||
|
@ -43,7 +44,7 @@ class BrushMode extends React.Component {
|
||||||
activateTool () {
|
activateTool () {
|
||||||
// TODO: Instead of clearing selection, consider a kind of "draw inside"
|
// TODO: Instead of clearing selection, consider a kind of "draw inside"
|
||||||
// analogous to how selection works with eraser
|
// analogous to how selection works with eraser
|
||||||
clearSelection();
|
clearSelection(this.props.clearSelectedItems);
|
||||||
|
|
||||||
// TODO: This is temporary until a component that provides the brush size is hooked up
|
// TODO: This is temporary until a component that provides the brush size is hooked up
|
||||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||||
|
@ -78,10 +79,11 @@ BrushMode.propTypes = {
|
||||||
}),
|
}),
|
||||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||||
changeBrushSize: PropTypes.func.isRequired,
|
changeBrushSize: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
colorState: PropTypes.shape({
|
colorState: PropTypes.shape({
|
||||||
fillColor: PropTypes.string.isRequired,
|
fillColor: PropTypes.string,
|
||||||
strokeColor: PropTypes.string.isRequired,
|
strokeColor: PropTypes.string,
|
||||||
strokeWidth: PropTypes.number.isRequired
|
strokeWidth: PropTypes.number
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
isBrushModeActive: PropTypes.bool.isRequired,
|
isBrushModeActive: PropTypes.bool.isRequired,
|
||||||
|
@ -94,6 +96,9 @@ const mapStateToProps = state => ({
|
||||||
isBrushModeActive: state.scratchPaint.mode === Modes.BRUSH
|
isBrushModeActive: state.scratchPaint.mode === Modes.BRUSH
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
changeBrushSize: brushSize => {
|
changeBrushSize: brushSize => {
|
||||||
dispatch(changeBrushSize(brushSize));
|
dispatch(changeBrushSize(brushSize));
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,6 +5,7 @@ import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
import Blobbiness from './blob/blob';
|
import Blobbiness from './blob/blob';
|
||||||
import {changeBrushSize} from '../reducers/eraser-mode';
|
import {changeBrushSize} from '../reducers/eraser-mode';
|
||||||
|
import {clearSelectedItems} from '../reducers/selected-items';
|
||||||
import EraserModeComponent from '../components/eraser-mode.jsx';
|
import EraserModeComponent from '../components/eraser-mode.jsx';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ class EraserMode extends React.Component {
|
||||||
'deactivateTool',
|
'deactivateTool',
|
||||||
'onScroll'
|
'onScroll'
|
||||||
]);
|
]);
|
||||||
this.blob = new Blobbiness(this.props.onUpdateSvg);
|
this.blob = new Blobbiness(this.props.onUpdateSvg, this.props.clearSelectedItems);
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.isEraserModeActive) {
|
if (this.props.isEraserModeActive) {
|
||||||
|
@ -65,6 +66,7 @@ class EraserMode extends React.Component {
|
||||||
EraserMode.propTypes = {
|
EraserMode.propTypes = {
|
||||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||||
changeBrushSize: PropTypes.func.isRequired,
|
changeBrushSize: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
eraserModeState: PropTypes.shape({
|
eraserModeState: PropTypes.shape({
|
||||||
brushSize: PropTypes.number.isRequired
|
brushSize: PropTypes.number.isRequired
|
||||||
}),
|
}),
|
||||||
|
@ -78,6 +80,9 @@ const mapStateToProps = state => ({
|
||||||
isEraserModeActive: state.scratchPaint.mode === Modes.ERASER
|
isEraserModeActive: state.scratchPaint.mode === Modes.ERASER
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
changeBrushSize: brushSize => {
|
changeBrushSize: brushSize => {
|
||||||
dispatch(changeBrushSize(brushSize));
|
dispatch(changeBrushSize(brushSize));
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {changeFillColor} from '../reducers/fill-color';
|
import {changeFillColor} from '../reducers/fill-color';
|
||||||
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
|
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
|
||||||
|
import {applyFillColorToSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
fillColor: state.scratchPaint.color.fillColor
|
fillColor: state.scratchPaint.color.fillColor
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
onChangeFillColor: fillColor => {
|
onChangeFillColor: fillColor => {
|
||||||
|
applyFillColorToSelection(fillColor);
|
||||||
dispatch(changeFillColor(fillColor));
|
dispatch(changeFillColor(fillColor));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,9 @@ import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
import {changeStrokeWidth} from '../reducers/stroke-width';
|
||||||
import {clearSelection} from '../helper/selection';
|
import {clearSelection, getSelectedLeafItems} from '../helper/selection';
|
||||||
|
import {MIXED} from '../helper/style-path';
|
||||||
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import LineModeComponent from '../components/line-mode.jsx';
|
import LineModeComponent from '../components/line-mode.jsx';
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
|
@ -43,7 +45,7 @@ class LineMode extends React.Component {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
clearSelection();
|
clearSelection(this.props.clearSelectedItems);
|
||||||
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
this.props.canvas.addEventListener('mousewheel', this.onScroll);
|
||||||
this.tool = new paper.Tool();
|
this.tool = new paper.Tool();
|
||||||
|
|
||||||
|
@ -93,9 +95,12 @@ class LineMode extends React.Component {
|
||||||
if (!this.path) {
|
if (!this.path) {
|
||||||
this.path = new paper.Path();
|
this.path = new paper.Path();
|
||||||
|
|
||||||
this.path.setStrokeColor(this.props.colorState.strokeColor);
|
this.path.setStrokeColor(
|
||||||
|
this.props.colorState.strokeColor === MIXED ? 'black' : this.props.colorState.strokeColor);
|
||||||
// Make sure a visible line is drawn
|
// Make sure a visible line is drawn
|
||||||
this.path.setStrokeWidth(Math.max(1, this.props.colorState.strokeWidth));
|
this.path.setStrokeWidth(
|
||||||
|
this.props.colorState.strokeWidth === null || this.props.colorState.strokeWidth === 0 ?
|
||||||
|
1 : this.props.colorState.strokeWidth);
|
||||||
|
|
||||||
this.path.setSelected(true);
|
this.path.setSelected(true);
|
||||||
this.path.add(event.point);
|
this.path.add(event.point);
|
||||||
|
@ -202,6 +207,7 @@ class LineMode extends React.Component {
|
||||||
this.hitResult = null;
|
this.hitResult = null;
|
||||||
}
|
}
|
||||||
this.props.onUpdateSvg();
|
this.props.onUpdateSvg();
|
||||||
|
this.props.setSelectedItems();
|
||||||
|
|
||||||
// TODO add back undo
|
// TODO add back undo
|
||||||
// if (this.path) {
|
// if (this.path) {
|
||||||
|
@ -269,14 +275,16 @@ class LineMode extends React.Component {
|
||||||
LineMode.propTypes = {
|
LineMode.propTypes = {
|
||||||
canvas: PropTypes.instanceOf(Element).isRequired,
|
canvas: PropTypes.instanceOf(Element).isRequired,
|
||||||
changeStrokeWidth: PropTypes.func.isRequired,
|
changeStrokeWidth: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
colorState: PropTypes.shape({
|
colorState: PropTypes.shape({
|
||||||
fillColor: PropTypes.string.isRequired,
|
fillColor: PropTypes.string,
|
||||||
strokeColor: PropTypes.string.isRequired,
|
strokeColor: PropTypes.string,
|
||||||
strokeWidth: PropTypes.number.isRequired
|
strokeWidth: PropTypes.number
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
isLineModeActive: PropTypes.bool.isRequired,
|
isLineModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -287,6 +295,12 @@ const mapDispatchToProps = dispatch => ({
|
||||||
changeStrokeWidth: strokeWidth => {
|
changeStrokeWidth: strokeWidth => {
|
||||||
dispatch(changeStrokeWidth(strokeWidth));
|
dispatch(changeStrokeWidth(strokeWidth));
|
||||||
},
|
},
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
|
setSelectedItems: () => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems()));
|
||||||
|
},
|
||||||
handleMouseDown: () => {
|
handleMouseDown: () => {
|
||||||
dispatch(changeMode(Modes.LINE));
|
dispatch(changeMode(Modes.LINE));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
|
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
|
||||||
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
|
|
||||||
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
import ReshapeTool from '../helper/selection-tools/reshape-tool';
|
||||||
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
import ReshapeModeComponent from '../components/reshape-mode.jsx';
|
||||||
|
@ -38,7 +40,12 @@ class ReshapeMode extends React.Component {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
this.tool = new ReshapeTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
this.tool = new ReshapeTool(
|
||||||
|
this.props.setHoveredItem,
|
||||||
|
this.props.clearHoveredItem,
|
||||||
|
this.props.setSelectedItems,
|
||||||
|
this.props.clearSelectedItems,
|
||||||
|
this.props.onUpdateSvg);
|
||||||
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
|
this.tool.setPrevHoveredItemId(this.props.hoveredItemId);
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
|
@ -57,11 +64,13 @@ class ReshapeMode extends React.Component {
|
||||||
|
|
||||||
ReshapeMode.propTypes = {
|
ReshapeMode.propTypes = {
|
||||||
clearHoveredItem: PropTypes.func.isRequired,
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
hoveredItemId: PropTypes.number,
|
hoveredItemId: PropTypes.number,
|
||||||
isReshapeModeActive: PropTypes.bool.isRequired,
|
isReshapeModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired,
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
setHoveredItem: PropTypes.func.isRequired
|
setHoveredItem: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -75,6 +84,12 @@ const mapDispatchToProps = dispatch => ({
|
||||||
clearHoveredItem: () => {
|
clearHoveredItem: () => {
|
||||||
dispatch(clearHoveredItem());
|
dispatch(clearHoveredItem());
|
||||||
},
|
},
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
|
setSelectedItems: () => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems()));
|
||||||
|
},
|
||||||
handleMouseDown: () => {
|
handleMouseDown: () => {
|
||||||
dispatch(changeMode(Modes.RESHAPE));
|
dispatch(changeMode(Modes.RESHAPE));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,10 @@ import bindAll from 'lodash.bindall';
|
||||||
import Modes from '../modes/modes';
|
import Modes from '../modes/modes';
|
||||||
|
|
||||||
import {changeMode} from '../reducers/modes';
|
import {changeMode} from '../reducers/modes';
|
||||||
import {setHoveredItem, clearHoveredItem} from '../reducers/hover';
|
import {clearHoveredItem, setHoveredItem} from '../reducers/hover';
|
||||||
|
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
|
|
||||||
|
import {getSelectedLeafItems} from '../helper/selection';
|
||||||
import SelectTool from '../helper/selection-tools/select-tool';
|
import SelectTool from '../helper/selection-tools/select-tool';
|
||||||
import SelectModeComponent from '../components/select-mode.jsx';
|
import SelectModeComponent from '../components/select-mode.jsx';
|
||||||
|
|
||||||
|
@ -38,7 +40,12 @@ class SelectMode extends React.Component {
|
||||||
return false; // Static component, for now
|
return false; // Static component, for now
|
||||||
}
|
}
|
||||||
activateTool () {
|
activateTool () {
|
||||||
this.tool = new SelectTool(this.props.setHoveredItem, this.props.clearHoveredItem, this.props.onUpdateSvg);
|
this.tool = new SelectTool(
|
||||||
|
this.props.setHoveredItem,
|
||||||
|
this.props.clearHoveredItem,
|
||||||
|
this.props.setSelectedItems,
|
||||||
|
this.props.clearSelectedItems,
|
||||||
|
this.props.onUpdateSvg);
|
||||||
this.tool.activate();
|
this.tool.activate();
|
||||||
}
|
}
|
||||||
deactivateTool () {
|
deactivateTool () {
|
||||||
|
@ -55,11 +62,13 @@ class SelectMode extends React.Component {
|
||||||
|
|
||||||
SelectMode.propTypes = {
|
SelectMode.propTypes = {
|
||||||
clearHoveredItem: PropTypes.func.isRequired,
|
clearHoveredItem: PropTypes.func.isRequired,
|
||||||
|
clearSelectedItems: PropTypes.func.isRequired,
|
||||||
handleMouseDown: PropTypes.func.isRequired,
|
handleMouseDown: PropTypes.func.isRequired,
|
||||||
hoveredItemId: PropTypes.number,
|
hoveredItemId: PropTypes.number,
|
||||||
isSelectModeActive: PropTypes.bool.isRequired,
|
isSelectModeActive: PropTypes.bool.isRequired,
|
||||||
onUpdateSvg: PropTypes.func.isRequired,
|
onUpdateSvg: PropTypes.func.isRequired,
|
||||||
setHoveredItem: PropTypes.func.isRequired
|
setHoveredItem: PropTypes.func.isRequired,
|
||||||
|
setSelectedItems: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -73,6 +82,12 @@ const mapDispatchToProps = dispatch => ({
|
||||||
clearHoveredItem: () => {
|
clearHoveredItem: () => {
|
||||||
dispatch(clearHoveredItem());
|
dispatch(clearHoveredItem());
|
||||||
},
|
},
|
||||||
|
clearSelectedItems: () => {
|
||||||
|
dispatch(clearSelectedItems());
|
||||||
|
},
|
||||||
|
setSelectedItems: () => {
|
||||||
|
dispatch(setSelectedItems(getSelectedLeafItems()));
|
||||||
|
},
|
||||||
handleMouseDown: () => {
|
handleMouseDown: () => {
|
||||||
dispatch(changeMode(Modes.SELECT));
|
dispatch(changeMode(Modes.SELECT));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import paper from 'paper';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import bindAll from 'lodash.bindall';
|
import bindAll from 'lodash.bindall';
|
||||||
import paper from 'paper';
|
|
||||||
|
|
||||||
const SelectionHOC = function (WrappedComponent) {
|
const SelectionHOC = function (WrappedComponent) {
|
||||||
class SelectionComponent extends React.Component {
|
class SelectionComponent extends React.Component {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {changeStrokeColor} from '../reducers/stroke-color';
|
import {changeStrokeColor} from '../reducers/stroke-color';
|
||||||
import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
|
import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
|
||||||
|
import {applyStrokeColorToSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
strokeColor: state.scratchPaint.color.strokeColor
|
strokeColor: state.scratchPaint.color.strokeColor
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
onChangeStrokeColor: strokeColor => {
|
onChangeStrokeColor: strokeColor => {
|
||||||
|
applyStrokeColorToSelection(strokeColor);
|
||||||
dispatch(changeStrokeColor(strokeColor));
|
dispatch(changeStrokeColor(strokeColor));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {changeStrokeWidth} from '../reducers/stroke-width';
|
import {changeStrokeWidth} from '../reducers/stroke-width';
|
||||||
import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
|
import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
|
||||||
|
import {applyStrokeWidthToSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
strokeWidth: state.scratchPaint.color.strokeWidth
|
strokeWidth: state.scratchPaint.color.strokeWidth
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
onChangeStrokeWidth: strokeWidth => {
|
onChangeStrokeWidth: strokeWidth => {
|
||||||
|
applyStrokeWidthToSelection(strokeWidth);
|
||||||
dispatch(changeStrokeWidth(strokeWidth));
|
dispatch(changeStrokeWidth(strokeWidth));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {getRootItem, isGroupItem} from './item';
|
import {getRootItem, isGroupItem} from './item';
|
||||||
import {clearSelection, getSelectedItems, setItemSelection} from './selection';
|
import {clearSelection, getSelectedRootItems, setItemSelection} from './selection';
|
||||||
|
|
||||||
const isGroup = function (item) {
|
const isGroup = function (item) {
|
||||||
return isGroupItem(item);
|
return isGroupItem(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupSelection = function () {
|
const groupSelection = function (clearSelectedItems) {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedRootItems();
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
const group = new paper.Group(items);
|
const group = new paper.Group(items);
|
||||||
clearSelection();
|
clearSelection(clearSelectedItems);
|
||||||
setItemSelection(group, true);
|
setItemSelection(group, true);
|
||||||
for (let i = 0; i < group.children.length; i++) {
|
for (let i = 0; i < group.children.length; i++) {
|
||||||
group.children[i].selected = true;
|
group.children[i].selected = true;
|
||||||
|
@ -47,8 +47,8 @@ const ungroupLoop = function (group, recursive) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ungroup items (only top hierarchy)
|
// ungroup items (only top hierarchy)
|
||||||
const ungroupItems = function (items) {
|
const ungroupItems = function (items, clearSelectedItems) {
|
||||||
clearSelection();
|
clearSelection(clearSelectedItems);
|
||||||
const emptyGroups = [];
|
const emptyGroups = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
|
@ -71,7 +71,7 @@ const ungroupItems = function (items) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ungroupSelection = function () {
|
const ungroupSelection = function () {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedRootItems();
|
||||||
ungroupItems(items);
|
ungroupItems(items);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,12 +102,12 @@ const isGroupChild = function (item) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowGroup = function () {
|
const shouldShowGroup = function () {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedRootItems();
|
||||||
return items.length > 1;
|
return items.length > 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowUngroup = function () {
|
const shouldShowUngroup = function () {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedRootItems();
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
if (isGroup(item) && !item.data.isPGTextItem && item.children && item.children.length > 0) {
|
if (isGroup(item) && !item.data.isPGTextItem && item.children && item.children.length > 0) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {getGuideLayer} from './layer';
|
import {getGuideLayer} from './layer';
|
||||||
import {getAllPaperItems} from './selection';
|
import {getAllRootItems} from './selection';
|
||||||
|
|
||||||
const GUIDE_BLUE = '#009dec';
|
const GUIDE_BLUE = '#009dec';
|
||||||
const GUIDE_GREY = '#aaaaaa';
|
const GUIDE_GREY = '#aaaaaa';
|
||||||
|
@ -66,7 +66,7 @@ const getGuideColor = function (colorName) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _removePaperItemsByDataTags = function (tags) {
|
const _removePaperItemsByDataTags = function (tags) {
|
||||||
const allItems = getAllPaperItems(true);
|
const allItems = getAllRootItems(true);
|
||||||
for (const item of allItems) {
|
for (const item of allItems) {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (item.data && item.data[tag]) {
|
if (item.data && item.data[tag]) {
|
||||||
|
@ -77,7 +77,7 @@ const _removePaperItemsByDataTags = function (tags) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _removePaperItemsByTags = function (tags) {
|
const _removePaperItemsByTags = function (tags) {
|
||||||
const allItems = getAllPaperItems(true);
|
const allItems = getAllRootItems(true);
|
||||||
for (const item of allItems) {
|
for (const item of allItems) {
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
if (item[tag]) {
|
if (item[tag]) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import keyMirror from 'keymirror';
|
import keyMirror from 'keymirror';
|
||||||
|
|
||||||
import {clearSelection, getSelectedItems} from '../selection';
|
import {getSelectedRootItems} from '../selection';
|
||||||
import {getGuideColor, removeHelperItems} from '../guides';
|
import {getGuideColor, removeHelperItems} from '../guides';
|
||||||
import {getGuideLayer} from '../layer';
|
import {getGuideLayer} from '../layer';
|
||||||
|
|
||||||
|
@ -31,7 +31,12 @@ const Modes = keyMirror({
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
class BoundingBoxTool {
|
class BoundingBoxTool {
|
||||||
constructor (onUpdateSvg) {
|
/**
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
|
*/
|
||||||
|
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
this.onUpdateSvg = onUpdateSvg;
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
this.mode = null;
|
this.mode = null;
|
||||||
this.boundsPath = null;
|
this.boundsPath = null;
|
||||||
|
@ -40,7 +45,7 @@ class BoundingBoxTool {
|
||||||
this._modeMap = {};
|
this._modeMap = {};
|
||||||
this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg);
|
this._modeMap[Modes.SCALE] = new ScaleTool(onUpdateSvg);
|
||||||
this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg);
|
this._modeMap[Modes.ROTATE] = new RotateTool(onUpdateSvg);
|
||||||
this._modeMap[Modes.MOVE] = new MoveTool(onUpdateSvg);
|
this._modeMap[Modes.MOVE] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +61,6 @@ class BoundingBoxTool {
|
||||||
if (!hitResults || hitResults.length === 0) {
|
if (!hitResults || hitResults.length === 0) {
|
||||||
if (!multiselect) {
|
if (!multiselect) {
|
||||||
this.removeBoundsPath();
|
this.removeBoundsPath();
|
||||||
clearSelection();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -86,9 +90,9 @@ class BoundingBoxTool {
|
||||||
this._modeMap[this.mode].onMouseDown(hitProperties);
|
this._modeMap[this.mode].onMouseDown(hitProperties);
|
||||||
} else if (this.mode === Modes.SCALE) {
|
} else if (this.mode === Modes.SCALE) {
|
||||||
this._modeMap[this.mode].onMouseDown(
|
this._modeMap[this.mode].onMouseDown(
|
||||||
hitResult, this.boundsPath, this.boundsScaleHandles, this.boundsRotHandles, getSelectedItems());
|
hitResult, this.boundsPath, this.boundsScaleHandles, this.boundsRotHandles, getSelectedRootItems());
|
||||||
} else if (this.mode === Modes.ROTATE) {
|
} else if (this.mode === Modes.ROTATE) {
|
||||||
this._modeMap[this.mode].onMouseDown(hitResult, this.boundsPath, getSelectedItems());
|
this._modeMap[this.mode].onMouseDown(hitResult, this.boundsPath, getSelectedRootItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
// while transforming object, never show the bounds stuff
|
// while transforming object, never show the bounds stuff
|
||||||
|
@ -109,7 +113,7 @@ class BoundingBoxTool {
|
||||||
setSelectionBounds () {
|
setSelectionBounds () {
|
||||||
this.removeBoundsPath();
|
this.removeBoundsPath();
|
||||||
|
|
||||||
const items = getSelectedItems(true /* recursive */);
|
const items = getSelectedRootItems();
|
||||||
if (items.length <= 0) return;
|
if (items.length <= 0) return;
|
||||||
|
|
||||||
let rect = null;
|
let rect = null;
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import {clearSelection, getSelectedItems} from '../selection';
|
import {clearSelection, getSelectedLeafItems} from '../selection';
|
||||||
|
|
||||||
/** Sub tool of the Reshape tool for moving handles, which adjust bezier curves. */
|
/** Sub tool of the Reshape tool for moving handles, which adjust bezier curves. */
|
||||||
class HandleTool {
|
class HandleTool {
|
||||||
/**
|
/**
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (onUpdateSvg) {
|
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
this.hitType = null;
|
this.hitType = null;
|
||||||
|
this.setSelectedItems = setSelectedItems;
|
||||||
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
this.onUpdateSvg = onUpdateSvg;
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +20,7 @@ class HandleTool {
|
||||||
*/
|
*/
|
||||||
onMouseDown (hitProperties) {
|
onMouseDown (hitProperties) {
|
||||||
if (!hitProperties.multiselect) {
|
if (!hitProperties.multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
hitProperties.hitResult.segment.handleIn.selected = true;
|
hitProperties.hitResult.segment.handleIn.selected = true;
|
||||||
|
@ -24,7 +28,7 @@ class HandleTool {
|
||||||
this.hitType = hitProperties.hitResult.type;
|
this.hitType = hitProperties.hitResult.type;
|
||||||
}
|
}
|
||||||
onMouseDrag (event) {
|
onMouseDrag (event) {
|
||||||
const selectedItems = getSelectedItems(true /* recursive */);
|
const selectedItems = getSelectedLeafItems();
|
||||||
|
|
||||||
for (const item of selectedItems) {
|
for (const item of selectedItems) {
|
||||||
for (const seg of item.segments) {
|
for (const seg of item.segments) {
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import {isGroup} from '../group';
|
import {isGroup} from '../group';
|
||||||
import {isCompoundPathItem, getRootItem} from '../item';
|
import {isCompoundPathItem, getRootItem} from '../item';
|
||||||
import {snapDeltaToAngle} from '../math';
|
import {snapDeltaToAngle} from '../math';
|
||||||
import {clearSelection, cloneSelection, getSelectedItems, setItemSelection} from '../selection';
|
import {clearSelection, cloneSelection, getSelectedLeafItems, setItemSelection} from '../selection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tool to handle dragging an item to reposition it in a selection mode.
|
* Tool to handle dragging an item to reposition it in a selection mode.
|
||||||
*/
|
*/
|
||||||
class MoveTool {
|
class MoveTool {
|
||||||
/**
|
/**
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (onUpdateSvg) {
|
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
|
this.setSelectedItems = setSelectedItems;
|
||||||
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
this.onUpdateSvg = onUpdateSvg;
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +38,7 @@ class MoveTool {
|
||||||
// Double click causes all points to be selected in subselect mode.
|
// Double click causes all points to be selected in subselect mode.
|
||||||
if (hitProperties.doubleClicked) {
|
if (hitProperties.doubleClicked) {
|
||||||
if (!hitProperties.multiselect) {
|
if (!hitProperties.multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
}
|
}
|
||||||
this._select(item, true /* state */, hitProperties.subselect, true /* fullySelect */);
|
this._select(item, true /* state */, hitProperties.subselect, true /* fullySelect */);
|
||||||
} else if (hitProperties.multiselect) {
|
} else if (hitProperties.multiselect) {
|
||||||
|
@ -43,12 +47,12 @@ class MoveTool {
|
||||||
} else {
|
} else {
|
||||||
// deselect all by default if multiselect isn't on
|
// deselect all by default if multiselect isn't on
|
||||||
if (!hitProperties.multiselect) {
|
if (!hitProperties.multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
}
|
}
|
||||||
this._select(item, true, hitProperties.subselect);
|
this._select(item, true, hitProperties.subselect);
|
||||||
}
|
}
|
||||||
if (hitProperties.clone) cloneSelection(hitProperties.subselect);
|
if (hitProperties.clone) cloneSelection(hitProperties.subselect);
|
||||||
this.selectedItems = getSelectedItems(true /* subselect */);
|
this.selectedItems = getSelectedLeafItems();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sets the selection state of an item.
|
* Sets the selection state of an item.
|
||||||
|
@ -71,6 +75,7 @@ class MoveTool {
|
||||||
} else {
|
} else {
|
||||||
setItemSelection(item, state);
|
setItemSelection(item, state);
|
||||||
}
|
}
|
||||||
|
this.setSelectedItems();
|
||||||
}
|
}
|
||||||
onMouseDrag (event) {
|
onMouseDrag (event) {
|
||||||
const dragVector = event.point.subtract(event.downPoint);
|
const dragVector = event.point.subtract(event.downPoint);
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import paper from 'paper';
|
import paper from 'paper';
|
||||||
import {snapDeltaToAngle} from '../math';
|
import {snapDeltaToAngle} from '../math';
|
||||||
import {clearSelection, getSelectedItems} from '../selection';
|
import {clearSelection, getSelectedLeafItems} from '../selection';
|
||||||
|
|
||||||
/** Subtool of ReshapeTool for moving control points. */
|
/** Subtool of ReshapeTool for moving control points. */
|
||||||
class PointTool {
|
class PointTool {
|
||||||
/**
|
/**
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (onUpdateSvg) {
|
constructor (setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
/**
|
/**
|
||||||
* Deselection often does not happen until mouse up. If the mouse is dragged before
|
* Deselection often does not happen until mouse up. If the mouse is dragged before
|
||||||
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
|
* mouse up, deselection is cancelled. This variable keeps track of which paper.Item to deselect.
|
||||||
|
@ -24,6 +26,8 @@ class PointTool {
|
||||||
*/
|
*/
|
||||||
this.invertDeselect = false;
|
this.invertDeselect = false;
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.setSelectedItems = setSelectedItems;
|
||||||
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
this.onUpdateSvg = onUpdateSvg;
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,12 +53,12 @@ class PointTool {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!hitProperties.multiselect) {
|
if (!hitProperties.multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
}
|
}
|
||||||
hitProperties.hitResult.segment.selected = true;
|
hitProperties.hitResult.segment.selected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedItems = getSelectedItems(true /* recursive */);
|
this.selectedItems = getSelectedLeafItems();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {!object} hitProperties Describes the mouse event
|
* @param {!object} hitProperties Describes the mouse event
|
||||||
|
@ -86,7 +90,7 @@ class PointTool {
|
||||||
hitProperties.hitResult.item.insert(hitProperties.hitResult.location.index + 1, newSegment);
|
hitProperties.hitResult.item.insert(hitProperties.hitResult.location.index + 1, newSegment);
|
||||||
hitProperties.hitResult.segment = newSegment;
|
hitProperties.hitResult.segment = newSegment;
|
||||||
if (!hitProperties.multiselect) {
|
if (!hitProperties.multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
}
|
}
|
||||||
newSegment.selected = true;
|
newSegment.selected = true;
|
||||||
|
|
||||||
|
@ -175,7 +179,7 @@ class PointTool {
|
||||||
// and delete
|
// and delete
|
||||||
if (this.deselectOnMouseUp) {
|
if (this.deselectOnMouseUp) {
|
||||||
if (this.invertDeselect) {
|
if (this.invertDeselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
this.deselectOnMouseUp.selected = true;
|
this.deselectOnMouseUp.selected = true;
|
||||||
} else {
|
} else {
|
||||||
this.deselectOnMouseUp.selected = false;
|
this.deselectOnMouseUp.selected = false;
|
||||||
|
@ -188,6 +192,7 @@ class PointTool {
|
||||||
this.deleteOnMouseUp = null;
|
this.deleteOnMouseUp = null;
|
||||||
}
|
}
|
||||||
this.selectedItems = null;
|
this.selectedItems = null;
|
||||||
|
this.setSelectedItems();
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
this.onUpdateSvg();
|
this.onUpdateSvg();
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,11 @@ class ReshapeTool extends paper.Tool {
|
||||||
/**
|
/**
|
||||||
* @param {function} setHoveredItem Callback to set the hovered item
|
* @param {function} setHoveredItem Callback to set the hovered item
|
||||||
* @param {function} clearHoveredItem Callback to clear the hovered item
|
* @param {function} clearHoveredItem Callback to clear the hovered item
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
super();
|
super();
|
||||||
this.setHoveredItem = setHoveredItem;
|
this.setHoveredItem = setHoveredItem;
|
||||||
this.clearHoveredItem = clearHoveredItem;
|
this.clearHoveredItem = clearHoveredItem;
|
||||||
|
@ -52,10 +54,11 @@ class ReshapeTool extends paper.Tool {
|
||||||
this.lastEvent = null;
|
this.lastEvent = null;
|
||||||
this.mode = ReshapeModes.SELECTION_BOX;
|
this.mode = ReshapeModes.SELECTION_BOX;
|
||||||
this._modeMap = {};
|
this._modeMap = {};
|
||||||
this._modeMap[ReshapeModes.FILL] = new MoveTool(onUpdateSvg);
|
this._modeMap[ReshapeModes.FILL] = new MoveTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.POINT] = new PointTool(onUpdateSvg);
|
this._modeMap[ReshapeModes.POINT] = new PointTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(onUpdateSvg);
|
this._modeMap[ReshapeModes.HANDLE] = new HandleTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||||
this._modeMap[ReshapeModes.SELECTION_BOX] = new SelectionBoxTool(Modes.RESHAPE);
|
this._modeMap[ReshapeModes.SELECTION_BOX] =
|
||||||
|
new SelectionBoxTool(Modes.RESHAPE, setSelectedItems, clearSelectedItems);
|
||||||
|
|
||||||
// We have to set these functions instead of just declaring them because
|
// We have to set these functions instead of just declaring them because
|
||||||
// paper.js tools hook up the listeners in the setter functions.
|
// paper.js tools hook up the listeners in the setter functions.
|
||||||
|
|
|
@ -21,15 +21,17 @@ class SelectTool extends paper.Tool {
|
||||||
/**
|
/**
|
||||||
* @param {function} setHoveredItem Callback to set the hovered item
|
* @param {function} setHoveredItem Callback to set the hovered item
|
||||||
* @param {function} clearHoveredItem Callback to clear the hovered item
|
* @param {function} clearHoveredItem Callback to clear the hovered item
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
* @param {!function} onUpdateSvg A callback to call when the image visibly changes
|
||||||
*/
|
*/
|
||||||
constructor (setHoveredItem, clearHoveredItem, onUpdateSvg) {
|
constructor (setHoveredItem, clearHoveredItem, setSelectedItems, clearSelectedItems, onUpdateSvg) {
|
||||||
super();
|
super();
|
||||||
this.setHoveredItem = setHoveredItem;
|
this.setHoveredItem = setHoveredItem;
|
||||||
this.clearHoveredItem = clearHoveredItem;
|
this.clearHoveredItem = clearHoveredItem;
|
||||||
this.onUpdateSvg = onUpdateSvg;
|
this.onUpdateSvg = onUpdateSvg;
|
||||||
this.boundingBoxTool = new BoundingBoxTool(onUpdateSvg);
|
this.boundingBoxTool = new BoundingBoxTool(setSelectedItems, clearSelectedItems, onUpdateSvg);
|
||||||
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT);
|
this.selectionBoxTool = new SelectionBoxTool(Modes.SELECT, setSelectedItems, clearSelectedItems);
|
||||||
this.selectionBoxMode = false;
|
this.selectionBoxMode = false;
|
||||||
this.prevHoveredItemId = null;
|
this.prevHoveredItemId = null;
|
||||||
|
|
||||||
|
@ -42,6 +44,7 @@ class SelectTool extends paper.Tool {
|
||||||
this.onKeyUp = this.handleKeyUp;
|
this.onKeyUp = this.handleKeyUp;
|
||||||
|
|
||||||
selectRootItem();
|
selectRootItem();
|
||||||
|
setSelectedItems();
|
||||||
this.boundingBoxTool.setSelectionBounds();
|
this.boundingBoxTool.setSelectionBounds();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,16 +3,24 @@ import {clearSelection, processRectangularSelection} from '../selection';
|
||||||
|
|
||||||
/** Tool to handle drag selection. A dotted line box appears and everything enclosed is selected. */
|
/** Tool to handle drag selection. A dotted line box appears and everything enclosed is selected. */
|
||||||
class SelectionBoxTool {
|
class SelectionBoxTool {
|
||||||
constructor (mode) {
|
/**
|
||||||
|
* @param {!Modes} mode Current paint editor mode
|
||||||
|
* @param {function} setSelectedItems Callback to set the set of selected items in the Redux state
|
||||||
|
* @param {function} clearSelectedItems Callback to clear the set of selected items in the Redux state
|
||||||
|
*/
|
||||||
|
constructor (mode, setSelectedItems, clearSelectedItems) {
|
||||||
this.selectionRect = null;
|
this.selectionRect = null;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
|
this.setSelectedItems = setSelectedItems;
|
||||||
|
this.clearSelectedItems = clearSelectedItems;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
* @param {boolean} multiselect Whether to multiselect on mouse down (e.g. shift key held)
|
||||||
*/
|
*/
|
||||||
onMouseDown (multiselect) {
|
onMouseDown (multiselect) {
|
||||||
if (!multiselect) {
|
if (!multiselect) {
|
||||||
clearSelection();
|
clearSelection(this.clearSelectedItems);
|
||||||
|
this.clearSelectedItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseDrag (event) {
|
onMouseDrag (event) {
|
||||||
|
@ -25,6 +33,7 @@ class SelectionBoxTool {
|
||||||
processRectangularSelection(event, this.selectionRect, this.mode);
|
processRectangularSelection(event, this.selectionRect, this.mode);
|
||||||
this.selectionRect.remove();
|
this.selectionRect.remove();
|
||||||
this.selectionRect = null;
|
this.selectionRect = null;
|
||||||
|
this.setSelectedItems();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {getItemsCompoundPath, isCompoundPath, isCompoundPathChild} from './compo
|
||||||
* be included in the returned items.
|
* be included in the returned items.
|
||||||
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
||||||
*/
|
*/
|
||||||
const getAllPaperItems = function (includeGuides) {
|
const getAllRootItems = function (includeGuides) {
|
||||||
includeGuides = includeGuides || false;
|
includeGuides = includeGuides || false;
|
||||||
const allItems = [];
|
const allItems = [];
|
||||||
for (const layer of paper.project.layers) {
|
for (const layer of paper.project.layers) {
|
||||||
|
@ -29,8 +29,8 @@ const getAllPaperItems = function (includeGuides) {
|
||||||
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
* @return {Array<paper.item>} all top-level (direct descendants of a paper.Layer) items
|
||||||
* that aren't guide items or helper items.
|
* that aren't guide items or helper items.
|
||||||
*/
|
*/
|
||||||
const getAllSelectableItems = function () {
|
const getAllSelectableRootItems = function () {
|
||||||
const allItems = getAllPaperItems();
|
const allItems = getAllRootItems();
|
||||||
const selectables = [];
|
const selectables = [];
|
||||||
for (let i = 0; i < allItems.length; i++) {
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
if (allItems[i].data && !allItems[i].data.isHelperItem) {
|
if (allItems[i].data && !allItems[i].data.isHelperItem) {
|
||||||
|
@ -97,7 +97,7 @@ const setItemSelection = function (item, state, fullySelected) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectAllItems = function () {
|
const selectAllItems = function () {
|
||||||
const items = getAllSelectableItems();
|
const items = getAllSelectableRootItems();
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
setItemSelection(items[i], true);
|
setItemSelection(items[i], true);
|
||||||
|
@ -105,51 +105,67 @@ const selectAllItems = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectAllSegments = function () {
|
const selectAllSegments = function () {
|
||||||
const items = getAllSelectableItems();
|
const items = getAllSelectableRootItems();
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
selectItemSegments(items[i], true);
|
selectItemSegments(items[i], true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearSelection = function () {
|
/** @param {!function} dispatchClearSelect Function to update the Redux select state */
|
||||||
|
const clearSelection = function (dispatchClearSelect) {
|
||||||
paper.project.deselectAll();
|
paper.project.deselectAll();
|
||||||
// @todo: Update toolbar state on change
|
// @todo: Update toolbar state on change
|
||||||
|
dispatchClearSelect();
|
||||||
};
|
};
|
||||||
|
|
||||||
// This gets all selected non-grouped items and groups
|
/**
|
||||||
// (alternative to paper.project.selectedItems, which includes
|
* This gets all selected non-grouped items and groups
|
||||||
// group children in addition to the group)
|
* (alternative to paper.project.selectedItems, which includes
|
||||||
// Returns in increasing Z order
|
* group children in addition to the group)
|
||||||
const getSelectedItems = function (recursive) {
|
* @return {Array<paper.Item>} in increasing Z order.
|
||||||
|
*/
|
||||||
|
const getSelectedRootItems = function () {
|
||||||
const allItems = paper.project.selectedItems;
|
const allItems = paper.project.selectedItems;
|
||||||
const itemsAndGroups = [];
|
const itemsAndGroups = [];
|
||||||
|
|
||||||
if (recursive) {
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
for (let i = 0; i < allItems.length; i++) {
|
const item = allItems[i];
|
||||||
const item = allItems[i];
|
if ((isGroup(item) && !isGroup(item.parent)) ||
|
||||||
|
!isGroup(item.parent)) {
|
||||||
if (item.data && !item.data.isSelectionBound) {
|
if (item.data && !item.data.isSelectionBound) {
|
||||||
itemsAndGroups.push(item);
|
itemsAndGroups.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
for (let i = 0; i < allItems.length; i++) {
|
|
||||||
const item = allItems[i];
|
|
||||||
if ((isGroup(item) && !isGroup(item.parent)) ||
|
|
||||||
!isGroup(item.parent)) {
|
|
||||||
if (item.data && !item.data.isSelectionBound) {
|
|
||||||
itemsAndGroups.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort items by index (0 at bottom)
|
// sort items by index (0 at bottom)
|
||||||
itemsAndGroups.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
|
itemsAndGroups.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
|
||||||
return itemsAndGroups;
|
return itemsAndGroups;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteItemSelection = function (recursive) {
|
/**
|
||||||
const items = getSelectedItems(recursive);
|
* This gets all selected items that are as deeply nested as possible. Does not
|
||||||
|
* return the parent groups.
|
||||||
|
* @return {Array<paper.Item>} in increasing Z order.
|
||||||
|
*/
|
||||||
|
const getSelectedLeafItems = function () {
|
||||||
|
const allItems = paper.project.selectedItems;
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
|
const item = allItems[i];
|
||||||
|
if (!isGroup(item) && item.data && !item.data.isSelectionBound) {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort items by index (0 at bottom)
|
||||||
|
items.sort((a, b) => parseFloat(a.index) - parseFloat(b.index));
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteItemSelection = function (items) {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
items[i].remove();
|
items[i].remove();
|
||||||
}
|
}
|
||||||
|
@ -160,11 +176,10 @@ const deleteItemSelection = function (recursive) {
|
||||||
// pg.undo.snapshot('deleteItemSelection');
|
// pg.undo.snapshot('deleteItemSelection');
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeSelectedSegments = function (recursive) {
|
const removeSelectedSegments = function (items) {
|
||||||
// @todo add back undo
|
// @todo add back undo
|
||||||
// pg.undo.snapshot('removeSelectedSegments');
|
// pg.undo.snapshot('removeSelectedSegments');
|
||||||
|
|
||||||
const items = getSelectedItems(recursive);
|
|
||||||
const segmentsToRemove = [];
|
const segmentsToRemove = [];
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
@ -188,12 +203,14 @@ const removeSelectedSegments = function (recursive) {
|
||||||
|
|
||||||
const deleteSelection = function (mode) {
|
const deleteSelection = function (mode) {
|
||||||
if (mode === Modes.RESHAPE) {
|
if (mode === Modes.RESHAPE) {
|
||||||
|
const selectedItems = getSelectedLeafItems();
|
||||||
// If there are points selected remove them. If not delete the item selected.
|
// If there are points selected remove them. If not delete the item selected.
|
||||||
if (!removeSelectedSegments(true /* recursive */)) {
|
if (!removeSelectedSegments(selectedItems)) {
|
||||||
deleteItemSelection(true /* recursive */);
|
deleteItemSelection(selectedItems);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deleteItemSelection();
|
const selectedItems = getSelectedRootItems();
|
||||||
|
deleteItemSelection(selectedItems);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -243,7 +260,7 @@ const splitPathRetainSelection = function (path, index, deselectSplitSegments) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const splitPathAtSelectedSegments = function () {
|
const splitPathAtSelectedSegments = function () {
|
||||||
const items = getSelectedItems();
|
const items = getSelectedRootItems();
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
const segments = item.segments;
|
const segments = item.segments;
|
||||||
|
@ -299,9 +316,7 @@ const deleteSegments = function (item) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSegmentSelection = function () {
|
const deleteSegmentSelection = function (items) {
|
||||||
|
|
||||||
const items = getSelectedItems();
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
deleteSegments(items[i]);
|
deleteSegments(items[i]);
|
||||||
}
|
}
|
||||||
|
@ -313,7 +328,7 @@ const deleteSegmentSelection = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const cloneSelection = function (recursive) {
|
const cloneSelection = function (recursive) {
|
||||||
const selectedItems = getSelectedItems(recursive);
|
const selectedItems = recursive ? getSelectedLeafItems() : getSelectedRootItems();
|
||||||
for (let i = 0; i < selectedItems.length; i++) {
|
for (let i = 0; i < selectedItems.length; i++) {
|
||||||
const item = selectedItems[i];
|
const item = selectedItems[i];
|
||||||
item.clone();
|
item.clone();
|
||||||
|
@ -325,7 +340,7 @@ const cloneSelection = function (recursive) {
|
||||||
|
|
||||||
// Only returns paths, no compound paths, groups or any other stuff
|
// Only returns paths, no compound paths, groups or any other stuff
|
||||||
const getSelectedPaths = function () {
|
const getSelectedPaths = function () {
|
||||||
const allPaths = getSelectedItems();
|
const allPaths = getSelectedRootItems();
|
||||||
const paths = [];
|
const paths = [];
|
||||||
|
|
||||||
for (let i = 0; i < allPaths.length; i++) {
|
for (let i = 0; i < allPaths.length; i++) {
|
||||||
|
@ -456,7 +471,7 @@ const _rectangularSelectionGroupLoop = function (group, rect, root, event, mode)
|
||||||
* @param {Modes} mode The mode of the paint editor when drawing the rectangle
|
* @param {Modes} mode The mode of the paint editor when drawing the rectangle
|
||||||
*/
|
*/
|
||||||
const processRectangularSelection = function (event, rect, mode) {
|
const processRectangularSelection = function (event, rect, mode) {
|
||||||
const allItems = getAllSelectableItems();
|
const allItems = getAllSelectableRootItems();
|
||||||
|
|
||||||
for (let i = 0; i < allItems.length; i++) {
|
for (let i = 0; i < allItems.length; i++) {
|
||||||
const item = allItems[i];
|
const item = allItems[i];
|
||||||
|
@ -478,7 +493,7 @@ const processRectangularSelection = function (event, rect, mode) {
|
||||||
* instead. (otherwise the compound path breaks because of scale-grouping)
|
* instead. (otherwise the compound path breaks because of scale-grouping)
|
||||||
*/
|
*/
|
||||||
const selectRootItem = function () {
|
const selectRootItem = function () {
|
||||||
const items = getSelectedItems(true /* recursive */);
|
const items = getSelectedLeafItems();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (isCompoundPathChild(item)) {
|
if (isCompoundPathChild(item)) {
|
||||||
const cp = getItemsCompoundPath(item);
|
const cp = getItemsCompoundPath(item);
|
||||||
|
@ -492,11 +507,11 @@ const selectRootItem = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowIfSelection = function () {
|
const shouldShowIfSelection = function () {
|
||||||
return getSelectedItems().length > 0;
|
return getSelectedRootItems().length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowIfSelectionRecursive = function () {
|
const shouldShowIfSelectionRecursive = function () {
|
||||||
return getSelectedItems(true /* recursive */).length > 0;
|
return getSelectedRootItems().length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldShowSelectAll = function () {
|
const shouldShowSelectAll = function () {
|
||||||
|
@ -504,7 +519,7 @@ const shouldShowSelectAll = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAllPaperItems,
|
getAllRootItems,
|
||||||
selectAllItems,
|
selectAllItems,
|
||||||
selectAllSegments,
|
selectAllSegments,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
|
@ -515,8 +530,9 @@ export {
|
||||||
cloneSelection,
|
cloneSelection,
|
||||||
setItemSelection,
|
setItemSelection,
|
||||||
setGroupSelection,
|
setGroupSelection,
|
||||||
getSelectedItems,
|
getSelectedLeafItems,
|
||||||
getSelectedPaths,
|
getSelectedPaths,
|
||||||
|
getSelectedRootItems,
|
||||||
removeSelectedSegments,
|
removeSelectedSegments,
|
||||||
processRectangularSelection,
|
processRectangularSelection,
|
||||||
selectRootItem,
|
selectRootItem,
|
||||||
|
|
193
src/helper/style-path.js
Normal file
193
src/helper/style-path.js
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import {getSelectedLeafItems} from './selection';
|
||||||
|
import {isPGTextItem, isPointTextItem} from './item';
|
||||||
|
import {isGroup} from './group';
|
||||||
|
|
||||||
|
const MIXED = 'scratch-paint/style-path/mixed';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when setting fill color
|
||||||
|
* @param {string} colorString New color, css format
|
||||||
|
*/
|
||||||
|
const applyFillColorToSelection = function (colorString) {
|
||||||
|
const items = getSelectedLeafItems();
|
||||||
|
for (const item of items) {
|
||||||
|
if (isPGTextItem(item)) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.children) {
|
||||||
|
for (const path of child.children) {
|
||||||
|
if (!path.data.isPGGlyphRect) {
|
||||||
|
path.fillColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!child.data.isPGGlyphRect) {
|
||||||
|
child.fillColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isPointTextItem(item) && !colorString) {
|
||||||
|
colorString = 'rgba(0,0,0,0)';
|
||||||
|
}
|
||||||
|
item.fillColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @todo add back undo
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when setting stroke color
|
||||||
|
* @param {string} colorString New color, css format
|
||||||
|
*/
|
||||||
|
const applyStrokeColorToSelection = function (colorString) {
|
||||||
|
const items = getSelectedLeafItems();
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (isPGTextItem(item)) {
|
||||||
|
if (item.children) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.children) {
|
||||||
|
for (const path of child.children) {
|
||||||
|
if (!path.data.isPGGlyphRect) {
|
||||||
|
path.strokeColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!child.data.isPGGlyphRect) {
|
||||||
|
child.strokeColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!item.data.isPGGlyphRect) {
|
||||||
|
item.strokeColor = colorString;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item.strokeColor = colorString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @todo add back undo
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when setting stroke width
|
||||||
|
* @param {number} value New stroke width
|
||||||
|
*/
|
||||||
|
const applyStrokeWidthToSelection = function (value) {
|
||||||
|
const items = getSelectedLeafItems();
|
||||||
|
for (const item of items) {
|
||||||
|
if (isGroup(item)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
item.strokeWidth = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @todo add back undo
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state of colors and stroke width for selection
|
||||||
|
* @param {!Array<paper.Item>} selectedItems Selected paper items
|
||||||
|
* @return {object} Object of strokeColor, strokeWidth, fillColor of the selection.
|
||||||
|
* Gives MIXED when there are mixed values for a color, and null for transparent.
|
||||||
|
* Gives null when there are mixed values for stroke width.
|
||||||
|
*/
|
||||||
|
const getColorsFromSelection = function (selectedItems) {
|
||||||
|
let selectionFillColorString;
|
||||||
|
let selectionStrokeColorString;
|
||||||
|
let selectionStrokeWidth;
|
||||||
|
let firstChild = true;
|
||||||
|
|
||||||
|
for (const item of selectedItems) {
|
||||||
|
let itemFillColorString;
|
||||||
|
let itemStrokeColorString;
|
||||||
|
|
||||||
|
// handle pgTextItems differently by going through their children
|
||||||
|
if (isPGTextItem(item)) {
|
||||||
|
for (const child of item.children) {
|
||||||
|
for (const path of child.children) {
|
||||||
|
if (!path.data.isPGGlyphRect) {
|
||||||
|
if (path.fillColor) {
|
||||||
|
itemFillColorString = path.fillColor.toCSS();
|
||||||
|
}
|
||||||
|
if (path.strokeColor) {
|
||||||
|
itemStrokeColorString = path.strokeColor.toCSS();
|
||||||
|
}
|
||||||
|
// check every style against the first of the items
|
||||||
|
if (firstChild) {
|
||||||
|
firstChild = false;
|
||||||
|
selectionFillColorString = itemFillColorString;
|
||||||
|
selectionStrokeColorString = itemStrokeColorString;
|
||||||
|
selectionStrokeWidth = path.strokeWidth;
|
||||||
|
}
|
||||||
|
if (itemFillColorString !== selectionFillColorString) {
|
||||||
|
selectionFillColorString = MIXED;
|
||||||
|
}
|
||||||
|
if (itemStrokeColorString !== selectionStrokeColorString) {
|
||||||
|
selectionStrokeColorString = MIXED;
|
||||||
|
}
|
||||||
|
if (selectionStrokeWidth !== path.strokeWidth) {
|
||||||
|
selectionStrokeWidth = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!isGroup(item)) {
|
||||||
|
if (item.fillColor) {
|
||||||
|
// hack bc text items with null fill can't be detected by fill-hitTest anymore
|
||||||
|
if (isPointTextItem(item) && item.fillColor.toCSS() === 'rgba(0,0,0,0)') {
|
||||||
|
itemFillColorString = null;
|
||||||
|
} else {
|
||||||
|
itemFillColorString = item.fillColor.toCSS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.strokeColor) {
|
||||||
|
itemStrokeColorString = item.strokeColor.toCSS();
|
||||||
|
}
|
||||||
|
// check every style against the first of the items
|
||||||
|
if (firstChild) {
|
||||||
|
firstChild = false;
|
||||||
|
selectionFillColorString = itemFillColorString;
|
||||||
|
selectionStrokeColorString = itemStrokeColorString;
|
||||||
|
selectionStrokeWidth = item.strokeWidth;
|
||||||
|
}
|
||||||
|
if (itemFillColorString !== selectionFillColorString) {
|
||||||
|
selectionFillColorString = MIXED;
|
||||||
|
}
|
||||||
|
if (itemStrokeColorString !== selectionStrokeColorString) {
|
||||||
|
selectionStrokeColorString = MIXED;
|
||||||
|
}
|
||||||
|
if (selectionStrokeWidth !== item.strokeWidth) {
|
||||||
|
selectionStrokeWidth = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fillColor: selectionFillColorString ? selectionFillColorString : null,
|
||||||
|
strokeColor: selectionStrokeColorString ? selectionStrokeColorString : null,
|
||||||
|
strokeWidth: selectionStrokeWidth || (selectionStrokeWidth === null) ? selectionStrokeWidth : 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const stylePath = function (path, options) {
|
||||||
|
if (options.isEraser) {
|
||||||
|
path.fillColor = 'white';
|
||||||
|
} else {
|
||||||
|
path.fillColor = options.fillColor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const styleCursorPreview = function (path, options) {
|
||||||
|
if (options.isEraser) {
|
||||||
|
path.fillColor = 'white';
|
||||||
|
path.strokeColor = 'cornflowerblue';
|
||||||
|
path.strokeWidth = 1;
|
||||||
|
} else {
|
||||||
|
path.fillColor = options.fillColor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
applyFillColorToSelection,
|
||||||
|
applyStrokeColorToSelection,
|
||||||
|
applyStrokeWidthToSelection,
|
||||||
|
getColorsFromSelection,
|
||||||
|
MIXED,
|
||||||
|
stylePath,
|
||||||
|
styleCursorPreview
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||||
|
import {getColorsFromSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const CHANGE_FILL_COLOR = 'scratch-paint/fill-color/CHANGE_FILL_COLOR';
|
const CHANGE_FILL_COLOR = 'scratch-paint/fill-color/CHANGE_FILL_COLOR';
|
||||||
const initialState = '#000';
|
const initialState = '#000';
|
||||||
|
@ -14,6 +16,12 @@ const reducer = function (state, action) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
return action.fillColor;
|
return action.fillColor;
|
||||||
|
case CHANGE_SELECTED_ITEMS:
|
||||||
|
// Don't change state if no selection
|
||||||
|
if (!action.selectedItems || !action.selectedItems.length) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return getColorsFromSelection(action.selectedItems).fillColor;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ import brushModeReducer from './brush-mode';
|
||||||
import eraserModeReducer from './eraser-mode';
|
import eraserModeReducer from './eraser-mode';
|
||||||
import colorReducer from './color';
|
import colorReducer from './color';
|
||||||
import hoverReducer from './hover';
|
import hoverReducer from './hover';
|
||||||
|
import selectedItemReducer from './selected-items';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
mode: modeReducer,
|
mode: modeReducer,
|
||||||
brushMode: brushModeReducer,
|
brushMode: brushModeReducer,
|
||||||
eraserMode: eraserModeReducer,
|
eraserMode: eraserModeReducer,
|
||||||
color: colorReducer,
|
color: colorReducer,
|
||||||
hoveredItemId: hoverReducer
|
hoveredItemId: hoverReducer,
|
||||||
|
selectedItems: selectedItemReducer
|
||||||
});
|
});
|
||||||
|
|
53
src/reducers/selected-items.js
Normal file
53
src/reducers/selected-items.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import log from '../log/log';
|
||||||
|
const CHANGE_SELECTED_ITEMS = 'scratch-paint/select/CHANGE_SELECTED_ITEMS';
|
||||||
|
const initialState = [];
|
||||||
|
|
||||||
|
const reducer = function (state, action) {
|
||||||
|
if (typeof state === 'undefined') state = initialState;
|
||||||
|
switch (action.type) {
|
||||||
|
case CHANGE_SELECTED_ITEMS:
|
||||||
|
if (!action.selectedItems || !(action.selectedItems instanceof Array)) {
|
||||||
|
log.warn(`No selected items or wrong format provided: ${action.selectedItems}`);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
// If they are not equal, return the new list of items. Else return old list
|
||||||
|
if (action.selectedItems.length !== state.length) {
|
||||||
|
return action.selectedItems;
|
||||||
|
}
|
||||||
|
// Shallow equality check (we may need to update this later for more granularity)
|
||||||
|
for (let i = 0; i < action.selectedItems.length; i++) {
|
||||||
|
if (action.selectedItems[i] !== state[i]) {
|
||||||
|
return action.selectedItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Action creators ==================================
|
||||||
|
/**
|
||||||
|
* Set the selected item state to the given array of items
|
||||||
|
* @param {Array<paper.Item>} selectedItems from paper.project.selectedItems
|
||||||
|
* @return {object} Redux action to change the selected items.
|
||||||
|
*/
|
||||||
|
const setSelectedItems = function (selectedItems) {
|
||||||
|
return {
|
||||||
|
type: CHANGE_SELECTED_ITEMS,
|
||||||
|
selectedItems: selectedItems
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const clearSelectedItems = function () {
|
||||||
|
return {
|
||||||
|
type: CHANGE_SELECTED_ITEMS,
|
||||||
|
selectedItems: []
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
reducer as default,
|
||||||
|
setSelectedItems,
|
||||||
|
clearSelectedItems,
|
||||||
|
CHANGE_SELECTED_ITEMS
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||||
|
import {getColorsFromSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const CHANGE_STROKE_COLOR = 'scratch-paint/stroke-color/CHANGE_STROKE_COLOR';
|
const CHANGE_STROKE_COLOR = 'scratch-paint/stroke-color/CHANGE_STROKE_COLOR';
|
||||||
const initialState = '#000';
|
const initialState = '#000';
|
||||||
|
@ -14,6 +16,12 @@ const reducer = function (state, action) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
return action.strokeColor;
|
return action.strokeColor;
|
||||||
|
case CHANGE_SELECTED_ITEMS:
|
||||||
|
// Don't change state if no selection
|
||||||
|
if (!action.selectedItems || !action.selectedItems.length) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return getColorsFromSelection(action.selectedItems).strokeColor;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import log from '../log/log';
|
import log from '../log/log';
|
||||||
|
import {CHANGE_SELECTED_ITEMS} from './selected-items';
|
||||||
|
import {getColorsFromSelection} from '../helper/style-path';
|
||||||
|
|
||||||
const CHANGE_STROKE_WIDTH = 'scratch-paint/stroke-width/CHANGE_STROKE_WIDTH';
|
const CHANGE_STROKE_WIDTH = 'scratch-paint/stroke-width/CHANGE_STROKE_WIDTH';
|
||||||
const MAX_STROKE_WIDTH = 400;
|
const MAX_STROKE_WIDTH = 400;
|
||||||
|
@ -13,6 +15,12 @@ const reducer = function (state, action) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
return Math.min(MAX_STROKE_WIDTH, Math.max(0, action.strokeWidth));
|
return Math.min(MAX_STROKE_WIDTH, Math.max(0, action.strokeWidth));
|
||||||
|
case CHANGE_SELECTED_ITEMS:
|
||||||
|
// Don't change state if no selection
|
||||||
|
if (!action.selectedItems || !action.selectedItems.length) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return getColorsFromSelection(action.selectedItems).strokeWidth;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
23
test/__mocks__/paperMocks.js
Normal file
23
test/__mocks__/paperMocks.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Pretend paper.Item whose parent is a layer.
|
||||||
|
* @param {object} options Item params
|
||||||
|
* @param {string} options.strokeColor Value to return for the item's stroke color
|
||||||
|
* @param {string} options.fillColor Value to return for the item's fill color
|
||||||
|
* @param {string} options.strokeWidth Value to return for the item's stroke width
|
||||||
|
* @return {object} mock item
|
||||||
|
*/
|
||||||
|
const mockPaperRootItem = function (options) {
|
||||||
|
return {
|
||||||
|
strokeColor: {toCSS: function () {
|
||||||
|
return options.strokeColor;
|
||||||
|
}},
|
||||||
|
fillColor: {toCSS: function () {
|
||||||
|
return options.fillColor;
|
||||||
|
}},
|
||||||
|
strokeWidth: options.strokeWidth,
|
||||||
|
parent: {className: 'Layer'},
|
||||||
|
data: {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export {mockPaperRootItem};
|
|
@ -1,6 +1,9 @@
|
||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
import fillColorReducer from '../../src/reducers/fill-color';
|
import fillColorReducer from '../../src/reducers/fill-color';
|
||||||
import {changeFillColor} from '../../src/reducers/fill-color';
|
import {changeFillColor} from '../../src/reducers/fill-color';
|
||||||
|
import {setSelectedItems} from '../../src/reducers/selected-items';
|
||||||
|
import {MIXED} from '../../src/helper/style-path';
|
||||||
|
import {mockPaperRootItem} from '../__mocks__/paperMocks';
|
||||||
|
|
||||||
test('initialState', () => {
|
test('initialState', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
|
@ -26,6 +29,22 @@ test('changeFillColor', () => {
|
||||||
.toEqual(newFillColor);
|
.toEqual(newFillColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('changefillColorViaSelectedItems', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
const fillColor1 = 6;
|
||||||
|
const fillColor2 = null; // transparent
|
||||||
|
let selectedItems = [mockPaperRootItem({fillColor: fillColor1})];
|
||||||
|
expect(fillColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(fillColor1);
|
||||||
|
selectedItems = [mockPaperRootItem({fillColor: fillColor2})];
|
||||||
|
expect(fillColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(fillColor2);
|
||||||
|
selectedItems = [mockPaperRootItem({fillColor: fillColor1}), mockPaperRootItem({fillColor: fillColor2})];
|
||||||
|
expect(fillColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(MIXED);
|
||||||
|
});
|
||||||
|
|
||||||
test('invalidChangeFillColor', () => {
|
test('invalidChangeFillColor', () => {
|
||||||
const origState = '#fff';
|
const origState = '#fff';
|
||||||
|
|
||||||
|
|
47
test/unit/selected-items-reducer.test.js
Normal file
47
test/unit/selected-items-reducer.test.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* eslint-env jest */
|
||||||
|
import selectedItemsReducer from '../../src/reducers/selected-items';
|
||||||
|
import {setSelectedItems, clearSelectedItems} from '../../src/reducers/selected-items';
|
||||||
|
|
||||||
|
test('initialState', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
expect(selectedItemsReducer(defaultState /* state */, {type: 'anything'} /* action */)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setSelectedItems', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
const newSelected1 = ['selected1', 'selected2'];
|
||||||
|
const newSelected2 = ['selected1', 'selected3'];
|
||||||
|
const unselected = [];
|
||||||
|
expect(selectedItemsReducer(defaultState /* state */, setSelectedItems(newSelected1) /* action */))
|
||||||
|
.toEqual(newSelected1);
|
||||||
|
expect(selectedItemsReducer(newSelected1, setSelectedItems(newSelected2) /* action */))
|
||||||
|
.toEqual(newSelected2);
|
||||||
|
expect(selectedItemsReducer(newSelected1, setSelectedItems(unselected) /* action */))
|
||||||
|
.toEqual(unselected);
|
||||||
|
expect(selectedItemsReducer(defaultState, setSelectedItems(unselected) /* action */))
|
||||||
|
.toEqual(unselected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('clearSelectedItems', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
const selectedState = ['selected1', 'selected2'];
|
||||||
|
const unselectedState = [];
|
||||||
|
expect(selectedItemsReducer(defaultState /* state */, clearSelectedItems() /* action */))
|
||||||
|
.toHaveLength(0);
|
||||||
|
expect(selectedItemsReducer(selectedState /* state */, clearSelectedItems() /* action */))
|
||||||
|
.toHaveLength(0);
|
||||||
|
expect(selectedItemsReducer(unselectedState /* state */, clearSelectedItems() /* action */))
|
||||||
|
.toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalidsetSelectedItems', () => {
|
||||||
|
const origState = ['selected1', 'selected2'];
|
||||||
|
|
||||||
|
expect(selectedItemsReducer(origState /* state */, setSelectedItems() /* action */))
|
||||||
|
.toBe(origState);
|
||||||
|
expect(selectedItemsReducer(origState /* state */, setSelectedItems('notAnArray') /* action */))
|
||||||
|
.toBe(origState);
|
||||||
|
});
|
|
@ -1,6 +1,9 @@
|
||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
import strokeColorReducer from '../../src/reducers/stroke-color';
|
import strokeColorReducer from '../../src/reducers/stroke-color';
|
||||||
import {changeStrokeColor} from '../../src/reducers/stroke-color';
|
import {changeStrokeColor} from '../../src/reducers/stroke-color';
|
||||||
|
import {setSelectedItems} from '../../src/reducers/selected-items';
|
||||||
|
import {MIXED} from '../../src/helper/style-path';
|
||||||
|
import {mockPaperRootItem} from '../__mocks__/paperMocks';
|
||||||
|
|
||||||
test('initialState', () => {
|
test('initialState', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
|
@ -26,6 +29,22 @@ test('changeStrokeColor', () => {
|
||||||
.toEqual(newStrokeColor);
|
.toEqual(newStrokeColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('changeStrokeColorViaSelectedItems', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
const strokeColor1 = 6;
|
||||||
|
const strokeColor2 = null; // transparent
|
||||||
|
let selectedItems = [mockPaperRootItem({strokeColor: strokeColor1})];
|
||||||
|
expect(strokeColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(strokeColor1);
|
||||||
|
selectedItems = [mockPaperRootItem({strokeColor: strokeColor2})];
|
||||||
|
expect(strokeColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(strokeColor2);
|
||||||
|
selectedItems = [mockPaperRootItem({strokeColor: strokeColor1}), mockPaperRootItem({strokeColor: strokeColor2})];
|
||||||
|
expect(strokeColorReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(MIXED);
|
||||||
|
});
|
||||||
|
|
||||||
test('invalidChangeStrokeColor', () => {
|
test('invalidChangeStrokeColor', () => {
|
||||||
const origState = '#fff';
|
const origState = '#fff';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
import strokeWidthReducer from '../../src/reducers/stroke-width';
|
import strokeWidthReducer from '../../src/reducers/stroke-width';
|
||||||
import {MAX_STROKE_WIDTH, changeStrokeWidth} from '../../src/reducers/stroke-width';
|
import {MAX_STROKE_WIDTH, changeStrokeWidth} from '../../src/reducers/stroke-width';
|
||||||
|
import {setSelectedItems} from '../../src/reducers/selected-items';
|
||||||
|
import {mockPaperRootItem} from '../__mocks__/paperMocks';
|
||||||
|
|
||||||
test('initialState', () => {
|
test('initialState', () => {
|
||||||
let defaultState;
|
let defaultState;
|
||||||
|
@ -23,6 +25,22 @@ test('changestrokeWidth', () => {
|
||||||
.toEqual(MAX_STROKE_WIDTH);
|
.toEqual(MAX_STROKE_WIDTH);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('changeStrokeWidthViaSelectedItems', () => {
|
||||||
|
let defaultState;
|
||||||
|
|
||||||
|
const strokeWidth1 = 6;
|
||||||
|
let strokeWidth2; // no outline
|
||||||
|
let selectedItems = [mockPaperRootItem({strokeWidth: strokeWidth1})];
|
||||||
|
expect(strokeWidthReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(strokeWidth1);
|
||||||
|
selectedItems = [mockPaperRootItem({strokeWidth: strokeWidth2})];
|
||||||
|
expect(strokeWidthReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(0); // Convert no outline to stroke width 0
|
||||||
|
selectedItems = [mockPaperRootItem({strokeWidth: strokeWidth1}), mockPaperRootItem({strokeWidth: strokeWidth2})];
|
||||||
|
expect(strokeWidthReducer(defaultState /* state */, setSelectedItems(selectedItems) /* action */))
|
||||||
|
.toEqual(null); // null indicates mixed for stroke width
|
||||||
|
});
|
||||||
|
|
||||||
test('invalidChangestrokeWidth', () => {
|
test('invalidChangestrokeWidth', () => {
|
||||||
const origState = {strokeWidth: 1};
|
const origState = {strokeWidth: 1};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue