-
-
Save nhducit/cb7b48b9bd8a12b9db54cff176d3a128 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Reconciler from 'react-reconciler' | |
import omit from 'lodash/omit' | |
import capitalize from 'lodash/capitalize' | |
import { actions as elementActions } from './store/elements' | |
import * as Elements from './elements' | |
const roots = new Map() | |
const emptyObject = {} | |
const Renderer = Reconciler({ | |
useSyncScheduling: true, | |
now: () => performance.now(), | |
// Create a new instance of whatever you need to create in your target system | |
// Here you set up initial props and event handlers | |
createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) { | |
const instance = new Elements[(capitalize(type))](rootContainerInstance, {}) | |
applyProps(instance, props, {}) | |
return instance | |
}, | |
// Append a first child | |
appendInitialChild(parentInstance, child) { | |
parentInstance.children = [...parentInstance.children, child.id] | |
}, | |
finalizeInitialChildren(instance, type, props, rootContainerInstance) { | |
return false | |
}, | |
prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, hostContext) { | |
return emptyObject | |
}, | |
getRootHostContext(rootContainerInstance) { | |
return emptyObject | |
}, | |
shouldDeprioritizeSubtree(type, props) { | |
return false | |
}, | |
getChildHostContext(parentHostContext, type) { | |
return emptyObject | |
}, | |
getPublicInstance(instance) { | |
return instance | |
}, | |
prepareForCommit() {}, | |
resetAfterCommit() {}, | |
shouldSetTextContent(props) { | |
return false | |
}, | |
mutation: { | |
// This is where you add the root element into the target container | |
appendChildToContainer(container, child) { | |
container.addElement(child) | |
}, | |
// From there on it's just basic adding, removing ... | |
appendChild(parentInstance, child) { | |
parentInstance.children = [...parentInstance.children, child.id] | |
}, | |
insertBefore(parentInstance, child, beforeChild) { | |
const index = parentInstance.children.indexOf(beforeChild.id) | |
parentInstance.children = [ | |
...parentInstance.children.slice(0, index), | |
child.id, | |
...parentInstance.children.slice(index), | |
] | |
}, | |
removeChild(parentInstance, child) { | |
parentInstance.children = parentInstance.children.filter(id => id !== child.id) | |
child.destroy() | |
child.unsubscribes = undefined | |
}, | |
removeChildFromContainer(parentInstance, child) { | |
parentInstance.removeElement(child.id) | |
child.destroy() | |
child.unsubscribes = undefined | |
}, | |
// This one updates prop changes | |
commitUpdate(instance, updatePayload, type, oldProps, newProps) { | |
applyProps(instance, newProps, oldProps) | |
}, | |
}, | |
}) | |
export default { | |
render(element, container) { | |
let root = roots.get(container) | |
if (!root) { | |
root = Renderer.createContainer(container) | |
roots.set(container, root) | |
} | |
Renderer.updateContainer(element, root, null, undefined) | |
return Renderer.getPublicRootInstance(root) | |
}, | |
unmountComponentAtNode(container) { | |
const root = roots.get(container) | |
if (root) Renderer.updateContainer(null, root, null, () => roots.delete(container)) | |
}, | |
} | |
// Internal stuff, that's how our particular target handles props and events | |
function applyProps(instance, newProps, oldProps) { | |
// Filter equals, events and reserved props | |
const sameProps = Object.keys(newProps).filter(key => newProps[key] === oldProps[key]) | |
const handlers = Object.keys(newProps).filter(key => typeof newProps[key] === 'function' && key.startsWith('on')) | |
const filteredProps = omit(newProps, [...sameProps, ...handlers, 'children', 'key', 'ref']) | |
if (Object.keys(filteredProps).length > 0) { | |
// Set props | |
instance.session.dispatch(elementActions.update(instance.id, filteredProps)) | |
// Set events | |
instance.unsubscribes = handlers.reduce((acc, key) => { | |
const name = key.charAt(2).toLowerCase() + key.substr(3) | |
// Remove old events and return new unsubscribe | |
if (instance.unsubscribes && instance.unsubscribes[key]) | |
instance.removeSubscription(instance.unsubscribes[key]) | |
return { ...acc, [key]: instance.observe(state => state[name], (state, old) => newProps[key](state, old)) } | |
}, {}) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { connect } from 'react-redux' | |
import PropTypes from 'prop-types' | |
import { actions } from './store/globals' | |
import ElementRenderer from './reconciler' | |
// Providing the store in context makes it possible to use regular redux with a custom renderer | |
export class Provider extends React.Component { | |
static childContextTypes = { store: PropTypes.object } | |
getChildContext() { | |
return { store: this.props.plugin.session.store } | |
} | |
render() { | |
return this.props.children | |
} | |
} | |
// Regular react-compoennt that renders custom elements | |
@connect(({ globals }) => ({ camera: globals.camera, day: globals.day }), { | |
setCamera: actions.setCamera, | |
setColorMode: actions.setColorMode, | |
}) | |
class Root extends React.Component { | |
setCamera = () => this.props.setCamera(this.props.camera === 'orthographic' ? 'perspective' : 'orthographic') | |
setColorMode = () => this.props.setColorMode(!this.props.day) | |
render() { | |
return ( | |
<group format="Table"> | |
<checkbox name="Orthographic" value={this.props.camera === 'orthographic'} onValue={this.setCamera} /> | |
<checkbox name="Daylight" value={this.props.day} onValue={this.setColorMode} /> | |
</group> | |
) | |
} | |
} | |
const plugin = /* This would be the target container that receives the result */ | |
ElementRenderer.render( | |
<Provider plugin={plugin}> | |
<Root /> | |
</Provider>, | |
plugin, | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment