mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2025-01-08 13:42:00 -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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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
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 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,
|
||||
|
|
Loading…
Reference in a new issue