Skip to content
This repository has been archived by the owner on Jun 4, 2024. It is now read-only.

Decoupled renderer #173

Closed
wants to merge 21 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c96a277
chore: updated webpack config to allow multiple compilers
lucasconstantino May 12, 2019
826cc54
feat: added initial decoupled build
lucasconstantino May 12, 2019
b3ea00c
chore: renamed decoupled build file name to allow dev version
lucasconstantino May 12, 2019
38eded5
feat: updated AppProvider to accept initialConfig as prop
lucasconstantino May 13, 2019
0ef15b1
feat: exposed AppProvider on decoupled bundle
lucasconstantino May 13, 2019
a7b5d6d
style: formatted TreeContainer code
lucasconstantino May 13, 2019
61b1019
feat: added registry override possibility
lucasconstantino May 13, 2019
7747ad1
feat: updated redux store creation to allow multiple instances
lucasconstantino May 13, 2019
2c1adca
chore: updated builds
lucasconstantino May 14, 2019
056e2ae
chore: updated entrypoint
lucasconstantino May 22, 2019
29cb3d1
chore: ignore linting on build dir
lucasconstantino May 23, 2019
ba68e84
chore: updated bundles
lucasconstantino Jun 24, 2019
806e0ad
chore: always use .min. name
lucasconstantino Jun 24, 2019
94f402c
chore: updated layout crawling to enter component props
lucasconstantino Jun 24, 2019
8364114
chore: updated bundles
lucasconstantino Jun 24, 2019
7ff5088
feat: fixed state on proxy component (ExtraProps)
lucasconstantino Jun 24, 2019
ea79392
feat: fixed nested proxy component state (ExtraProps)
lucasconstantino Jun 24, 2019
09f4dc7
chore: fixed nestedId crawling on TreeContainer
lucasconstantino Jun 24, 2019
129428a
chore: updated bundles
lucasconstantino Jun 24, 2019
eac97ac
feat: allow sub-prop modification on Input/Output/State
lucasconstantino Jul 3, 2019
65011b1
chore: updated bundles
lucasconstantino Jul 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 112 additions & 74 deletions src/TreeContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,57 +19,63 @@ import {
omit,
pick,
propOr,
type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting - seems the pattern we have in our prettier calls in the package.json miss the top level files in src/ - would you mind changing
src/**/*.js src/**/*.react.js
to
src/*.js src/**/*.js
so we know this formatting is consistent with the rest of our codebase?
(the *.react.js variant also doesn't seem to be necessary, *.js covers it)

type,
} from 'ramda';
import { notifyObservers, updateProps } from './actions';
import {notifyObservers, updateProps} from './actions';
import ComponentErrorBoundary from './components/error/ComponentErrorBoundary.react';
import checkPropTypes from 'check-prop-types';


const SIMPLE_COMPONENT_TYPES = ['String', 'Number', 'Null', 'Boolean'];
const isSimpleComponent = component => contains(type(component), SIMPLE_COMPONENT_TYPES)
const isSimpleComponent = component =>
contains(type(component), SIMPLE_COMPONENT_TYPES);

function validateComponent(componentDefinition) {
if (type(componentDefinition) === 'Array') {
throw new Error(
'The children property of a component is a list of lists, instead '+
'of just a list. ' +
'Check the component that has the following contents, ' +
'and remove of the levels of nesting: \n' +
JSON.stringify(componentDefinition, null, 2)
'The children property of a component is a list of lists, instead ' +
'of just a list. ' +
'Check the component that has the following contents, ' +
'and remove of the levels of nesting: \n' +
JSON.stringify(componentDefinition, null, 2)
);
}
if (type(componentDefinition) === 'Object' &&
!(has('namespace', componentDefinition) &&
has('type', componentDefinition) &&
has('props', componentDefinition))) {
if (
type(componentDefinition) === 'Object' &&
!(
has('namespace', componentDefinition) &&
has('type', componentDefinition) &&
has('props', componentDefinition)
)
) {
throw new Error(
'An object was provided as `children` instead of a component, ' +
'string, or number (or list of those). ' +
'Check the children property that looks something like:\n' +
JSON.stringify(componentDefinition, null, 2)
'string, or number (or list of those). ' +
'Check the children property that looks something like:\n' +
JSON.stringify(componentDefinition, null, 2)
);
}
}

const createContainer = (component, path) => isSimpleComponent(component) ?
component :
(<AugmentedTreeContainer
key={component && component.props && component.props.id}
_dashprivate_layout={component}
_dashprivate_path={path}
/>);
const createContainer = (component, path) =>
isSimpleComponent(component) ? (
component
) : (
<AugmentedTreeContainer
key={component && component.props && component.props.id}
_dashprivate_layout={component}
_dashprivate_path={path}
/>
);

function CheckedComponent(p) {
const {
element,
extraProps,
props,
children,
type
} = p;
const {element, extraProps, props, children, type} = p;

const errorMessage = checkPropTypes(element.propTypes, props, 'component prop', element);
const errorMessage = checkPropTypes(
element.propTypes,
props,
'component prop',
element
);
if (errorMessage) {
propTypeErrorHandler(errorMessage, props, type);
}
Expand All @@ -95,15 +101,20 @@ class TreeContainer extends Component {
return null;
}

return Array.isArray(components) ?
addIndex(map)(
(component, i) => createContainer(component, concat(path, ['props', 'children', i])),
components
) : createContainer(components, concat(path, ['props', 'children']));
return Array.isArray(components)
? addIndex(map)(
(component, i) =>
createContainer(
component,
concat(path, ['props', 'children', i])
),
components
)
: createContainer(components, concat(path, ['props', 'children']));
}

getComponent(_dashprivate_layout, children, loading_state, setProps) {
const { _dashprivate_config } = this.props;
const {_dashprivate_config} = this.props;

if (isEmpty(_dashprivate_layout)) {
return null;
Expand All @@ -128,7 +139,7 @@ class TreeContainer extends Component {
children={children}
element={element}
props={props}
extraProps={{ loading_state, setProps }}
extraProps={{loading_state, setProps}}
type={_dashprivate_layout.type}
/>
</ComponentErrorBoundary>
Expand All @@ -140,54 +151,67 @@ class TreeContainer extends Component {
>
{React.createElement(
element,
mergeAll([props, { loading_state, setProps }]),
mergeAll([props, {loading_state, setProps}]),
...(Array.isArray(children) ? children : [children])
)}
</ComponentErrorBoundary>
);

}

getSetProps() {
return newProps => {
const {
_dashprivate_dependencies,
_dashprivate_dispatch,
_dashprivate_path
_dashprivate_path,
} = this.props;

const id = this.getLayoutProps().id;

// Identify the modified props that are required for callbacks
const watchedKeys = filter(key =>
_dashprivate_dependencies &&
_dashprivate_dependencies.find(dependency =>
dependency.inputs.find(input => input.id === id && input.property === key) ||
dependency.state.find(state => state.id === id && state.property === key)
)
const watchedKeys = filter(
key =>
_dashprivate_dependencies &&
_dashprivate_dependencies.find(
dependency =>
dependency.inputs.find(
input =>
input.id === id && input.property === key
) ||
dependency.state.find(
state =>
state.id === id && state.property === key
)
)
)(keysIn(newProps));

// Always update this component's props
_dashprivate_dispatch(updateProps({
props: newProps,
itempath: _dashprivate_path
}));
_dashprivate_dispatch(
updateProps({
props: newProps,
itempath: _dashprivate_path,
})
);

// Only dispatch changes to Dash if a watched prop changed
if (watchedKeys.length) {
_dashprivate_dispatch(notifyObservers({
id: id,
props: pick(watchedKeys)(newProps)
}));
_dashprivate_dispatch(
notifyObservers({
id: id,
props: pick(watchedKeys)(newProps),
})
);
}

};
}

shouldComponentUpdate(nextProps) {
const { _dashprivate_layout, _dashprivate_loadingState } = nextProps;
return _dashprivate_layout !== this.props._dashprivate_layout ||
_dashprivate_loadingState.is_loading !== this.props._dashprivate_loadingState.is_loading;
const {_dashprivate_layout, _dashprivate_loadingState} = nextProps;
return (
_dashprivate_layout !== this.props._dashprivate_layout ||
_dashprivate_loadingState.is_loading !==
this.props._dashprivate_loadingState.is_loading
);
}

getLayoutProps() {
Expand All @@ -199,15 +223,23 @@ class TreeContainer extends Component {
_dashprivate_dispatch,
_dashprivate_layout,
_dashprivate_loadingState,
_dashprivate_path
_dashprivate_path,
} = this.props;

const layoutProps = this.getLayoutProps();

const children = this.getChildren(layoutProps.children, _dashprivate_path);
const children = this.getChildren(
layoutProps.children,
_dashprivate_path
);
const setProps = this.getSetProps(_dashprivate_dispatch);

return this.getComponent(_dashprivate_layout, children, _dashprivate_loadingState, setProps);
return this.getComponent(
_dashprivate_layout,
children,
_dashprivate_loadingState,
setProps
);
}
}

Expand All @@ -233,22 +265,22 @@ function getNestedIds(layout) {
while (queue.length) {
const elementLayout = queue.shift();

const props = elementLayout &&
elementLayout.props;
const props = elementLayout && elementLayout.props;

if (!props) {
continue;
}

const { children, id } = props;
const {children, id} = props;

if (id) {
ids.push(id);
}

if (children) {
const filteredChildren = filter(
child => !isSimpleComponent(child) && !isLoadingComponent(child),
child =>
!isSimpleComponent(child) && !isLoadingComponent(child),
Array.isArray(children) ? children : [children]
);

Expand All @@ -260,11 +292,11 @@ function getNestedIds(layout) {
}

function getLoadingState(layout, requestQueue) {
const ids = isLoadingComponent(layout) ?
getNestedIds(layout) :
(layout && layout.props.id ?
[layout.props.id] :
[]);
const ids = isLoadingComponent(layout)
? getNestedIds(layout)
: layout && layout.props.id
? [layout.props.id]
: [];

let isLoading = false;
let loadingProp;
Expand All @@ -273,7 +305,10 @@ function getLoadingState(layout, requestQueue) {
if (requestQueue) {
forEach(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
if (r.status === 'loading' && any(id => contains(id, controllerId), ids)) {
if (
r.status === 'loading' &&
any(id => contains(id, controllerId), ids)
) {
isLoading = true;
[loadingComponent, loadingProp] = r.controllerId.split('.');
}
Expand All @@ -292,15 +327,18 @@ export const AugmentedTreeContainer = connect(
state => ({
dependencies: state.dependenciesRequest.content,
requestQueue: state.requestQueue,
config: state.config
config: state.config,
}),
dispatch => ({dispatch}),
(stateProps, dispatchProps, ownProps) => ({
_dashprivate_dependencies: stateProps.dependencies,
_dashprivate_dispatch: dispatchProps.dispatch,
_dashprivate_layout: ownProps._dashprivate_layout,
_dashprivate_path: ownProps._dashprivate_path,
_dashprivate_loadingState: getLoadingState(ownProps._dashprivate_layout, stateProps.requestQueue),
_dashprivate_loadingState: getLoadingState(
ownProps._dashprivate_layout,
stateProps.requestQueue
),
_dashprivate_requestQueue: stateProps.requestQueue,
_dashprivate_config: stateProps.config,
})
Expand Down