diff --git a/src/TreeContainer.js b/src/TreeContainer.js index 40a7295..b0a99b4 100644 --- a/src/TreeContainer.js +++ b/src/TreeContainer.js @@ -1,9 +1,24 @@ -'use strict' +/* global window:true */ import R from 'ramda'; import React, {Component, PropTypes} from 'react'; import Registry from './registry'; -import NotifyObservers from './components/core/NotifyObservers.react'; +//import {NotifyObservers} from './components/core/NotifyObservers.react'; + +/* global window:true */ +import {connect} from 'react-redux'; +import {isEmpty} from 'ramda'; +//import {notifyObservers, updateProps} from '../../actions'; +import {notifyObservers, updateProps} from './actions'; +//import React, {PropTypes} from 'react'; +//import {render} from '../../TreeContainer.js'; +window.render = render; + + +var reactDocs = require('react-docgen'); + +window.reactDocs = reactDocs; + export default class TreeContainer extends Component { shouldComponentUpdate(nextProps) { @@ -19,7 +34,7 @@ TreeContainer.propTypes = { layout: PropTypes.object, } -function render(component) { +export function render(component) { if (R.contains(R.type(component), ['String', 'Number', 'Null'])) { return component; } @@ -84,3 +99,127 @@ function render(component) { render.propTypes = { children: PropTypes.object } + + +/* + * NotifyObservers passes a connected `setProps` handler down to + * its child as a prop + */ + +function mapStateToProps (state) { + return { + dependencies: state.dependenciesRequest.content, + paths: state.paths + }; +} + +function mapDispatchToProps (dispatch) { + return {dispatch}; +} + +function mergeProps(stateProps, dispatchProps, ownProps) { + const {dispatch} = dispatchProps; + return { + id: ownProps.id, + children: ownProps.children, + dependencies: stateProps.dependencies, + paths: stateProps.paths, + + fireEvent: function fireEvent({event}) { + // Update this component's observers with the updated props + dispatch(notifyObservers({event, id: ownProps.id})); + }, + + setProps: function setProps(newProps) { + const payload = { + props: newProps, + id: ownProps.id, + itempath: stateProps.paths[ownProps.id] + }; + + // Update this component's props + dispatch(updateProps(payload)); + + // Update output components that depend on this input + dispatch(notifyObservers({id: ownProps.id, props: newProps})); + } + } + +} + +function NotifyObserversComponent ({ + children, + id, + paths, + + dependencies, + + fireEvent, + setProps +}) { + const thisComponentTriggersEvents = ( + dependencies && dependencies.find(dependency => ( + dependency.events.find(event => event.id === id) + )) + ); + const thisComponentSharesState = ( + dependencies && dependencies.find(dependency => ( + dependency.inputs.find(input => input.id === id) || + dependency.state.find(state => state.id === id) + )) + ); + /* + * Only pass in `setProps` and `fireEvent` if they are actually + * necessary. + * This allows component authors to skip computing data + * for `setProps` or `fireEvent` (which can be expensive) + * in the case when they aren't actually used. + * For example, consider `hoverData` for graphs. If it isn't + * actually used, then the component author can skip binding + * the events for the component. + * + * TODO - A nice enhancement would be to pass in the actual events + * and properties that are used into the component so that the + * component author can check for something like `subscribed_events` + * or `subscribed_properties` instead of `fireEvent` and `setProps`. + */ + const extraProps = {}; + if (thisComponentSharesState && + + // there is a bug with graphs right now where + // the restyle listener gets assigned with a + // setProps function that was created before + // the item was added. only pass in setProps + // if the item's path exists for now. + paths[id] + ) { + extraProps.setProps = setProps; + } + if (thisComponentTriggersEvents && paths[id]) { + extraProps.fireEvent = fireEvent; + } + extraProps.render = componentJson => { + // TODO - check if it's already react component + // TODO - we could push all of the recursive rendering + // to the components and their children + return render(componentJson); + }; + + if (!isEmpty(extraProps)) { + return React.cloneElement(children, extraProps); + } else { + return children; + } +} + +NotifyObserversComponent.propTypes = { + id: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + path: PropTypes.array.isRequired +}; + +export const NotifyObservers = connect( + mapStateToProps, + mapDispatchToProps, + mergeProps +)(NotifyObserversComponent); diff --git a/src/reducers/utils.js b/src/reducers/utils.js index 540516a..3beac0e 100644 --- a/src/reducers/utils.js +++ b/src/reducers/utils.js @@ -1,34 +1,24 @@ import R from 'ramda'; -const extend = R.reduce(R.flip(R.append)) - // crawl a layout object, apply a function on every object export const crawlLayout = (object, func, path=[]) => { func(object, path); + /* eslint-disable */ + console.warn(path); + /* eslint-enable */ /* * object may be a string, a number, or null * R.has will return false for both of those types */ - if (R.type(object) === 'Object' && - R.has('props', object) && - R.has('children', object.props) - ) { - const newPath = extend(path, ['props', 'children']); - if (Array.isArray(object.props.children)) { - object.props.children.forEach((child, i) => { + if (R.type(object) === 'Object') { + R.keys(object).forEach(key => { + if (R.contains(R.type(object[key]), ['Array', 'Object'])) { crawlLayout( - child, - func, - R.append(i, newPath)); - }); - } else { - crawlLayout( - object.props.children, - func, - newPath - ); - } + object[key], func, R.append(key, path) + ); + } + }) } else if (R.type(object) === 'Array') { /*