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);
diff --git a/packages/xod-client/src/editor/components/inspectorWidgets/ColorPickerWidget.jsx b/packages/xod-client/src/editor/components/inspectorWidgets/ColorPickerWidget.jsx
new file mode 100644
index 000000000..d93e77926
--- /dev/null
+++ b/packages/xod-client/src/editor/components/inspectorWidgets/ColorPickerWidget.jsx
@@ -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 (
+
+
+
+ );
+ }
+}
+
+ColorPickerWidget.propTypes = {
+ color: colorPropType,
+ isVisible: PropTypes.bool.isRequired,
+ widgetId: PropTypes.string,
+ onChange: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired,
+};
+
+export default ColorPickerWidget;
diff --git a/packages/xod-client/src/editor/components/inspectorWidgets/pinWidgets/ColorPinWidget.jsx b/packages/xod-client/src/editor/components/inspectorWidgets/pinWidgets/ColorPinWidget.jsx
index dab019bad..fcd610ac5 100644
--- a/packages/xod-client/src/editor/components/inspectorWidgets/pinWidgets/ColorPinWidget.jsx
+++ b/packages/xod-client/src/editor/components/inspectorWidgets/pinWidgets/ColorPinWidget.jsx
@@ -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,14 +32,10 @@ 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) {
@@ -43,7 +43,6 @@ class ColorPinWidget extends React.Component {
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 (
{
- this.inputRef = el;
- }}
+ ref={this.storeInputRef}
/>
);
@@ -128,12 +139,14 @@ 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,
};
@@ -141,16 +154,19 @@ 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
diff --git a/packages/xod-client/src/editor/containers/ColorPickerWidget.jsx b/packages/xod-client/src/editor/containers/ColorPickerWidget.jsx
deleted file mode 100644
index 16afc9679..000000000
--- a/packages/xod-client/src/editor/containers/ColorPickerWidget.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import * as R from 'ramda';
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-
-import * as Actions from '../actions';
-import {
- isColorPickerWidgetVisible,
- getColorPickerWidgetElementId,
-} from '../selectors';
-
-import PointingPopup from '../components/PointingPopup';
-import ColorPicker from '../components/ColorPicker';
-import colorPropType from '../components/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 (
-
-
-
- );
- }
-}
-
-ColorPickerWidget.propTypes = {
- color: colorPropType,
- isVisible: PropTypes.bool.isRequired,
- widgetId: PropTypes.string,
- onChange: PropTypes.func.isRequired,
- actions: PropTypes.shape({
- // eslint-disable-next-line react/no-unused-prop-types
- hideColorPickerWidget: PropTypes.func.isRequired,
- }),
-};
-
-const mapStateToProps = R.applySpec({
- isVisible: isColorPickerWidgetVisible,
- widgetId: getColorPickerWidgetElementId,
-});
-const mapDispatchToProps = dispatch => ({
- actions: bindActionCreators(
- {
- hideColorPickerWidget: Actions.hideColorPickerWidget,
- },
- dispatch
- ),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ColorPickerWidget);
diff --git a/packages/xod-client/src/editor/reducer.js b/packages/xod-client/src/editor/reducer.js
index 1e595b4e1..3889530da 100644
--- a/packages/xod-client/src/editor/reducer.js
+++ b/packages/xod-client/src/editor/reducer.js
@@ -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:
diff --git a/packages/xod-client/src/editor/selectors.js b/packages/xod-client/src/editor/selectors.js
index 9f8e75001..3a6d7cf32 100644
--- a/packages/xod-client/src/editor/selectors.js
+++ b/packages/xod-client/src/editor/selectors.js
@@ -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'])
);
diff --git a/packages/xod-client/src/editor/state.js b/packages/xod-client/src/editor/state.js
index 37f9b9282..3a4403203 100644
--- a/packages/xod-client/src/editor/state.js
+++ b/packages/xod-client/src/editor/state.js
@@ -15,10 +15,7 @@ export default {
highlightedPatchPath: null,
},
pointingPopups: {
- colorPickerWidget: {
- isVisible: false,
- elementId: null,
- },
+ colorPickerWidget: null,
},
tabs: {
'@/main': {