avoid dynamic connections to store

This commit is contained in:
Val Erastov 2018-01-19 19:16:24 -08:00
parent ceb9b89616
commit efe3efa7c9
5 changed files with 84 additions and 63 deletions

View file

@ -0,0 +1,13 @@
import React, {Fragment} from 'react';
export default class NeverUpdate extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
return <Fragment>{this.props.children}</Fragment>;
}
}

View file

@ -1,12 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import shallowEqual from "../gems/shallowEqual";
export default function connect(WrappedComponent, tokens, {staticProps, mapProps, mapActions}) {
if (!Array.isArray(tokens)) {
tokens = [tokens];
}
export default function connect(WrappedComponent, tokens, {staticProps, mapProps, mapActions, mapSelfProps}) {
mapProps = createMapper(mapProps);
@ -14,20 +9,30 @@ export default function connect(WrappedComponent, tokens, {staticProps, mapProps
return dispatch;
};
return class StateConnector extends React.Component {
mapSelfProps = mapSelfProps || (() => undefined);
return class StateConnector extends React.PureComponent {
constructor(context) {
constructor(props) {
super();
this.mounted = false;
this.stateProps = {};
this.dispatchProps = mapActions(this.dispatch);
this.dispatchProps = mapActions(this.dispatch, props);
}
componentWillMount() {
this.externalStateConnection = this.context.bus.connectToState(tokens, this.setExternalState);
this.externalStateConnection = this.context.bus.connectToState(this.getTokens(), this.setExternalState);
this.externalStateConnection();
}
getTokens() {
let tokensArr = tokens instanceof Function ? tokens(this.props) : tokens;
if (!Array.isArray(tokensArr)) {
tokensArr = [tokensArr];
}
return tokensArr;
}
componentDidMount() {
this.mounted = true;
}
@ -38,23 +43,21 @@ export default function connect(WrappedComponent, tokens, {staticProps, mapProps
}
setExternalState = (state) => {
this.stateProps = mapProps(state);
this.stateProps = mapProps(state, this.props);
if (this.mounted) {
this.forceUpdate();
}
};
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps);
}
dispatch = (event, data) => {
this.context.bus.dispatch(event, data);
};
render() {
return <WrappedComponent {...this.stateProps} {...this.dispatchProps} {...staticProps} />
return <WrappedComponent {...this.stateProps}
{...this.dispatchProps}
{...staticProps}
{...mapSelfProps(this.props)}/>
}
componentDidCatch() {
@ -66,13 +69,9 @@ export default function connect(WrappedComponent, tokens, {staticProps, mapProps
}
}
function createMapper(mapper) {
function createMapper(mapper, comp) {
if (!mapper) {
return function (state) {
let props = {};
state.forEach(stateItem => Object.assign(props, stateItem));
return props;
};
return DEFAULT_MAPPER;
} else if (Array.isArray(mapper)) {
return function (state) {
let props = {};
@ -87,5 +86,8 @@ function createMapper(mapper) {
return mapper;
}
export function DEFAULT_MAPPER(state) {
let props = {};
state.forEach(stateItem => Object.assign(props, stateItem));
return props;
}

View file

@ -1,12 +1,12 @@
import {TOKENS as ACTION_TOKENS} from "./actionSystemPlugin";
export function mapActionBehavior(actionId) {
let actionRunToken = ACTION_TOKENS.actionRun(actionId);
return dispatch => ({
onClick: data => dispatch(actionRunToken, data),
onMouseEnter: ({pageX, pageY}) => dispatch(ACTION_TOKENS.SHOW_HINT_FOR, [actionId, pageX, pageY]),
onMouseLeave: () => dispatch(ACTION_TOKENS.SHOW_HINT_FOR, null)
});
export function mapActionBehavior(actionIdProp) {
return (dispatch, props) => {
const actionId = props[actionIdProp];
const actionRunToken = ACTION_TOKENS.actionRun(actionId);
return {
onClick: data => dispatch(actionRunToken, data),
onMouseEnter: ({pageX, pageY}) => dispatch(ACTION_TOKENS.SHOW_HINT_FOR, [actionId, pageX, pageY]),
onMouseLeave: () => dispatch(ACTION_TOKENS.SHOW_HINT_FOR, null)
}};
}

View file

@ -6,6 +6,7 @@ import {TOKENS as UI_TOKENS} from '../uiEntryPointsPlugin';
import {TOKENS as ACTION_TOKENS} from '../../actions/actionSystemPlugin';
import {toIdAndOverrides} from "../../actions/actionRef";
import {mapActionBehavior} from "../../actions/actionButtonBehavior";
import {DEFAULT_MAPPER} from "../../../../../modules/ui/connect";
export default function PlugableControlBar() {
@ -15,15 +16,7 @@ export default function PlugableControlBar() {
function ButtonGroup({actions}) {
return actions.map(actionRef => {
let [id, overrides] = toIdAndOverrides(actionRef);
let Comp = connect(ActionButton,
[ACTION_TOKENS.actionAppearance(id), ACTION_TOKENS.actionState(id)],
{
staticProps: {actionId: id},
mapProps: ([appearance, state]) => Object.assign({}, appearance, state, overrides),
mapActions: mapActionBehavior(id)
}
);
return <Comp key={id}/>;
return <ConnectedActionButton key={id} actionId={id} {...overrides}/>;
});
}
@ -56,6 +49,16 @@ const BUTTON_CONNECTOR = {
const LeftGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_LEFT, BUTTON_CONNECTOR);
const RightGroup = connect(ButtonGroup, UI_TOKENS.CONTROL_BAR_RIGHT, BUTTON_CONNECTOR);
const ConnectedActionButton = connect(ActionButton,
props => [ACTION_TOKENS.actionAppearance(props.actionId),
ACTION_TOKENS.actionState(props.actionId)],
{
mapProps: (state, props) => Object.assign(DEFAULT_MAPPER(state), props),
mapActions: mapActionBehavior('actionId'),
}
);
function getMenuData(el) {
//TODO: make more generic
return {

View file

@ -6,33 +6,20 @@ import Menu, {MenuItem, MenuSeparator} from "../../../../../modules/ui/component
import Fa from "../../../../../modules/ui/components/Fa";
import Filler from "../../../../../modules/ui/components/Filler";
import {TOKENS as KeyboardTokens} from "../../keyboard/keyboardPlugin";
import {DEFAULT_MAPPER} from "../../../../../modules/ui/connect";
function MenuHolder({menus}) {
return menus.map(({id, actions}) => {
let menuToken = MENU_TOKENS.menuState(id);
let connectedMenu = connect(ActionMenu, [menuToken, KeyboardTokens.KEYMAP], {
staticProps: {actions},
mapProps: [,keymap => ({keymap})]
});
return React.createElement(connectedMenu, {key: id});
});
return menus.map(({id, actions}) => <ConnectedActionMenu key={id} menuId={id} actions={actions} />);
}
function ActionMenu({actions, keymap, ...props}) {
return <Menu {...props}>
{actions.map((action, index)=> {
function ActionMenu({actions, keymap, ...menuState}) {
return <Menu {...menuState}>
{actions.map((action, index) => {
if (action === '-') {
return <MenuSeparator key={index} />
}
const runToken = ACTION_TOKENS.actionRun(action);
return React.createElement(
connect(ActionMenuItem, [ACTION_TOKENS.actionState(action), ACTION_TOKENS.actionAppearance(action)], {
staticProps: {hotKey: keymap[action]},
mapActions: dispatch => ({
onClick: () => dispatch(runToken)
})
}), {key: action});
})}
return <ConnectedMenuItem key={action} actionId={action} hotKey={keymap[action]} />;
})}
</Menu>;
}
@ -61,6 +48,22 @@ function ActionMenuItem({label, cssIcons, icon32, icon96, onClick, enabled, hotK
return <MenuItem {...{label, icon, style, disabled: !enabled, hotKey, onClick}} />;
}
const ConnectedActionMenu = connect(ActionMenu,
({menuId}) => [MENU_TOKENS.menuState(menuId), KeyboardTokens.KEYMAP],
{
mapProps: ([menuState, keymap], {actions}) => Object.assign({keymap, actions}, menuState)
});
let ConnectedMenuItem = connect(ActionMenuItem,
({actionId}) => [ACTION_TOKENS.actionState(actionId), ACTION_TOKENS.actionAppearance(actionId)],
{
mapActions: (dispatch, {actionId}) => ({
onClick: () => dispatch(ACTION_TOKENS.actionRun(actionId))
}),
mapSelfProps: ({hotKey}) => ({hotKey})
}
);
export default connect(MenuHolder, MENU_TOKENS.MENUS, {
mapProps: ([menus]) => ({menus})