diff --git a/src/components/fill-color-indicator.jsx b/src/components/color-indicator.jsx
similarity index 53%
rename from src/components/fill-color-indicator.jsx
rename to src/components/color-indicator.jsx
index 0a0fa0cf..d04ea2bb 100644
--- a/src/components/fill-color-indicator.jsx
+++ b/src/components/color-indicator.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import Popover from 'react-popover';
-import {defineMessages, injectIntl, intlShape} from 'react-intl';
import ColorButton from './color-button/color-button.jsx';
import ColorPicker from '../containers/color-picker.jsx';
@@ -10,15 +9,7 @@ import Label from './forms/label.jsx';
import GradientTypes from '../lib/gradient-types';
-const messages = defineMessages({
- fill: {
- id: 'paint.paintEditor.fill',
- description: 'Label for the color picker for the fill color',
- defaultMessage: 'Fill'
- }
-});
-
-const FillColorIndicatorComponent = props => (
+const ColorIndicatorComponent = props => (
(
}
- isOpen={props.fillColorModalVisible}
+ isOpen={props.colorModalVisible}
preferPlace="below"
- onOuterAction={props.onCloseFillColor}
+ onOuterAction={props.onCloseColor}
>
-
);
-FillColorIndicatorComponent.propTypes = {
+ColorIndicatorComponent.propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool.isRequired,
- fillColor: PropTypes.string,
- fillColor2: PropTypes.string,
- fillColorModalVisible: PropTypes.bool.isRequired,
+ color: PropTypes.string,
+ color2: PropTypes.string,
+ colorModalVisible: PropTypes.bool.isRequired,
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
- intl: intlShape,
- onChangeFillColor: PropTypes.func.isRequired,
+ label: PropTypes.string.isRequired,
+ onChangeColor: PropTypes.func.isRequired,
onChangeGradientType: PropTypes.func.isRequired,
- onCloseFillColor: PropTypes.func.isRequired,
- onOpenFillColor: PropTypes.func.isRequired,
+ onCloseColor: PropTypes.func.isRequired,
+ onOpenColor: PropTypes.func.isRequired,
onSwap: PropTypes.func.isRequired,
+ outline: PropTypes.bool.isRequired,
shouldShowGradientTools: PropTypes.bool.isRequired
};
-export default injectIntl(FillColorIndicatorComponent);
+export default ColorIndicatorComponent;
diff --git a/src/components/stroke-color-indicator.jsx b/src/components/stroke-color-indicator.jsx
deleted file mode 100644
index 15c40d9b..00000000
--- a/src/components/stroke-color-indicator.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Popover from 'react-popover';
-import {defineMessages, injectIntl, intlShape} from 'react-intl';
-
-import ColorButton from './color-button/color-button.jsx';
-import ColorPicker from '../containers/color-picker.jsx';
-import InputGroup from './input-group/input-group.jsx';
-import Label from './forms/label.jsx';
-import GradientTypes from '../lib/gradient-types';
-
-const messages = defineMessages({
- stroke: {
- id: 'paint.paintEditor.stroke',
- description: 'Label for the color picker for the outline color',
- defaultMessage: 'Outline'
- }
-});
-
-const StrokeColorIndicatorComponent = props => (
-
-
- }
- isOpen={props.strokeColorModalVisible}
- preferPlace="below"
- onOuterAction={props.onCloseStrokeColor}
- >
-
-
-
-
-
-);
-
-StrokeColorIndicatorComponent.propTypes = {
- className: PropTypes.string,
- disabled: PropTypes.bool.isRequired,
- intl: intlShape,
- onChangeStrokeColor: PropTypes.func.isRequired,
- onCloseStrokeColor: PropTypes.func.isRequired,
- onOpenStrokeColor: PropTypes.func.isRequired,
- strokeColor: PropTypes.string,
- strokeColorModalVisible: PropTypes.bool.isRequired
-};
-
-export default injectIntl(StrokeColorIndicatorComponent);
diff --git a/src/containers/brush-mode.jsx b/src/containers/brush-mode.jsx
index 32a80640..f1936ad6 100644
--- a/src/containers/brush-mode.jsx
+++ b/src/containers/brush-mode.jsx
@@ -3,6 +3,7 @@ import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../lib/modes';
+import ColorStyleProptype from '../lib/color-style-proptype';
import Blobbiness from '../helper/blob-tools/blob';
import {MIXED} from '../helper/style-path';
@@ -38,7 +39,7 @@ class BrushMode extends React.Component {
this.blob.setOptions({
isEraser: false,
fillColor: fillColor.primary,
- strokeColor,
+ strokeColor: strokeColor.primary,
strokeWidth,
...nextProps.brushModeState
});
@@ -88,11 +89,8 @@ BrushMode.propTypes = {
clearGradient: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
colorState: PropTypes.shape({
- fillColor: PropTypes.shape({
- primary: PropTypes.string,
- secondary: PropTypes.string
- }),
- strokeColor: PropTypes.string,
+ fillColor: ColorStyleProptype,
+ strokeColor: ColorStyleProptype,
strokeWidth: PropTypes.number
}).isRequired,
handleMouseDown: PropTypes.func.isRequired,
diff --git a/src/containers/color-indicator.jsx b/src/containers/color-indicator.jsx
new file mode 100644
index 00000000..37e2b40a
--- /dev/null
+++ b/src/containers/color-indicator.jsx
@@ -0,0 +1,156 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import bindAll from 'lodash.bindall';
+import parseColor from 'parse-color';
+import {injectIntl, intlShape} from 'react-intl';
+
+import {getSelectedLeafItems} from '../helper/selection';
+import Formats from '../lib/format';
+import {isBitmap} from '../lib/format';
+import GradientTypes from '../lib/gradient-types';
+
+import ColorIndicatorComponent from '../components/color-indicator.jsx';
+import {applyColorToSelection,
+ applyGradientTypeToSelection,
+ applyStrokeWidthToSelection,
+ getRotatedColor,
+ swapColorsInSelection,
+ MIXED} from '../helper/style-path';
+
+const makeColorIndicator = (label, isStroke) => {
+ class ColorIndicator extends React.Component {
+ constructor (props) {
+ super(props);
+ bindAll(this, [
+ 'handleChangeColor',
+ 'handleChangeGradientType',
+ 'handleCloseColor',
+ 'handleSwap'
+ ]);
+
+ // Flag to track whether an svg-update-worthy change has been made
+ this._hasChanged = false;
+ }
+ componentWillReceiveProps (newProps) {
+ const {colorModalVisible, onUpdateImage} = this.props;
+ if (colorModalVisible && !newProps.colorModalVisible) {
+ // Submit the new SVG, which also stores a single undo/redo action.
+ if (this._hasChanged) onUpdateImage();
+ this._hasChanged = false;
+ }
+ }
+ handleChangeColor (newColor) {
+ // Stroke-selector-specific logic: if we change the stroke color from "none" to something visible, ensure
+ // there's a nonzero stroke width. If we change the stroke color to "none", set the stroke width to zero.
+ if (isStroke) {
+ if (this.props.color === null && newColor !== null) {
+ this._hasChanged = applyStrokeWidthToSelection(1, this.props.textEditTarget) || this._hasChanged;
+ this.props.onChangeStrokeWidth(1);
+ } else if (this.props.color !== null && newColor === null) {
+ this._hasChanged = applyStrokeWidthToSelection(0, this.props.textEditTarget) || this._hasChanged;
+ this.props.onChangeStrokeWidth(0);
+ }
+ }
+
+ // Apply color and update redux, but do not update svg until picker closes.
+ const isDifferent = applyColorToSelection(
+ newColor,
+ this.props.colorIndex,
+ this.props.gradientType === GradientTypes.SOLID,
+ isBitmap(this.props.format),
+ isStroke,
+ this.props.textEditTarget);
+ this._hasChanged = this._hasChanged || isDifferent;
+ this.props.onChangeColor(newColor, this.props.colorIndex);
+ }
+ handleChangeGradientType (gradientType) {
+ // Apply color and update redux, but do not update svg until picker closes.
+ const isDifferent = applyGradientTypeToSelection(
+ gradientType,
+ isBitmap(this.props.format),
+ isStroke,
+ this.props.textEditTarget);
+ this._hasChanged = this._hasChanged || isDifferent;
+ const hasSelectedItems = getSelectedLeafItems().length > 0;
+ if (hasSelectedItems) {
+ if (isDifferent) {
+ // Recalculates the swatch colors
+ this.props.setSelectedItems();
+ }
+ }
+ if (this.props.gradientType === GradientTypes.SOLID && gradientType !== GradientTypes.SOLID) {
+ // Generate color 2 and change to the 2nd swatch when switching from solid to gradient
+ if (!hasSelectedItems) {
+ this.props.onChangeColor(getRotatedColor(this.props.color), 1);
+ }
+ this.props.onChangeColorIndex(1);
+ }
+ if (this.props.onChangeGradientType) this.props.onChangeGradientType(gradientType);
+ }
+ handleCloseColor () {
+ // If the eyedropper is currently being used, don't
+ // close the color menu.
+ if (this.props.isEyeDropping) return;
+
+ // Otherwise, close the color menu and
+ // also reset the color index to indicate
+ // that `color1` is selected.
+ this.props.onCloseColor();
+ this.props.onChangeColorIndex(0);
+ }
+ handleSwap () {
+ if (getSelectedLeafItems().length) {
+ const isDifferent = swapColorsInSelection(
+ isBitmap(this.props.format),
+ isStroke,
+ this.props.textEditTarget);
+ this.props.setSelectedItems();
+ this._hasChanged = this._hasChanged || isDifferent;
+ } else {
+ let color1 = this.props.color;
+ let color2 = this.props.color2;
+ color1 = color1 === null || color1 === MIXED ? color1 : parseColor(color1).hex;
+ color2 = color2 === null || color2 === MIXED ? color2 : parseColor(color2).hex;
+ this.props.onChangeColor(color1, 1);
+ this.props.onChangeColor(color2, 0);
+ }
+ }
+ render () {
+ return (
+
+ );
+ }
+ }
+
+ ColorIndicator.propTypes = {
+ colorIndex: PropTypes.number.isRequired,
+ disabled: PropTypes.bool.isRequired,
+ color: PropTypes.string,
+ color2: PropTypes.string,
+ colorModalVisible: PropTypes.bool.isRequired,
+ format: PropTypes.oneOf(Object.keys(Formats)),
+ gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
+ intl: intlShape,
+ isEyeDropping: PropTypes.bool.isRequired,
+ onChangeColorIndex: PropTypes.func.isRequired,
+ onChangeColor: PropTypes.func.isRequired,
+ onChangeGradientType: PropTypes.func,
+ onChangeStrokeWidth: PropTypes.func,
+ onCloseColor: PropTypes.func.isRequired,
+ onUpdateImage: PropTypes.func.isRequired,
+ setSelectedItems: PropTypes.func.isRequired,
+ textEditTarget: PropTypes.number
+ };
+
+ return injectIntl(ColorIndicator);
+};
+
+export default makeColorIndicator;
diff --git a/src/containers/fill-color-indicator.jsx b/src/containers/fill-color-indicator.jsx
index 43291462..9d88070d 100644
--- a/src/containers/fill-color-indicator.jsx
+++ b/src/containers/fill-color-indicator.jsx
@@ -1,8 +1,5 @@
import {connect} from 'react-redux';
-import PropTypes from 'prop-types';
-import React from 'react';
-import bindAll from 'lodash.bindall';
-import parseColor from 'parse-color';
+import {defineMessages} from 'react-intl';
import {changeColorIndex} from '../reducers/color-index';
import {changeFillColor, changeFillColor2} from '../reducers/fill-style';
@@ -11,121 +8,26 @@ import {openFillColor, closeFillColor} from '../reducers/modals';
import {getSelectedLeafItems} from '../helper/selection';
import {setSelectedItems} from '../reducers/selected-items';
import Modes from '../lib/modes';
-import Formats from '../lib/format';
import {isBitmap} from '../lib/format';
-import GradientTypes from '../lib/gradient-types';
-import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
-import {applyColorToSelection,
- applyGradientTypeToSelection,
- getRotatedColor,
- swapColorsInSelection,
- MIXED} from '../helper/style-path';
+import makeColorIndicator from './color-indicator.jsx';
-class FillColorIndicator extends React.Component {
- constructor (props) {
- super(props);
- bindAll(this, [
- 'handleChangeFillColor',
- 'handleChangeGradientType',
- 'handleCloseFillColor',
- 'handleSwap'
- ]);
+const messages = defineMessages({
+ label: {
+ id: 'paint.paintEditor.fill',
+ description: 'Label for the color picker for the fill color',
+ defaultMessage: 'Fill'
+ }
+});
- // Flag to track whether an svg-update-worthy change has been made
- this._hasChanged = false;
- }
- componentWillReceiveProps (newProps) {
- const {fillColorModalVisible, onUpdateImage} = this.props;
- if (fillColorModalVisible && !newProps.fillColorModalVisible) {
- // Submit the new SVG, which also stores a single undo/redo action.
- if (this._hasChanged) onUpdateImage();
- this._hasChanged = false;
- }
- }
- handleChangeFillColor (newColor) {
- // Apply color and update redux, but do not update svg until picker closes.
- const isDifferent = applyColorToSelection(
- newColor,
- this.props.colorIndex,
- this.props.gradientType === GradientTypes.SOLID,
- isBitmap(this.props.format),
- false, // applyToStroke
- this.props.textEditTarget);
- this._hasChanged = this._hasChanged || isDifferent;
- this.props.onChangeFillColor(newColor, this.props.colorIndex);
- }
- handleChangeGradientType (gradientType) {
- // Apply color and update redux, but do not update svg until picker closes.
- const isDifferent = applyGradientTypeToSelection(
- gradientType,
- isBitmap(this.props.format),
- false, // applyToStroke
- this.props.textEditTarget);
- this._hasChanged = this._hasChanged || isDifferent;
- const hasSelectedItems = getSelectedLeafItems().length > 0;
- if (hasSelectedItems) {
- if (isDifferent) {
- // Recalculates the swatch colors
- this.props.setSelectedItems();
- }
- }
- if (this.props.gradientType === GradientTypes.SOLID && gradientType !== GradientTypes.SOLID) {
- // Generate color 2 and change to the 2nd swatch when switching from solid to gradient
- if (!hasSelectedItems) {
- this.props.onChangeFillColor(getRotatedColor(this.props.fillColor), 1);
- }
- this.props.onChangeColorIndex(1);
- }
- this.props.onChangeGradientType(gradientType);
- }
- handleCloseFillColor () {
- // If the eyedropper is currently being used, don't
- // close the fill color menu.
- if (this.props.isEyeDropping) return;
-
- // Otherwise, close the fill color menu and
- // also reset the color index to indicate
- // that `color1` is selected.
- this.props.onCloseFillColor();
- this.props.onChangeColorIndex(0);
- }
- handleSwap () {
- if (getSelectedLeafItems().length) {
- const isDifferent = swapColorsInSelection(
- isBitmap(this.props.format),
- false, // applyToStroke
- this.props.textEditTarget);
- this.props.setSelectedItems();
- this._hasChanged = this._hasChanged || isDifferent;
- } else {
- let color1 = this.props.fillColor;
- let color2 = this.props.fillColor2;
- color1 = color1 === null || color1 === MIXED ? color1 : parseColor(color1).hex;
- color2 = color2 === null || color2 === MIXED ? color2 : parseColor(color2).hex;
- this.props.onChangeFillColor(color1, 1);
- this.props.onChangeFillColor(color2, 0);
- }
- }
- render () {
- return (
-
- );
- }
-}
+const FillColorIndicator = makeColorIndicator(messages.label, false);
const mapStateToProps = state => ({
colorIndex: state.scratchPaint.fillMode.colorIndex,
disabled: state.scratchPaint.mode === Modes.LINE,
- fillColor: state.scratchPaint.color.fillColor.primary,
- fillColor2: state.scratchPaint.color.fillColor.secondary,
- fillColorModalVisible: state.scratchPaint.modals.fillColor,
+ color: state.scratchPaint.color.fillColor.primary,
+ color2: state.scratchPaint.color.fillColor.secondary,
+ colorModalVisible: state.scratchPaint.modals.fillColor,
format: state.scratchPaint.format,
gradientType: state.scratchPaint.color.fillColor.gradientType,
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
@@ -142,17 +44,17 @@ const mapDispatchToProps = dispatch => ({
onChangeColorIndex: index => {
dispatch(changeColorIndex(index));
},
- onChangeFillColor: (fillColor, index) => {
+ onChangeColor: (fillColor, index) => {
if (index === 0) {
dispatch(changeFillColor(fillColor));
} else if (index === 1) {
dispatch(changeFillColor2(fillColor));
}
},
- onOpenFillColor: () => {
+ onOpenColor: () => {
dispatch(openFillColor());
},
- onCloseFillColor: () => {
+ onCloseColor: () => {
dispatch(closeFillColor());
},
onChangeGradientType: gradientType => {
@@ -163,24 +65,6 @@ const mapDispatchToProps = dispatch => ({
}
});
-FillColorIndicator.propTypes = {
- colorIndex: PropTypes.number.isRequired,
- disabled: PropTypes.bool.isRequired,
- fillColor: PropTypes.string,
- fillColor2: PropTypes.string,
- fillColorModalVisible: PropTypes.bool.isRequired,
- format: PropTypes.oneOf(Object.keys(Formats)),
- gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
- isEyeDropping: PropTypes.bool.isRequired,
- onChangeColorIndex: PropTypes.func.isRequired,
- onChangeFillColor: PropTypes.func.isRequired,
- onChangeGradientType: PropTypes.func.isRequired,
- onCloseFillColor: PropTypes.func.isRequired,
- onUpdateImage: PropTypes.func.isRequired,
- setSelectedItems: PropTypes.func.isRequired,
- textEditTarget: PropTypes.number
-};
-
export default connect(
mapStateToProps,
mapDispatchToProps
diff --git a/src/containers/line-mode.jsx b/src/containers/line-mode.jsx
index 9f2d675e..2368c43b 100644
--- a/src/containers/line-mode.jsx
+++ b/src/containers/line-mode.jsx
@@ -4,11 +4,12 @@ import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../lib/modes';
+import ColorStyleProptype from '../lib/color-style-proptype';
import {clearSelection} from '../helper/selection';
import {endPointHit, touching} from '../helper/snapping';
import {drawHitPoint, removeHitPoint} from '../helper/guides';
import {stylePath} from '../helper/style-path';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {changeStrokeColor, clearStrokeGradient} from '../reducers/stroke-style';
import {changeStrokeWidth} from '../reducers/stroke-width';
import {changeMode} from '../reducers/modes';
import {clearSelectedItems} from '../reducers/selected-items';
@@ -58,9 +59,10 @@ class LineMode extends React.Component {
}
activateTool () {
clearSelection(this.props.clearSelectedItems);
+ this.props.clearGradient();
// Force the default line color if stroke is MIXED or transparent
- const {strokeColor} = this.props.colorState;
+ const strokeColor = this.props.colorState.strokeColor.primary;
if (strokeColor === MIXED || strokeColor === null) {
this.props.onChangeStrokeColor(LineMode.DEFAULT_COLOR);
}
@@ -101,7 +103,7 @@ class LineMode extends React.Component {
this.hitResult = endPointHit(event.point, LineMode.SNAP_TOLERANCE);
if (this.hitResult) {
this.path = this.hitResult.path;
- stylePath(this.path, this.props.colorState.strokeColor, this.props.colorState.strokeWidth);
+ stylePath(this.path, this.props.colorState.strokeColor.primary, this.props.colorState.strokeWidth);
if (this.hitResult.isFirst) {
this.path.reverse();
}
@@ -114,7 +116,7 @@ class LineMode extends React.Component {
if (!this.path) {
this.path = new paper.Path();
this.path.strokeCap = 'round';
- stylePath(this.path, this.props.colorState.strokeColor, this.props.colorState.strokeWidth);
+ stylePath(this.path, this.props.colorState.strokeColor.primary, this.props.colorState.strokeWidth);
this.path.add(event.point);
this.path.add(event.point); // Add second point, which is what will move when dragged
@@ -253,13 +255,11 @@ class LineMode extends React.Component {
}
LineMode.propTypes = {
+ clearGradient: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
colorState: PropTypes.shape({
- fillColor: PropTypes.shape({
- primary: PropTypes.string,
- secondary: PropTypes.string
- }),
- strokeColor: PropTypes.string,
+ fillColor: ColorStyleProptype,
+ strokeColor: ColorStyleProptype,
strokeWidth: PropTypes.number
}).isRequired,
handleMouseDown: PropTypes.func.isRequired,
@@ -274,6 +274,9 @@ const mapStateToProps = state => ({
isLineModeActive: state.scratchPaint.mode === Modes.LINE
});
const mapDispatchToProps = dispatch => ({
+ clearGradient: () => {
+ dispatch(clearStrokeGradient());
+ },
clearSelectedItems: () => {
dispatch(clearSelectedItems());
},
diff --git a/src/containers/oval-mode.jsx b/src/containers/oval-mode.jsx
index c5b99d99..8333b3f2 100644
--- a/src/containers/oval-mode.jsx
+++ b/src/containers/oval-mode.jsx
@@ -4,10 +4,11 @@ import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../lib/modes';
+import ColorStyleProptype from '../lib/color-style-proptype';
import {MIXED} from '../helper/style-path';
import {changeFillColor, clearFillGradient, DEFAULT_COLOR} from '../reducers/fill-style';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {changeStrokeColor} from '../reducers/stroke-style';
import {changeMode} from '../reducers/modes';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {setCursor} from '../reducers/cursor';
@@ -57,8 +58,9 @@ class OvalMode extends React.Component {
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
// If exactly one of fill or stroke color is set, set the other one to transparent.
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
- const {strokeColor, strokeWidth} = this.props.colorState;
+ const {strokeWidth} = this.props.colorState;
const fillColor = this.props.colorState.fillColor.primary;
+ const strokeColor = this.props.colorState.strokeColor.primary;
const fillColorPresent = fillColor !== MIXED && fillColor !== null;
const strokeColorPresent =
strokeColor !== MIXED && strokeColor !== null && strokeWidth !== null && strokeWidth !== 0;
@@ -98,11 +100,8 @@ OvalMode.propTypes = {
clearGradient: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
colorState: PropTypes.shape({
- fillColor: PropTypes.shape({
- primary: PropTypes.string,
- secondary: PropTypes.string
- }),
- strokeColor: PropTypes.string,
+ fillColor: ColorStyleProptype,
+ strokeColor: ColorStyleProptype,
strokeWidth: PropTypes.number
}).isRequired,
handleMouseDown: PropTypes.func.isRequired,
diff --git a/src/containers/rect-mode.jsx b/src/containers/rect-mode.jsx
index f4929f93..5730b0a6 100644
--- a/src/containers/rect-mode.jsx
+++ b/src/containers/rect-mode.jsx
@@ -4,10 +4,11 @@ import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../lib/modes';
+import ColorStyleProptype from '../lib/color-style-proptype';
import {MIXED} from '../helper/style-path';
import {changeFillColor, clearFillGradient, DEFAULT_COLOR} from '../reducers/fill-style';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {changeStrokeColor} from '../reducers/stroke-style';
import {changeMode} from '../reducers/modes';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {setCursor} from '../reducers/cursor';
@@ -57,8 +58,9 @@ class RectMode extends React.Component {
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
// If exactly one of fill or stroke color is set, set the other one to transparent.
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
- const {strokeColor, strokeWidth} = this.props.colorState;
+ const {strokeWidth} = this.props.colorState;
const fillColor = this.props.colorState.fillColor.primary;
+ const strokeColor = this.props.colorState.strokeColor.primary;
const fillColorPresent = fillColor !== MIXED && fillColor !== null;
const strokeColorPresent =
strokeColor !== MIXED && strokeColor !== null && strokeWidth !== null && strokeWidth !== 0;
@@ -98,11 +100,8 @@ RectMode.propTypes = {
clearGradient: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
colorState: PropTypes.shape({
- fillColor: PropTypes.shape({
- primary: PropTypes.string,
- secondary: PropTypes.string
- }),
- strokeColor: PropTypes.string,
+ fillColor: ColorStyleProptype,
+ strokeColor: ColorStyleProptype,
strokeWidth: PropTypes.number
}).isRequired,
handleMouseDown: PropTypes.func.isRequired,
diff --git a/src/containers/stroke-color-indicator.jsx b/src/containers/stroke-color-indicator.jsx
index 57f68e3e..409064c1 100644
--- a/src/containers/stroke-color-indicator.jsx
+++ b/src/containers/stroke-color-indicator.jsx
@@ -1,109 +1,73 @@
import {connect} from 'react-redux';
-import PropTypes from 'prop-types';
-import React from 'react';
-import bindAll from 'lodash.bindall';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {defineMessages} from 'react-intl';
+
+import {changeColorIndex} from '../reducers/color-index';
+import {changeStrokeColor, changeStrokeColor2} from '../reducers/stroke-style';
import {changeStrokeWidth} from '../reducers/stroke-width';
+import {changeStrokeGradientType} from '../reducers/stroke-style';
import {openStrokeColor, closeStrokeColor} from '../reducers/modals';
+import {getSelectedLeafItems} from '../helper/selection';
+import {setSelectedItems} from '../reducers/selected-items';
import Modes from '../lib/modes';
-import Formats from '../lib/format';
import {isBitmap} from '../lib/format';
-import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
-import {applyColorToSelection, applyStrokeWidthToSelection} from '../helper/style-path';
+import makeColorIndicator from './color-indicator.jsx';
-class StrokeColorIndicator extends React.Component {
- constructor (props) {
- super(props);
- bindAll(this, [
- 'handleChangeStrokeColor',
- 'handleCloseStrokeColor'
- ]);
+const messages = defineMessages({
+ label: {
+ id: 'paint.paintEditor.stroke',
+ description: 'Label for the color picker for the outline color',
+ defaultMessage: 'Outline'
+ }
+});
- // Flag to track whether an svg-update-worthy change has been made
- this._hasChanged = false;
- }
- componentWillReceiveProps (newProps) {
- const {strokeColorModalVisible, onUpdateImage} = this.props;
- if (strokeColorModalVisible && !newProps.strokeColorModalVisible) {
- // Submit the new SVG, which also stores a single undo/redo action.
- if (this._hasChanged) onUpdateImage();
- this._hasChanged = false;
- }
- }
- handleChangeStrokeColor (newColor) {
- if (this.props.strokeColor === null && newColor !== null) {
- this._hasChanged = applyStrokeWidthToSelection(1, this.props.textEditTarget) || this._hasChanged;
- this.props.onChangeStrokeWidth(1);
- } else if (this.props.strokeColor !== null && newColor === null) {
- this._hasChanged = applyStrokeWidthToSelection(0, this.props.textEditTarget) || this._hasChanged;
- this.props.onChangeStrokeWidth(0);
- }
- // Apply color and update redux, but do not update svg until picker closes.
- this._hasChanged = applyColorToSelection(
- newColor,
- 0, // colorIndex,
- true, // isSolidGradient
- isBitmap(this.props.format),
- true, // applyToStroke
- this.props.textEditTarget) ||
- this._hasChanged;
- this.props.onChangeStrokeColor(newColor);
- }
- handleCloseStrokeColor () {
- if (!this.props.isEyeDropping) {
- this.props.onCloseStrokeColor();
- }
- }
- render () {
- return (
-
- );
- }
-}
+const StrokeColorIndicator = makeColorIndicator(messages.label, true);
const mapStateToProps = state => ({
+ colorIndex: state.scratchPaint.fillMode.colorIndex,
disabled: state.scratchPaint.mode === Modes.BRUSH ||
state.scratchPaint.mode === Modes.TEXT ||
state.scratchPaint.mode === Modes.FILL,
+ color: state.scratchPaint.color.strokeColor.primary,
+ color2: state.scratchPaint.color.strokeColor.secondary,
+ colorModalVisible: state.scratchPaint.modals.strokeColor,
format: state.scratchPaint.format,
+ gradientType: state.scratchPaint.color.strokeColor.gradientType,
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
- strokeColor: state.scratchPaint.color.strokeColor,
- strokeColorModalVisible: state.scratchPaint.modals.strokeColor,
+ mode: state.scratchPaint.mode,
+ shouldShowGradientTools: state.scratchPaint.mode === Modes.SELECT ||
+ state.scratchPaint.mode === Modes.RESHAPE,
textEditTarget: state.scratchPaint.textEditTarget
});
const mapDispatchToProps = dispatch => ({
- onChangeStrokeColor: strokeColor => {
- dispatch(changeStrokeColor(strokeColor));
+ onChangeColorIndex: index => {
+ dispatch(changeColorIndex(index));
+ },
+ onChangeColor: (strokeColor, index) => {
+ if (index === 0) {
+ dispatch(changeStrokeColor(strokeColor));
+ } else if (index === 1) {
+ dispatch(changeStrokeColor2(strokeColor));
+ }
},
onChangeStrokeWidth: strokeWidth => {
dispatch(changeStrokeWidth(strokeWidth));
},
- onOpenStrokeColor: () => {
+ onOpenColor: () => {
dispatch(openStrokeColor());
},
- onCloseStrokeColor: () => {
+ onCloseColor: () => {
dispatch(closeStrokeColor());
+ },
+ onChangeGradientType: gradientType => {
+ dispatch(changeStrokeGradientType(gradientType));
+ },
+ setSelectedItems: format => {
+ dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
}
});
-StrokeColorIndicator.propTypes = {
- format: PropTypes.oneOf(Object.keys(Formats)),
- isEyeDropping: PropTypes.bool.isRequired,
- onChangeStrokeColor: PropTypes.func.isRequired,
- onChangeStrokeWidth: PropTypes.func.isRequired,
- onCloseStrokeColor: PropTypes.func.isRequired,
- onUpdateImage: PropTypes.func.isRequired,
- strokeColor: PropTypes.string,
- strokeColorModalVisible: PropTypes.bool.isRequired,
- textEditTarget: PropTypes.number
-};
-
export default connect(
mapStateToProps,
mapDispatchToProps
diff --git a/src/containers/stroke-width-indicator.jsx b/src/containers/stroke-width-indicator.jsx
index 16412677..31e4d88f 100644
--- a/src/containers/stroke-width-indicator.jsx
+++ b/src/containers/stroke-width-indicator.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import parseColor from 'parse-color';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {changeStrokeColor} from '../reducers/stroke-style';
import {changeStrokeWidth} from '../reducers/stroke-width';
import StrokeWidthIndicatorComponent from '../components/stroke-width-indicator.jsx';
import {getSelectedLeafItems} from '../helper/selection';
diff --git a/src/containers/text-mode.jsx b/src/containers/text-mode.jsx
index eda064d7..ce270cd5 100644
--- a/src/containers/text-mode.jsx
+++ b/src/containers/text-mode.jsx
@@ -5,11 +5,12 @@ import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Fonts from '../lib/fonts';
import Modes from '../lib/modes';
+import ColorStyleProptype from '../lib/color-style-proptype';
import {MIXED} from '../helper/style-path';
import {changeFont} from '../reducers/font';
import {changeFillColor, clearFillGradient, DEFAULT_COLOR} from '../reducers/fill-style';
-import {changeStrokeColor} from '../reducers/stroke-color';
+import {changeStrokeColor} from '../reducers/stroke-style';
import {changeMode} from '../reducers/modes';
import {setTextEditTarget} from '../reducers/text-edit-target';
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
@@ -81,8 +82,9 @@ class TextMode extends React.Component {
// If fill and stroke color are both mixed/transparent/absent, set fill to default and stroke to transparent.
// If exactly one of fill or stroke color is set, set the other one to transparent.
// This way the tool won't draw an invisible state, or be unclear about what will be drawn.
- const {strokeColor, strokeWidth} = nextProps.colorState;
- const fillColor = this.props.colorState.fillColor.primary;
+ const {strokeWidth} = nextProps.colorState;
+ const fillColor = nextProps.colorState.fillColor.primary;
+ const strokeColor = nextProps.colorState.strokeColor.primary;
const fillColorPresent = fillColor !== MIXED && fillColor !== null;
const strokeColorPresent = nextProps.isBitmap ? false :
strokeColor !== MIXED && strokeColor !== null && strokeWidth !== null && strokeWidth !== 0;
@@ -143,11 +145,8 @@ TextMode.propTypes = {
clearGradient: PropTypes.func.isRequired,
clearSelectedItems: PropTypes.func.isRequired,
colorState: PropTypes.shape({
- fillColor: PropTypes.shape({
- primary: PropTypes.string,
- secondary: PropTypes.string
- }),
- strokeColor: PropTypes.string,
+ fillColor: ColorStyleProptype,
+ strokeColor: ColorStyleProptype,
strokeWidth: PropTypes.number
}).isRequired,
font: PropTypes.string,
diff --git a/src/helper/style-path.js b/src/helper/style-path.js
index b6af421a..53ad35e3 100644
--- a/src/helper/style-path.js
+++ b/src/helper/style-path.js
@@ -516,10 +516,6 @@ const getColorsFromSelection = function (selectedItems, bitmapMode) {
};
}
- // Treat stroke gradients as MIXED
- // TODO: remove this once stroke gradients are supported
- if (selectionStrokeGradientType !== GradientTypes.SOLID) selectionStrokeColorString = MIXED;
-
return {
fillColor: selectionFillColorString ? selectionFillColorString : null,
fillColor2: selectionFillColor2String ? selectionFillColor2String : null,
@@ -566,7 +562,7 @@ const styleCursorPreview = function (path, options) {
// TODO: style using gradient?
const styleShape = function (path, options) {
path.fillColor = options.fillColor.primary;
- path.strokeColor = options.strokeColor;
+ path.strokeColor = options.strokeColor.primary;
path.strokeWidth = options.strokeWidth;
};
diff --git a/src/lib/color-style-proptype.js b/src/lib/color-style-proptype.js
new file mode 100644
index 00000000..6e59603f
--- /dev/null
+++ b/src/lib/color-style-proptype.js
@@ -0,0 +1,9 @@
+import {PropTypes} from 'prop-types';
+
+import GradientTypes from './gradient-types';
+
+export default PropTypes.shape({
+ primary: PropTypes.string,
+ secondary: PropTypes.string,
+ gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired
+});
diff --git a/src/reducers/color.js b/src/reducers/color.js
index 1ceb3aa4..4d50d49b 100644
--- a/src/reducers/color.js
+++ b/src/reducers/color.js
@@ -1,7 +1,7 @@
import {combineReducers} from 'redux';
import eyeDropperReducer from './eye-dropper';
import fillColorReducer from './fill-style';
-import strokeColorReducer from './stroke-color';
+import strokeColorReducer from './stroke-style';
import strokeWidthReducer from './stroke-width';
export default combineReducers({
diff --git a/src/reducers/fill-style.js b/src/reducers/fill-style.js
index 98046c49..b97c4bef 100644
--- a/src/reducers/fill-style.js
+++ b/src/reducers/fill-style.js
@@ -35,7 +35,7 @@ const changeFillColor2 = function (fillColor) {
const changeFillGradientType = function (gradientType) {
return {
type: CHANGE_FILL_GRADIENT_TYPE,
- gradientType: gradientType
+ gradientType
};
};
diff --git a/src/reducers/stroke-color.js b/src/reducers/stroke-color.js
deleted file mode 100644
index 50cad337..00000000
--- a/src/reducers/stroke-color.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import log from '../log/log';
-import {CHANGE_SELECTED_ITEMS} from './selected-items';
-import {CHANGE_STROKE_WIDTH} from './stroke-width';
-import {getColorsFromSelection, MIXED} from '../helper/style-path';
-
-const CHANGE_STROKE_COLOR = 'scratch-paint/stroke-color/CHANGE_STROKE_COLOR';
-const initialState = '#000';
-// Matches hex colors
-const regExp = /^#([0-9a-f]{3}){1,2}$/i;
-
-const reducer = function (state, action) {
- if (typeof state === 'undefined') state = initialState;
- switch (action.type) {
- case CHANGE_STROKE_WIDTH:
- if (Math.max(0, action.strokeWidth) === 0) {
- return null;
- }
- return state;
- case CHANGE_STROKE_COLOR:
- if (!regExp.test(action.strokeColor) && action.strokeColor !== null && action.strokeColor !== MIXED) {
- log.warn(`Invalid hex color code: ${action.fillColor}`);
- return state;
- }
- return action.strokeColor;
- case CHANGE_SELECTED_ITEMS:
- // Don't change state if no selection
- if (!action.selectedItems || !action.selectedItems.length) {
- return state;
- }
- // Bitmap mode doesn't have stroke color
- if (action.bitmapMode) {
- return state;
- }
- return getColorsFromSelection(action.selectedItems, action.bitmapMode).strokeColor;
- default:
- return state;
- }
-};
-
-// Action creators ==================================
-const changeStrokeColor = function (strokeColor) {
- return {
- type: CHANGE_STROKE_COLOR,
- strokeColor: strokeColor
- };
-};
-
-export {
- reducer as default,
- changeStrokeColor
-};
diff --git a/src/reducers/stroke-style.js b/src/reducers/stroke-style.js
new file mode 100644
index 00000000..72a15168
--- /dev/null
+++ b/src/reducers/stroke-style.js
@@ -0,0 +1,56 @@
+import makeColorStyleReducer from '../lib/make-color-style-reducer';
+
+const CHANGE_STROKE_COLOR = 'scratch-paint/stroke-style/CHANGE_STROKE_COLOR';
+const CHANGE_STROKE_COLOR_2 = 'scratch-paint/stroke-style/CHANGE_STROKE_COLOR_2';
+const CHANGE_STROKE_GRADIENT_TYPE = 'scratch-paint/stroke-style/CHANGE_STROKE_GRADIENT_TYPE';
+const CLEAR_STROKE_GRADIENT = 'scratch-paint/stroke-style/CLEAR_STROKE_GRADIENT';
+const DEFAULT_COLOR = '#000000';
+
+const reducer = makeColorStyleReducer({
+ changePrimaryColorAction: CHANGE_STROKE_COLOR,
+ changeSecondaryColorAction: CHANGE_STROKE_COLOR_2,
+ changeGradientTypeAction: CHANGE_STROKE_GRADIENT_TYPE,
+ clearGradientAction: CLEAR_STROKE_GRADIENT,
+ defaultColor: DEFAULT_COLOR,
+ selectionPrimaryColorKey: 'strokeColor',
+ selectionSecondaryColorKey: 'strokeColor2',
+ selectionGradientTypeKey: 'strokeGradientType'
+});
+
+// Action creators ==================================
+const changeStrokeColor = function (strokeColor) {
+ return {
+ type: CHANGE_STROKE_COLOR,
+ color: strokeColor
+ };
+};
+
+const changeStrokeColor2 = function (strokeColor) {
+ return {
+ type: CHANGE_STROKE_COLOR_2,
+ color: strokeColor
+ };
+};
+
+const changeStrokeGradientType = function (gradientType) {
+ return {
+ type: CHANGE_STROKE_GRADIENT_TYPE,
+ gradientType
+ };
+};
+
+const clearStrokeGradient = function () {
+ return {
+ type: CLEAR_STROKE_GRADIENT
+ };
+};
+
+export {
+ reducer as default,
+ changeStrokeColor,
+ changeStrokeColor2,
+ changeStrokeGradientType,
+ clearStrokeGradient,
+ DEFAULT_COLOR,
+ CHANGE_STROKE_GRADIENT_TYPE
+};