Merge pull request #112 from paulkaplan/transparent-state

Fix several color state inconsistencies
This commit is contained in:
Paul Kaplan 2017-10-27 08:20:09 -04:00 committed by GitHub
commit c1ce433f72
17 changed files with 185 additions and 12 deletions

View file

@ -5,6 +5,7 @@
} }
.color-button-swatch { .color-button-swatch {
position: relative;
display: flex; display: flex;
flex-basis: 2rem; flex-basis: 2rem;
flex-shrink: 0; flex-shrink: 0;
@ -30,3 +31,23 @@
color: #575e75; color: #575e75;
font-size: 0.75rem; font-size: 0.75rem;
} }
.swatch-icon {
width: 1.75rem;
margin: auto;
/* Make sure it appears above the outline box */
z-index: 2;
}
.outline-swatch:after {
content: "";
position: absolute;
top: calc(0.5rem + 1px);
left: calc(0.5rem + 1px);
width: 0.75rem;
height: 0.75rem;
background: white;
border: 1px solid rgba(0, 0, 0, 0.25);
/* Make sure it appears below the transparent icon */
z-index: 1;
}

View file

@ -1,26 +1,55 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames';
import {MIXED} from '../../helper/style-path';
import noFillIcon from './no-fill.svg';
import mixedFillIcon from './mixed-fill.svg';
import styles from './color-button.css'; import styles from './color-button.css';
const colorToBackground = color => {
if (color === MIXED || color === null) return 'white';
return color;
};
const ColorButtonComponent = props => ( const ColorButtonComponent = props => (
<div <div
className={styles.colorButton} className={styles.colorButton}
onClick={props.onClick} onClick={props.onClick}
> >
<div <div
className={styles.colorButtonSwatch} className={classNames(styles.colorButtonSwatch, {
[styles.outlineSwatch]: props.outline
})}
style={{ style={{
background: props.color background: colorToBackground(props.color)
}} }}
>
{props.color === null ? (
<img
className={styles.swatchIcon}
src={noFillIcon}
/> />
) : ((props.color === MIXED ? (
<img
className={styles.swatchIcon}
src={mixedFillIcon}
/>
) : null))}
</div>
<div className={styles.colorButtonArrow}></div> <div className={styles.colorButtonArrow}></div>
</div> </div>
); );
ColorButtonComponent.propTypes = { ColorButtonComponent.propTypes = {
color: PropTypes.string, color: PropTypes.string,
onClick: PropTypes.func.isRequired onClick: PropTypes.func.isRequired,
outline: PropTypes.bool.isRequired
};
ColorButtonComponent.defaultProps = {
outline: false
}; };
export default ColorButtonComponent; export default ColorButtonComponent;

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>mixed-fill</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.75">
<g id="mixed-fill">
<g id="mixed-fill-icon" transform="translate(2.000000, 2.500000)">
<circle id="blue" fill="#4C97FF" cx="4.5" cy="10.5" r="4.5"></circle>
<circle id="red" fill="#FF5500" cx="8" cy="4.5" r="4.5"></circle>
<circle id="yellow" fill="#FFBF00" cx="11.4099998" cy="10.5" r="4.5"></circle>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 888 B

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>no-fill</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
<g id="no-fill" stroke="#FF661A" stroke-width="2">
<path d="M3,17 L17,3" id="Line"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 612 B

View file

@ -48,3 +48,7 @@
border: 1px solid #4C97FF; border: 1px solid #4C97FF;
box-shadow: 0px 0px 0px 3px hsla(215, 100%, 65%, 0.2); box-shadow: 0px 0px 0px 3px hsla(215, 100%, 65%, 0.2);
} }
.swatch > img {
max-width: 100%;
}

View file

