Skip to content

Instantly share code, notes, and snippets.

@drcmda
Last active April 7, 2021 14:40
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save drcmda/cbc77a30681bf41791ccc29ee0f5d855 to your computer and use it in GitHub Desktop.
Save drcmda/cbc77a30681bf41791ccc29ee0f5d855 to your computer and use it in GitHub Desktop.
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)) }
}, {})
}
}
import React from 'react'
import { Provider, connect } from 'react-redux'
import PropTypes from 'prop-types'
import { actions } from './store/globals'
import ElementRenderer from './reconciler'
// Regular react-component 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/factory/canvas/document that receives the result */ }
const store = { /* application store, i.e. the result of redux.createStore */ }
ElementRenderer.render(
<Provider store={store}>
<Root />
</Provider>,
plugin,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment