Make PaintEditor component rtl aware (#617)

* Make PaintEditor component rtl aware

Fixes the color-picker and any other popover elements.

Adds an RTL prop to the PaintEditor that initializes the `layout` state in redux. Any other components that need to know the layout refer to the state in redux.

I debated whether the state should just be a boolean (true for RTL), or ‘rtl’, ‘ltr’. I went with the latter, but could be convinced that boolean would be better.

I did not add `dir=“rtl”` to the font picker dropdown as all the names are in LTR languages.

Question: Should the sliders reverse direction, and if so, is it worth doing right now when the layout of the color picker may change.

Adding the rtl prop and the `dir=…` to the PaintEditorComponent fixes layout issues in the playground.

* Don’t reverse gradient swatches: the fill colors (and swatch preview) should represent the way the left-right colors will blend when filling a shape.
This commit is contained in:
chrisgarrity 2018-08-23 19:08:56 -04:00 committed by GitHub
parent 620dd6ae49
commit 97f669423a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 101 additions and 9 deletions

View file

@ -32,10 +32,14 @@
margin: 8px;
}
.label-readout {
[dir="ltr"] .label-readout {
margin-left: 10px;
}
[dir="rtl"] .label-readout {
margin-right: 10px;
}
.label-name {
font-weight: bold;
}
@ -96,6 +100,14 @@
margin: 8px;
}
.gradient-picker-row > img + img {
[dir="ltr"] .gradient-picker-row > img + img {
margin-left: calc(2 * $grid-unit);
}
[dir="rtl"] .gradient-picker-row > img + img {
margin-right: calc(2 * $grid-unit);
}
[dir="rtl"] .gradient-swatches-row {
flex-direction: row-reverse;
}

View file

@ -56,7 +56,10 @@ class ColorPickerComponent extends React.Component {
}
render () {
return (
<div className={styles.colorPickerContainer}>
<div
className={styles.colorPickerContainer}
dir={this.props.rtl ? 'rtl' : 'ltr'}
>
{this.props.shouldShowGradientTools ? (
<div>
<div className={styles.row}>
@ -103,7 +106,12 @@ class ColorPickerComponent extends React.Component {
<div className={styles.divider} />
{this.props.gradientType === GradientTypes.SOLID ? null : (
<div className={styles.row}>
<div className={styles.gradientPickerRow}>
<div
className={classNames(
styles.gradientPickerRow,
styles.gradientSwatchesRow
)}
>
<div
className={classNames({
[styles.clickable]: true,
@ -295,6 +303,7 @@ ColorPickerComponent.propTypes = {
onSelectColor2: PropTypes.func.isRequired,
onSwap: PropTypes.func,
onTransparent: PropTypes.func.isRequired,
rtl: PropTypes.bool.isRequired,
saturation: PropTypes.number.isRequired,
shouldShowGradientTools: PropTypes.bool.isRequired
};

View file

@ -126,3 +126,8 @@ $border-radius: 0.25rem;
.menu-item-icon {
margin-right: calc(2 * $grid-unit);
}
[dir="rtl"] .menu-item-icon {
margin-right: 0;
margin-left: calc(2 * $grid-unit);
}

View file

@ -237,7 +237,10 @@ const FixedToolsComponent = props => {
className={styles.modUnselect}
enterExitTransitionDurationMs={20}
popoverContent={
<InputGroup className={styles.modContextMenu}>
<InputGroup
className={styles.modContextMenu}
rtl={props.rtl}
>
<Button
className={classNames(styles.modMenuItem, {
[styles.modDisabled]: !shouldShowBringForward()
@ -306,11 +309,13 @@ FixedToolsComponent.propTypes = {
onSendToFront: PropTypes.func.isRequired,
onUndo: PropTypes.func.isRequired,
onUngroup: PropTypes.func.isRequired,
onUpdateName: PropTypes.func.isRequired
onUpdateName: PropTypes.func.isRequired,
rtl: PropTypes.bool.isRequired
};
const mapStateToProps = state => ({
format: state.scratchPaint.format,
rtl: state.scratchPaint.layout.rtl,
selectedItems: state.scratchPaint.selectedItems,
undoState: state.scratchPaint.undo
});

View file

@ -9,6 +9,7 @@ const InputGroup = props => (
className={classNames(props.className, styles.inputGroup, {
[styles.disabled]: props.disabled
})}
dir={props.rtl ? 'rtl' : ''}
>
{props.children}
</div>
@ -17,7 +18,8 @@ const InputGroup = props => (
InputGroup.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool
disabled: PropTypes.bool,
rtl: PropTypes.bool
};
export default InputGroup;

View file

@ -57,7 +57,10 @@ const messages = defineMessages({
});
const PaintEditorComponent = props => (
<div className={styles.editorContainer}>
<div
className={styles.editorContainer}
dir={props.rtl ? 'rtl' : 'ltr'}
>
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
<div className={styles.editorContainerTop}>
{/* First row */}
@ -338,6 +341,7 @@ PaintEditorComponent.propTypes = {
onZoomReset: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
rtl: PropTypes.bool,
setCanvas: PropTypes.func.isRequired,
setTextArea: PropTypes.func.isRequired,
textArea: PropTypes.instanceOf(Element)

View file

@ -129,6 +129,7 @@ class ColorPicker extends React.Component {
gradientType={this.props.gradientType}
hue={this.state.hue}
isEyeDropping={this.props.isEyeDropping}
rtl={this.props.rtl}
saturation={this.state.saturation}
shouldShowGradientTools={this.props.shouldShowGradientTools}
onActivateEyeDropper={this.handleActivateEyeDropper}
@ -160,12 +161,14 @@ ColorPicker.propTypes = {
onSelectColor: PropTypes.func.isRequired,
onSelectColor2: PropTypes.func.isRequired,
onSwap: PropTypes.func,
rtl: PropTypes.bool.isRequired,
shouldShowGradientTools: PropTypes.bool.isRequired
};
const mapStateToProps = state => ({
colorIndex: state.scratchPaint.fillMode.colorIndex,
isEyeDropping: state.scratchPaint.color.eyeDropper.active
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
rtl: state.scratchPaint.layout.rtl
});
const mapDispatchToProps = dispatch => ({

View file

@ -13,6 +13,7 @@ import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
import {deactivateEyeDropper} from '../reducers/eye-dropper';
import {setTextEditTarget} from '../reducers/text-edit-target';
import {updateViewBounds} from '../reducers/view-bounds';
import {setLayout} from '../reducers/layout';
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
import {commitSelectionToBitmap, convertToBitmap, convertToVector, getHitBounds} from '../helper/bitmap';
@ -69,6 +70,7 @@ class PaintEditor extends React.Component {
// When isSwitchingFormats is true, the format is about to switch, but isn't done switching.
// This gives currently active tools a chance to finish what they were doing.
this.isSwitchingFormats = false;
this.props.setLayout(this.props.rtl ? 'rtl' : 'ltr');
}
componentDidMount () {
document.addEventListener('keydown', (/* event */) => {
@ -94,6 +96,9 @@ class PaintEditor extends React.Component {
} else if (isVector(newProps.format) && isBitmap(this.props.format)) {
this.switchMode(Formats.VECTOR);
}
if (newProps.rtl !== this.props.rtl) {
this.props.setLayout(newProps.rtl ? 'rtl' : 'ltr');
}
}
componentDidUpdate (prevProps) {
if (this.props.isEyeDropping && !prevProps.isEyeDropping) {
@ -386,6 +391,7 @@ class PaintEditor extends React.Component {
name={this.props.name}
rotationCenterX={this.props.rotationCenterX}
rotationCenterY={this.props.rotationCenterY}
rtl={this.props.rtl}
setCanvas={this.setCanvas}
setTextArea={this.setTextArea}
textArea={this.state.textArea}
@ -438,6 +444,8 @@ PaintEditor.propTypes = {
removeTextEditTarget: PropTypes.func.isRequired,
rotationCenterX: PropTypes.number,
rotationCenterY: PropTypes.number,
rtl: PropTypes.bool,
setLayout: PropTypes.func.isRequired,
setSelectedItems: PropTypes.func.isRequired,
textEditing: PropTypes.bool.isRequired,
undoSnapshot: PropTypes.func.isRequired,
@ -499,6 +507,9 @@ const mapDispatchToProps = dispatch => ({
removeTextEditTarget: () => {
dispatch(setTextEditTarget());
},
setLayout: layout => {
dispatch(setLayout(layout));
},
setSelectedItems: format => {
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
},

39
src/reducers/layout.js Normal file
View file

@ -0,0 +1,39 @@
import log from '../log/log';
const SET_LAYOUT = 'scratch-paint/layout/SET_LAYOUT';
const initialState = {rtl: false};
const layouts = ['ltr', 'rtl'];
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case SET_LAYOUT:
if (layouts.indexOf(action.layout) === -1) {
log.warn(`Unrecognized layout provided: ${action.layout}`);
return state;
}
return {rtl: action.layout === 'rtl'};
default:
return state;
}
};
// Action creators ==================================
/**
* Change the layout to the new layout
* @param {string} layout either 'ltr' or 'rtl'
* @return {object} Redux action to change the selected items.
*/
const setLayout = function (layout) {
return {
type: SET_LAYOUT,
layout: layout
};
};
export {
reducer as default,
setLayout,
SET_LAYOUT
};

View file

@ -11,6 +11,7 @@ import fillModeReducer from './fill-mode';
import fontReducer from './font';
import formatReducer from './format';
import hoverReducer from './hover';
import layoutReducer from './layout';
import modalsReducer from './modals';
import selectedItemReducer from './selected-items';
import textEditTargetReducer from './text-edit-target';
@ -30,6 +31,7 @@ export default combineReducers({
font: fontReducer,
format: formatReducer,
hoveredItemId: hoverReducer,
layout: layoutReducer,
modals: modalsReducer,
selectedItems: selectedItemReducer,
textEditTarget: textEditTargetReducer,