@ -9,6 +9,7 @@ import {MIXED} from '../../helper/style-path';
import Slider from '../forms/slider.jsx'; import Slider from '../forms/slider.jsx';
import styles from './color-picker.css'; import styles from './color-picker.css';
import noFillIcon from '../color-button/no-fill.svg';
const colorStringToHsv = hexString => { const colorStringToHsv = hexString => {
const hsv = parseColor(hexString).hsv; const hsv = parseColor(hexString).hsv;
@ -179,7 +180,9 @@ class ColorPickerComponent extends React.Component {
[styles.activeSwatch]: this.props.color === null [styles.activeSwatch]: this.props.color === null
})} })}
onClick={this.handleTransparent} onClick={this.handleTransparent}
/> >
<img src={noFillIcon} />
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -17,7 +17,7 @@ const messages = defineMessages({
}); });
const FillColorIndicatorComponent = props => ( const FillColorIndicatorComponent = props => (
<InputGroup> <InputGroup disabled={props.disabled}>
<Popover <Popover
body={ body={
<ColorPicker <ColorPicker
@ -40,6 +40,7 @@ const FillColorIndicatorComponent = props => (
); );
FillColorIndicatorComponent.propTypes = { FillColorIndicatorComponent.propTypes = {
disabled: PropTypes.bool.isRequired,
fillColor: PropTypes.string, fillColor: PropTypes.string,
fillColorModalVisible: PropTypes.bool.isRequired, fillColorModalVisible: PropTypes.bool.isRequired,
intl: intlShape, intl: intlShape,

View file

@ -3,3 +3,9 @@
.input-group + .input-group { .input-group + .input-group {
margin-left: calc(3 * $grid-unit); margin-left: calc(3 * $grid-unit);
} }
.disabled {
opacity: 0.3;
/* Prevent any user actions */
pointer-events: none;
}

View file

@ -5,14 +5,19 @@ import PropTypes from 'prop-types';
import styles from './input-group.css'; import styles from './input-group.css';
const InputGroup = props => ( const InputGroup = props => (
<div className={classNames(props.className, styles.inputGroup)}> <div
className={classNames(props.className, styles.inputGroup, {
[styles.disabled]: props.disabled
})}
>
{props.children} {props.children}
</div> </div>
); );
InputGroup.propTypes = { InputGroup.propTypes = {
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
className: PropTypes.string className: PropTypes.string,
disabled: PropTypes.bool
}; };
export default InputGroup; export default InputGroup;

View file

@ -17,7 +17,7 @@ const messages = defineMessages({
}); });
const StrokeColorIndicatorComponent = props => ( const StrokeColorIndicatorComponent = props => (
<InputGroup> <InputGroup disabled={props.disabled}>
<Popover <Popover
body={ body={
<ColorPicker <ColorPicker
@ -31,6 +31,7 @@ const StrokeColorIndicatorComponent = props => (
> >
<Label text={props.intl.formatMessage(messages.stroke)}> <Label text={props.intl.formatMessage(messages.stroke)}>
<ColorButton <ColorButton
outline
color={props.strokeColor} color={props.strokeColor}
onClick={props.onOpenStrokeColor} onClick={props.onOpenStrokeColor}
/> />
@ -40,6 +41,7 @@ const StrokeColorIndicatorComponent = props => (
); );
StrokeColorIndicatorComponent.propTypes = { StrokeColorIndicatorComponent.propTypes = {
disabled: PropTypes.bool.isRequired,
intl: intlShape, intl: intlShape,
onChangeStrokeColor: PropTypes.func.isRequired, onChangeStrokeColor: PropTypes.func.isRequired,
onCloseStrokeColor: PropTypes.func.isRequired, onCloseStrokeColor: PropTypes.func.isRequired,

View file

@ -9,9 +9,10 @@ import {MAX_STROKE_WIDTH} from '../reducers/stroke-width';
const BufferedInput = BufferedInputHOC(Input); const BufferedInput = BufferedInputHOC(Input);
const StrokeWidthIndicatorComponent = props => ( const StrokeWidthIndicatorComponent = props => (
<InputGroup> <InputGroup disabled={props.disabled}>
<BufferedInput <BufferedInput
small small
disabled={props.disabled}
max={MAX_STROKE_WIDTH} max={MAX_STROKE_WIDTH}
min="0" min="0"
type="number" type="number"
@ -22,6 +23,7 @@ const StrokeWidthIndicatorComponent = props => (
); );
StrokeWidthIndicatorComponent.propTypes = { StrokeWidthIndicatorComponent.propTypes = {
disabled: PropTypes.bool.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired, onChangeStrokeWidth: PropTypes.func.isRequired,
strokeWidth: PropTypes.number strokeWidth: PropTypes.number
}; };

View file

@ -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 Blobbiness from '../helper/blob-tools/blob'; import Blobbiness from '../helper/blob-tools/blob';
import {MIXED} from '../helper/style-path';
import {changeFillColor} from '../reducers/fill-color';
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 {clearSelectedItems} from '../reducers/selected-items';
@ -13,6 +15,9 @@ import {clearSelection} from '../helper/selection';
import BrushModeComponent from '../components/brush-mode/brush-mode.jsx'; import BrushModeComponent from '../components/brush-mode/brush-mode.jsx';
class BrushMode extends React.Component { class BrushMode extends React.Component {
static get DEFAULT_COLOR () {
return '#9966FF';
}
constructor (props) { constructor (props) {
super(props); super(props);
bindAll(this, [ bindAll(this, [
@ -49,6 +54,12 @@ class BrushMode extends React.Component {
// analogous to how selection works with eraser // analogous to how selection works with eraser
clearSelection(this.props.clearSelectedItems); clearSelection(this.props.clearSelectedItems);
// Force the default brush color if fill is MIXED or transparent
const {fillColor} = this.props.colorState;
if (fillColor === MIXED || fillColor === null) {
this.props.onChangeFillColor(BrushMode.DEFAULT_COLOR);
}
// 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);
this.blob.activateTool({ this.blob.activateTool({
@ -93,6 +104,7 @@ BrushMode.propTypes = {
}).isRequired, }).isRequired,
handleMouseDown: PropTypes.func.isRequired, handleMouseDown: PropTypes.func.isRequired,
isBrushModeActive: PropTypes.bool.isRequired, isBrushModeActive: PropTypes.bool.isRequired,
onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired onUpdateSvg: PropTypes.func.isRequired
}; };
@ -110,6 +122,9 @@ const mapDispatchToProps = dispatch => ({
}, },
handleMouseDown: () => { handleMouseDown: () => {
dispatch(changeMode(Modes.BRUSH)); dispatch(changeMode(Modes.BRUSH));
},
onChangeFillColor: fillColor => {
dispatch(changeFillColor(fillColor));
} }
}); });

View file

@ -4,6 +4,7 @@ import React from 'react';
import bindAll from 'lodash.bindall'; import bindAll from 'lodash.bindall';
import {changeFillColor} from '../reducers/fill-color'; import {changeFillColor} from '../reducers/fill-color';
import {openFillColor, closeFillColor} from '../reducers/modals'; import {openFillColor, closeFillColor} from '../reducers/modals';
import Modes from '../modes/modes';
import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx'; import FillColorIndicatorComponent from '../components/fill-color-indicator.jsx';
import {applyFillColorToSelection} from '../helper/style-path'; import {applyFillColorToSelection} from '../helper/style-path';
@ -30,6 +31,7 @@ class FillColorIndicator extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
disabled: state.scratchPaint.mode === Modes.PEN,
fillColor: state.scratchPaint.color.fillColor, fillColor: state.scratchPaint.color.fillColor,
fillColorModalVisible: state.scratchPaint.modals.fillColor fillColorModalVisible: state.scratchPaint.modals.fillColor
}); });
@ -47,6 +49,7 @@ const mapDispatchToProps = dispatch => ({
}); });
FillColorIndicator.propTypes = { FillColorIndicator.propTypes = {
disabled: PropTypes.bool.isRequired,
fillColor: PropTypes.string, fillColor: PropTypes.string,
onChangeFillColor: PropTypes.func.isRequired, onChangeFillColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired onUpdateSvg: PropTypes.func.isRequired

View file

@ -8,8 +8,11 @@ import {clearSelection} from '../helper/selection';
import {endPointHit, touching} from '../helper/snapping'; import {endPointHit, touching} from '../helper/snapping';
import {drawHitPoint, removeHitPoint} from '../helper/guides'; import {drawHitPoint, removeHitPoint} from '../helper/guides';
import {stylePath} from '../helper/style-path'; import {stylePath} from '../helper/style-path';
import {changeStrokeColor} from '../reducers/stroke-color';
import {changeStrokeWidth} from '../reducers/stroke-width';
import {changeMode} from '../reducers/modes'; import {changeMode} from '../reducers/modes';
import {clearSelectedItems} from '../reducers/selected-items'; import {clearSelectedItems} from '../reducers/selected-items';
import {MIXED} from '../helper/style-path';
import LineModeComponent from '../components/line-mode/line-mode.jsx'; import LineModeComponent from '../components/line-mode/line-mode.jsx';
@ -17,6 +20,9 @@ class LineMode extends React.Component {
static get SNAP_TOLERANCE () { static get SNAP_TOLERANCE () {
return 6; return 6;
} }
static get DEFAULT_COLOR () {
return '#000000';
}
constructor (props) { constructor (props) {
super(props); super(props);
bindAll(this, [ bindAll(this, [
@ -46,6 +52,16 @@ class LineMode extends React.Component {
} }
activateTool () { activateTool () {
clearSelection(this.props.clearSelectedItems); clearSelection(this.props.clearSelectedItems);
// Force the default line color if stroke is MIXED or transparent
const {strokeColor} = this.props.colorState;
if (strokeColor === MIXED || strokeColor === null) {
this.props.onChangeStrokeColor(LineMode.DEFAULT_COLOR);
}
// Force a minimum stroke width
if (!this.props.colorState.strokeWidth) {
this.props.onChangeStrokeWidth(1);
}
this.tool = new paper.Tool(); this.tool = new paper.Tool();
this.path = null; this.path = null;
@ -220,6 +236,8 @@ LineMode.propTypes = {
}).isRequired, }).isRequired,
handleMouseDown: PropTypes.func.isRequired, handleMouseDown: PropTypes.func.isRequired,
isLineModeActive: PropTypes.bool.isRequired, isLineModeActive: PropTypes.bool.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired onUpdateSvg: PropTypes.func.isRequired
}; };
@ -233,6 +251,12 @@ const mapDispatchToProps = dispatch => ({
}, },
handleMouseDown: () => { handleMouseDown: () => {
dispatch(changeMode(Modes.LINE)); dispatch(changeMode(Modes.LINE));
},
onChangeStrokeColor: strokeColor => {
dispatch(changeStrokeColor(strokeColor));
},
onChangeStrokeWidth: strokeWidth => {
dispatch(changeStrokeWidth(strokeWidth));
} }
}); });

View file

@ -4,14 +4,20 @@ 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 {changeStrokeColor} from '../reducers/stroke-color';
import {changeStrokeWidth} from '../reducers/stroke-width';
import {changeMode} from '../reducers/modes'; import {changeMode} from '../reducers/modes';
import {clearSelectedItems} from '../reducers/selected-items'; import {clearSelectedItems} from '../reducers/selected-items';
import {MIXED} from '../helper/style-path';
import {clearSelection} from '../helper/selection'; import {clearSelection} from '../helper/selection';
import PenTool from '../helper/tools/pen-tool'; import PenTool from '../helper/tools/pen-tool';
import PenModeComponent from '../components/pen-mode/pen-mode.jsx'; import PenModeComponent from '../components/pen-mode/pen-mode.jsx';
class PenMode extends React.Component { class PenMode extends React.Component {
static get DEFAULT_COLOR () {
return '#000000';
}
constructor (props) { constructor (props) {
super(props); super(props);
bindAll(this, [ bindAll(this, [
@ -42,6 +48,15 @@ class PenMode extends React.Component {
} }
activateTool () { activateTool () {
clearSelection(this.props.clearSelectedItems); clearSelection(this.props.clearSelectedItems);
// Force the default pen color if stroke is MIXED or transparent
const {strokeColor} = this.props.colorState;
if (strokeColor === MIXED || strokeColor === null) {
this.props.onChangeStrokeColor(PenMode.DEFAULT_COLOR);
}
// Force a minimum stroke width
if (!this.props.colorState.strokeWidth) {
this.props.onChangeStrokeWidth(1);
}
this.tool = new PenTool( this.tool = new PenTool(
this.props.clearSelectedItems, this.props.clearSelectedItems,
this.props.onUpdateSvg this.props.onUpdateSvg
@ -73,6 +88,8 @@ PenMode.propTypes = {
}).isRequired, }).isRequired,
handleMouseDown: PropTypes.func.isRequired, handleMouseDown: PropTypes.func.isRequired,
isPenModeActive: PropTypes.bool.isRequired, isPenModeActive: PropTypes.bool.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired onUpdateSvg: PropTypes.func.isRequired
}; };
@ -89,6 +106,12 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeMode(Modes.PEN)); dispatch(changeMode(Modes.PEN));
}, },
deactivateTool () { deactivateTool () {
},
onChangeStrokeColor: strokeColor => {
dispatch(changeStrokeColor(strokeColor));
},
onChangeStrokeWidth: strokeWidth => {
dispatch(changeStrokeWidth(strokeWidth));
} }
}); });

View file

@ -4,6 +4,7 @@ import React from 'react';
import bindAll from 'lodash.bindall'; import bindAll from 'lodash.bindall';
import {changeStrokeColor} from '../reducers/stroke-color'; import {changeStrokeColor} from '../reducers/stroke-color';
import {openStrokeColor, closeStrokeColor} from '../reducers/modals'; import {openStrokeColor, closeStrokeColor} from '../reducers/modals';
import Modes from '../modes/modes';
import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx'; import StrokeColorIndicatorComponent from '../components/stroke-color-indicator.jsx';
import {applyStrokeColorToSelection} from '../helper/style-path'; import {applyStrokeColorToSelection} from '../helper/style-path';
@ -30,6 +31,7 @@ class StrokeColorIndicator extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
disabled: state.scratchPaint.mode === Modes.BRUSH,
strokeColor: state.scratchPaint.color.strokeColor, strokeColor: state.scratchPaint.color.strokeColor,
strokeColorModalVisible: state.scratchPaint.modals.strokeColor strokeColorModalVisible: state.scratchPaint.modals.strokeColor
}); });
@ -47,6 +49,7 @@ const mapDispatchToProps = dispatch => ({
}); });
StrokeColorIndicator.propTypes = { StrokeColorIndicator.propTypes = {
disabled: PropTypes.bool.isRequired,
onChangeStrokeColor: PropTypes.func.isRequired, onChangeStrokeColor: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired, onUpdateSvg: PropTypes.func.isRequired,
strokeColor: PropTypes.string strokeColor: PropTypes.string

View file

@ -5,6 +5,7 @@ import bindAll from 'lodash.bindall';
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'; import {applyStrokeWidthToSelection} from '../helper/style-path';
import Modes from '../modes/modes';
class StrokeWidthIndicator extends React.Component { class StrokeWidthIndicator extends React.Component {
constructor (props) { constructor (props) {
@ -20,6 +21,7 @@ class StrokeWidthIndicator extends React.Component {
render () { render () {
return ( return (
<StrokeWidthIndicatorComponent <StrokeWidthIndicatorComponent
disabled={this.props.disabled}
strokeWidth={this.props.strokeWidth} strokeWidth={this.props.strokeWidth}
onChangeStrokeWidth={this.handleChangeStrokeWidth} onChangeStrokeWidth={this.handleChangeStrokeWidth}
/> />
@ -28,6 +30,7 @@ class StrokeWidthIndicator extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
disabled: state.scratchPaint.mode === Modes.BRUSH,
strokeWidth: state.scratchPaint.color.strokeWidth strokeWidth: state.scratchPaint.color.strokeWidth
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
@ -37,6 +40,7 @@ const mapDispatchToProps = dispatch => ({
}); });
StrokeWidthIndicator.propTypes = { StrokeWidthIndicator.propTypes = {
disabled: PropTypes.bool.isRequired,
onChangeStrokeWidth: PropTypes.func.isRequired, onChangeStrokeWidth: PropTypes.func.isRequired,
onUpdateSvg: PropTypes.func.isRequired, onUpdateSvg: PropTypes.func.isRequired,
strokeWidth: PropTypes.number strokeWidth: PropTypes.number