mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-09 14:12:13 -05:00
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:
parent
620dd6ae49
commit
97f669423a
10 changed files with 101 additions and 9 deletions
|
@ -32,10 +32,14 @@
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-readout {
|
[dir="ltr"] .label-readout {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[dir="rtl"] .label-readout {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.label-name {
|
.label-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +100,14 @@
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gradient-picker-row > img + img {
|
[dir="ltr"] .gradient-picker-row > img + img {
|
||||||
margin-left: calc(2 * $grid-unit);
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -56,7 +56,10 @@ class ColorPickerComponent extends React.Component {
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className={styles.colorPickerContainer}>
|
<div
|
||||||
|
className={styles.colorPickerContainer}
|
||||||
|
dir={this.props.rtl ? 'rtl' : 'ltr'}
|
||||||
|
>
|
||||||
{this.props.shouldShowGradientTools ? (
|
{this.props.shouldShowGradientTools ? (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
|
@ -103,7 +106,12 @@ class ColorPickerComponent extends React.Component {
|
||||||
<div className={styles.divider} />
|
<div className={styles.divider} />
|
||||||
{this.props.gradientType === GradientTypes.SOLID ? null : (
|
{this.props.gradientType === GradientTypes.SOLID ? null : (
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<div className={styles.gradientPickerRow}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.gradientPickerRow,
|
||||||
|
styles.gradientSwatchesRow
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={classNames({
|
className={classNames({
|
||||||
[styles.clickable]: true,
|
[styles.clickable]: true,
|
||||||
|
@ -295,6 +303,7 @@ ColorPickerComponent.propTypes = {
|
||||||
onSelectColor2: PropTypes.func.isRequired,
|
onSelectColor2: PropTypes.func.isRequired,
|
||||||
onSwap: PropTypes.func,
|
onSwap: PropTypes.func,
|
||||||
onTransparent: PropTypes.func.isRequired,
|
onTransparent: PropTypes.func.isRequired,
|
||||||
|
rtl: PropTypes.bool.isRequired,
|
||||||
saturation: PropTypes.number.isRequired,
|
saturation: PropTypes.number.isRequired,
|
||||||
shouldShowGradientTools: PropTypes.bool.isRequired
|
shouldShowGradientTools: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -126,3 +126,8 @@ $border-radius: 0.25rem;
|
||||||
.menu-item-icon {
|
.menu-item-icon {
|
||||||
margin-right: calc(2 * $grid-unit);
|
margin-right: calc(2 * $grid-unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[dir="rtl"] .menu-item-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: calc(2 * $grid-unit);
|
||||||
|
}
|
||||||
|
|
|
@ -237,7 +237,10 @@ const FixedToolsComponent = props => {
|
||||||
className={styles.modUnselect}
|
className={styles.modUnselect}
|
||||||
enterExitTransitionDurationMs={20}
|
enterExitTransitionDurationMs={20}
|
||||||
popoverContent={
|
popoverContent={
|
||||||
<InputGroup className={styles.modContextMenu}>
|
<InputGroup
|
||||||
|
className={styles.modContextMenu}
|
||||||
|
rtl={props.rtl}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
className={classNames(styles.modMenuItem, {
|
className={classNames(styles.modMenuItem, {
|
||||||
[styles.modDisabled]: !shouldShowBringForward()
|
[styles.modDisabled]: !shouldShowBringForward()
|
||||||
|
@ -306,11 +309,13 @@ FixedToolsComponent.propTypes = {
|
||||||
onSendToFront: PropTypes.func.isRequired,
|
onSendToFront: PropTypes.func.isRequired,
|
||||||
onUndo: PropTypes.func.isRequired,
|
onUndo: PropTypes.func.isRequired,
|
||||||
onUngroup: PropTypes.func.isRequired,
|
onUngroup: PropTypes.func.isRequired,
|
||||||
onUpdateName: PropTypes.func.isRequired
|
onUpdateName: PropTypes.func.isRequired,
|
||||||
|
rtl: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
format: state.scratchPaint.format,
|
format: state.scratchPaint.format,
|
||||||
|
rtl: state.scratchPaint.layout.rtl,
|
||||||
selectedItems: state.scratchPaint.selectedItems,
|
selectedItems: state.scratchPaint.selectedItems,
|
||||||
undoState: state.scratchPaint.undo
|
undoState: state.scratchPaint.undo
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ const InputGroup = props => (
|
||||||
className={classNames(props.className, styles.inputGroup, {
|
className={classNames(props.className, styles.inputGroup, {
|
||||||
[styles.disabled]: props.disabled
|
[styles.disabled]: props.disabled
|
||||||
})}
|
})}
|
||||||
|
dir={props.rtl ? 'rtl' : ''}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +18,8 @@ const InputGroup = props => (
|
||||||
InputGroup.propTypes = {
|
InputGroup.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
disabled: PropTypes.bool
|
disabled: PropTypes.bool,
|
||||||
|
rtl: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InputGroup;
|
export default InputGroup;
|
||||||
|
|
|
@ -57,7 +57,10 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const PaintEditorComponent = props => (
|
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
|
{props.canvas !== null ? ( // eslint-disable-line no-negated-condition
|
||||||
<div className={styles.editorContainerTop}>
|
<div className={styles.editorContainerTop}>
|
||||||
{/* First row */}
|
{/* First row */}
|
||||||
|
@ -338,6 +341,7 @@ PaintEditorComponent.propTypes = {
|
||||||
onZoomReset: PropTypes.func.isRequired,
|
onZoomReset: PropTypes.func.isRequired,
|
||||||
rotationCenterX: PropTypes.number,
|
rotationCenterX: PropTypes.number,
|
||||||
rotationCenterY: PropTypes.number,
|
rotationCenterY: PropTypes.number,
|
||||||
|
rtl: PropTypes.bool,
|
||||||
setCanvas: PropTypes.func.isRequired,
|
setCanvas: PropTypes.func.isRequired,
|
||||||
setTextArea: PropTypes.func.isRequired,
|
setTextArea: PropTypes.func.isRequired,
|
||||||
textArea: PropTypes.instanceOf(Element)
|
textArea: PropTypes.instanceOf(Element)
|
||||||
|
|
|
@ -129,6 +129,7 @@ class ColorPicker extends React.Component {
|
||||||
gradientType={this.props.gradientType}
|
gradientType={this.props.gradientType}
|
||||||
hue={this.state.hue}
|
hue={this.state.hue}
|
||||||
isEyeDropping={this.props.isEyeDropping}
|
isEyeDropping={this.props.isEyeDropping}
|
||||||
|
rtl={this.props.rtl}
|
||||||
saturation={this.state.saturation}
|
saturation={this.state.saturation}
|
||||||
shouldShowGradientTools={this.props.shouldShowGradientTools}
|
shouldShowGradientTools={this.props.shouldShowGradientTools}
|
||||||
onActivateEyeDropper={this.handleActivateEyeDropper}
|
onActivateEyeDropper={this.handleActivateEyeDropper}
|
||||||
|
@ -160,12 +161,14 @@ ColorPicker.propTypes = {
|
||||||
onSelectColor: PropTypes.func.isRequired,
|
onSelectColor: PropTypes.func.isRequired,
|
||||||
onSelectColor2: PropTypes.func.isRequired,
|
onSelectColor2: PropTypes.func.isRequired,
|
||||||
onSwap: PropTypes.func,
|
onSwap: PropTypes.func,
|
||||||
|
rtl: PropTypes.bool.isRequired,
|
||||||
shouldShowGradientTools: PropTypes.bool.isRequired
|
shouldShowGradientTools: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
colorIndex: state.scratchPaint.fillMode.colorIndex,
|
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 => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||||
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
import {deactivateEyeDropper} from '../reducers/eye-dropper';
|
||||||
import {setTextEditTarget} from '../reducers/text-edit-target';
|
import {setTextEditTarget} from '../reducers/text-edit-target';
|
||||||
import {updateViewBounds} from '../reducers/view-bounds';
|
import {updateViewBounds} from '../reducers/view-bounds';
|
||||||
|
import {setLayout} from '../reducers/layout';
|
||||||
|
|
||||||
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||||
import {commitSelectionToBitmap, convertToBitmap, convertToVector, getHitBounds} from '../helper/bitmap';
|
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.
|
// 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 gives currently active tools a chance to finish what they were doing.
|
||||||
this.isSwitchingFormats = false;
|
this.isSwitchingFormats = false;
|
||||||
|
this.props.setLayout(this.props.rtl ? 'rtl' : 'ltr');
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
document.addEventListener('keydown', (/* event */) => {
|
document.addEventListener('keydown', (/* event */) => {
|
||||||
|
@ -94,6 +96,9 @@ class PaintEditor extends React.Component {
|
||||||
} else if (isVector(newProps.format) && isBitmap(this.props.format)) {
|
} else if (isVector(newProps.format) && isBitmap(this.props.format)) {
|
||||||
this.switchMode(Formats.VECTOR);
|
this.switchMode(Formats.VECTOR);
|
||||||
}
|
}
|
||||||
|
if (newProps.rtl !== this.props.rtl) {
|
||||||
|
this.props.setLayout(newProps.rtl ? 'rtl' : 'ltr');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (this.props.isEyeDropping && !prevProps.isEyeDropping) {
|
if (this.props.isEyeDropping && !prevProps.isEyeDropping) {
|
||||||
|
@ -386,6 +391,7 @@ class PaintEditor extends React.Component {
|
||||||
name={this.props.name}
|
name={this.props.name}
|
||||||
rotationCenterX={this.props.rotationCenterX}
|
rotationCenterX={this.props.rotationCenterX}
|
||||||
rotationCenterY={this.props.rotationCenterY}
|
rotationCenterY={this.props.rotationCenterY}
|
||||||
|
rtl={this.props.rtl}
|
||||||
setCanvas={this.setCanvas}
|
setCanvas={this.setCanvas}
|
||||||
setTextArea={this.setTextArea}
|
setTextArea={this.setTextArea}
|
||||||
textArea={this.state.textArea}
|
textArea={this.state.textArea}
|
||||||
|
@ -438,6 +444,8 @@ PaintEditor.propTypes = {
|
||||||
removeTextEditTarget: PropTypes.func.isRequired,
|
removeTextEditTarget: PropTypes.func.isRequired,
|
||||||
rotationCenterX: PropTypes.number,
|
rotationCenterX: PropTypes.number,
|
||||||
rotationCenterY: PropTypes.number,
|
rotationCenterY: PropTypes.number,
|
||||||
|
rtl: PropTypes.bool,
|
||||||
|
setLayout: PropTypes.func.isRequired,
|
||||||
setSelectedItems: PropTypes.func.isRequired,
|
setSelectedItems: PropTypes.func.isRequired,
|
||||||
textEditing: PropTypes.bool.isRequired,
|
textEditing: PropTypes.bool.isRequired,
|
||||||
undoSnapshot: PropTypes.func.isRequired,
|
undoSnapshot: PropTypes.func.isRequired,
|
||||||
|
@ -499,6 +507,9 @@ const mapDispatchToProps = dispatch => ({
|
||||||
removeTextEditTarget: () => {
|
removeTextEditTarget: () => {
|
||||||
dispatch(setTextEditTarget());
|
dispatch(setTextEditTarget());
|
||||||
},
|
},
|
||||||
|
setLayout: layout => {
|
||||||
|
dispatch(setLayout(layout));
|
||||||
|
},
|
||||||
setSelectedItems: format => {
|
setSelectedItems: format => {
|
||||||
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
dispatch(setSelectedItems(getSelectedLeafItems(), isBitmap(format)));
|
||||||
},
|
},
|
||||||
|
|
39
src/reducers/layout.js
Normal file
39
src/reducers/layout.js
Normal 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
|
||||||
|
};
|
|
@ -11,6 +11,7 @@ import fillModeReducer from './fill-mode';
|
||||||
import fontReducer from './font';
|
import fontReducer from './font';
|
||||||
import formatReducer from './format';
|
import formatReducer from './format';
|
||||||
import hoverReducer from './hover';
|
import hoverReducer from './hover';
|
||||||
|
import layoutReducer from './layout';
|
||||||
import modalsReducer from './modals';
|
import modalsReducer from './modals';
|
||||||
import selectedItemReducer from './selected-items';
|
import selectedItemReducer from './selected-items';
|
||||||
import textEditTargetReducer from './text-edit-target';
|
import textEditTargetReducer from './text-edit-target';
|
||||||
|
@ -30,6 +31,7 @@ export default combineReducers({
|
||||||
font: fontReducer,
|
font: fontReducer,
|
||||||
format: formatReducer,
|
format: formatReducer,
|
||||||
hoveredItemId: hoverReducer,
|
hoveredItemId: hoverReducer,
|
||||||
|
layout: layoutReducer,
|
||||||
modals: modalsReducer,
|
modals: modalsReducer,
|
||||||
selectedItems: selectedItemReducer,
|
selectedItems: selectedItemReducer,
|
||||||
textEditTarget: textEditTargetReducer,
|
textEditTarget: textEditTargetReducer,
|
||||||
|
|
Loading…
Reference in a new issue