Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(xod-client): fix bug of ColorPinWidget and ColorPicker with varia…
Browse files Browse the repository at this point in the history
…dic inputs and avoid rendering a multiple ColorPickerWidgets at one time
brusherru committed Feb 13, 2020
1 parent aa33137 commit 4b78155
Showing 10 changed files with 122 additions and 134 deletions.
4 changes: 2 additions & 2 deletions packages/xod-client/src/editor/actions.js
Original file line number Diff line number Diff line change
@@ -902,9 +902,9 @@ export const sendTweakPulse = tweakNodeId => (dispatch, getState) => {
);
};

export const showColorPickerWidget = elementId => ({
export const showColorPickerWidget = widgetId => ({
type: ActionType.SHOW_COLORPICKER_WIDGET,
payload: { elementId },
payload: { widgetId },
});

export const hideColorPickerWidget = () => ({
Original file line number Diff line number Diff line change
@@ -42,16 +42,16 @@ class SatLightBox extends React.Component {
this.unbindHandlers = this.unbindHandlers.bind(this);
}
unbindHandlers() {
document.addEventListener('mousemove', this.handleMove);
document.addEventListener('mouseup', this.handleEnd);
document.addEventListener('dragstart', preventDefaultOnly);
document.removeEventListener('mousemove', this.handleMove);
document.removeEventListener('mouseup', this.handleEnd);
document.removeEventListener('dragstart', preventDefaultOnly);
}

handleStart() {
this.setState({ dragging: true });
document.addEventListener('mousemove', this.handleMove);
document.addEventListener('mouseup', this.handleEnd);
document.removeEventListener('dragstart', preventDefaultOnly);
document.addEventListener('dragstart', preventDefaultOnly);
}

handleMove(event) {
Original file line number Diff line number Diff line change
@@ -110,7 +110,7 @@ class ColorPicker extends React.Component {
<div className="ColorPicker_values">
<div>
<input
id="ColorPicker_Hue"
id={`ColorPicker_Hue_${this.props.widgetId}`}
value={hue}
onChange={this.onHueInputChange}
onBlur={this.commitInputs}
@@ -120,7 +120,7 @@ class ColorPicker extends React.Component {
</div>
<div>
<input
id="ColorPicker_Saturation"
id={`ColorPicker_Saturation_${this.props.widgetId}`}
value={saturation}
onChange={this.onSaturationInputChange}
onBlur={this.commitInputs}
@@ -130,7 +130,7 @@ class ColorPicker extends React.Component {
</div>
<div>
<input
id="ColorPicker_Lightness"
id={`ColorPicker_Lightness_${this.props.widgetId}`}
value={lightness}
onChange={this.onLightnessInputChange}
onBlur={this.commitInputs}
@@ -145,6 +145,7 @@ class ColorPicker extends React.Component {
}

ColorPicker.propTypes = {
widgetId: PropTypes.string.isRequired,
color: colorPropType,
onChange: PropTypes.func,
};
11 changes: 10 additions & 1 deletion packages/xod-client/src/editor/components/PointingPopup.jsx
Original file line number Diff line number Diff line change
@@ -11,6 +11,13 @@ import CloseButton from '../../core/components/CloseButton';
// might be hidden outside the viewbox of the container.
const ALLOWED_OFFSET = 5;

// :: String -> String
// It makes a safe selector for `querySelector`.
// For example, some variadic inputs have an id, like:
// `#widget_asdasda-$1`, which is not a valid querySelector
// and we have to escap the `$` symbol.
const safeSelector = R.replace(/(\$)/g, '\\$1');

const getRelativeOffsetTop = (containerEl, el, offset = 0) => {
if (el === containerEl) return offset;
if (el.tagName === 'BODY') return 0;
@@ -88,7 +95,9 @@ class PointingPopup extends React.Component {
}
onUpdatePosition() {
if (!this.ref || !this.props.isVisible) return;
const item = document.querySelector(this.props.selectorPointingAt);
const item = document.querySelector(
safeSelector(this.props.selectorPointingAt)
);
if (!item) return;
const container = item.closest('.inner-container');
const position = calculatePointingPopupPosition(container, item);
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';

import PointingPopup from '../PointingPopup';
import ColorPicker from '../ColorPicker';
import colorPropType from '../ColorPicker/colorPropType';

class ColorPickerWidget extends React.Component {
constructor(props) {
super(props);

this.state = {
color: props.color,
};

this.onChange = this.onChange.bind(this);
}

componentDidUpdate(prevProps) {
if (
prevProps.color.hex !== this.props.color.hex &&
this.state.color.hex !== this.props.color.hex
) {
// Update the color stored in the state only if it changed
// outside the ColorPicker.
// E.G. user types the new hex color in the input.
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ color: this.props.color });
}
}

onChange(color) {
this.setState({ color });
this.props.onChange(color);
}

render() {
return (
<PointingPopup
className="ColorPickerWidget"
isVisible={this.props.isVisible}
selectorPointingAt={`#${this.props.widgetId}`}
hidePopup={this.props.onClose}
>
<ColorPicker
widgetId={this.props.widgetId}
color={this.state.color}
onChange={this.onChange}
/>
</PointingPopup>
);
}
}

ColorPickerWidget.propTypes = {
color: colorPropType,
isVisible: PropTypes.bool.isRequired,
widgetId: PropTypes.string,
onChange: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
};

export default ColorPickerWidget;
Original file line number Diff line number Diff line change
@@ -5,17 +5,21 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { throttle } from 'throttle-debounce';

import { showColorPickerWidget, tweakNodeProperty } from '../../../actions';
import {
showColorPickerWidget,
hideColorPickerWidget,
tweakNodeProperty,
} from '../../../actions';
import PinWidget from './PinWidget';
import { hex2color } from '../../ColorPicker';
import ColorPickerWidget from '../../../containers/ColorPickerWidget';
import ColorPickerWidget from '../ColorPickerWidget';
import { isSessionActive } from '../../../../debugger/selectors';
import { getVisibleColorPickerWidgetId } from '../../../selectors';

class ColorPinWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
focused: false,
selection: [0, 0],
};
@@ -28,22 +32,17 @@ class ColorPinWidget extends React.Component {
this.onWidgetChange = this.onWidgetChange.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
componentDidUpdate(prevProps) {
if (prevProps.selection !== this.state.selection && this.inputRef) {
this.inputRef.setSelectionRange(
this.state.selection[0],
this.state.selection[1]
);
}

this.showColorPickerWidget = this.showColorPickerWidget.bind(this);
this.hideColorPickerWidget = this.hideColorPickerWidget.bind(this);
this.storeInputRef = this.storeInputRef.bind(this);
}

onValueTweaked(value) {
const { entityId, kind, keyName, tweakColor } = this.props;
return tweakColor(entityId, kind, keyName, value);
}
onChangeHandler(value) {
this.setState({ value });
this.props.onChange(value);
if (this.props.isActiveSession) {
this.onValueTweaked(value);
@@ -69,6 +68,17 @@ class ColorPinWidget extends React.Component {
this.props.onBlur();
}

showColorPickerWidget() {
this.props.showColorPickerWidget(this.props.elementId);
}
hideColorPickerWidget() {
this.props.hideColorPickerWidget();
}

storeInputRef(el) {
this.inputRef = el;
}

render() {
return (
<PinWidget
@@ -93,21 +103,22 @@ class ColorPinWidget extends React.Component {
onBlur={this.onBlur}
onKeyDown={this.props.onKeyDown}
spellCheck={false}
ref={el => {
this.inputRef = el;
}}
ref={this.storeInputRef}
/>
<button
className="ColorPicker_toggleBtn"
style={{ background: this.props.value }}
onClick={() =>
this.props.showColorPickerWidget(`#${this.inputRef.id}`)
}
onClick={this.showColorPickerWidget}
/>
</span>
<ColorPickerWidget
widgetId={this.props.elementId}
isVisible={
this.props.visibleColorPickerWidgetId === this.props.elementId
}
color={hex2color(this.props.value)}
onChange={this.onWidgetChange}
onClose={this.hideColorPickerWidget}
/>
</PinWidget>
);
@@ -128,29 +139,34 @@ ColorPinWidget.propTypes = {
deducedType: PropTypes.object,
direction: PropTypes.string,
isActiveSession: PropTypes.bool,
visibleColorPickerWidgetId: PropTypes.string,

value: PropTypes.string,
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired,
showColorPickerWidget: PropTypes.func.isRequired,
hideColorPickerWidget: PropTypes.func.isRequired,
tweakColor: PropTypes.func.isRequired,
};

ColorPinWidget.defaultProps = {
label: 'Unnamed property',
value: '',
disabled: false,
visibleColorPickerWidgetId: null,
};

export default connect(
R.applySpec({
isActiveSession: isSessionActive,
visibleColorPickerWidgetId: getVisibleColorPickerWidgetId,
}),
dispatch =>
bindActionCreators(
{
showColorPickerWidget,
hideColorPickerWidget,
tweakColor: tweakNodeProperty,
},
dispatch
84 changes: 0 additions & 84 deletions packages/xod-client/src/editor/containers/ColorPickerWidget.jsx

This file was deleted.

13 changes: 2 additions & 11 deletions packages/xod-client/src/editor/reducer.js
Original file line number Diff line number Diff line change
@@ -665,21 +665,12 @@ const editorReducer = (state = {}, action) => {
case EAT.SHOW_COLORPICKER_WIDGET:
return R.over(
R.lensProp('pointingPopups'),
R.compose(
R.assocPath(['colorPickerWidget', 'isVisible'], true),
R.assocPath(
['colorPickerWidget', 'elementId'],
action.payload.elementId
)
)
R.assoc('colorPickerWidget', action.payload.widgetId)
)(state);
case EAT.HIDE_COLORPICKER_WIDGET:
return R.over(
R.lensProp('pointingPopups'),
R.compose(
R.assocPath(['colorPickerWidget', 'isVisible'], false),
R.assocPath(['colorPickerWidget', 'elementId'], null)
)
R.assoc('colorPickerWidget', null)
)(state);

default:
9 changes: 2 additions & 7 deletions packages/xod-client/src/editor/selectors.js
Original file line number Diff line number Diff line change
@@ -243,12 +243,7 @@ export const simulationWorker = R.compose(
//
const getPointingPopups = R.pipe(getEditor, R.prop('pointingPopups'));

export const isColorPickerWidgetVisible = R.pipe(
export const getVisibleColorPickerWidgetId = R.pipe(
getPointingPopups,
R.pathOr(false, ['colorPickerWidget', 'isVisible'])
);

export const getColorPickerWidgetElementId = R.pipe(
getPointingPopups,
R.pathOr(null, ['colorPickerWidget', 'elementId'])
R.propOr(null, ['colorPickerWidget'])
);
5 changes: 1 addition & 4 deletions packages/xod-client/src/editor/state.js
Original file line number Diff line number Diff line change
@@ -15,10 +15,7 @@ export default {
highlightedPatchPath: null,
},
pointingPopups: {
colorPickerWidget: {
isVisible: false,
elementId: null,
},
colorPickerWidget: null,
},
tabs: {
'@/main': {

0 comments on commit 4b78155

Please sign in to comment.