Created
April 19, 2017 05:50
-
-
Save ManasJayanth/6e2c75ea5df988c4ae6ea778710287ba to your computer and use it in GitHub Desktop.
Canvas Immediate mode rendering using React
This file contains 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 type { HostConfig, Reconciler } from 'react-fiber-types'; | |
import type { ReactNodeList } from 'react-fiber-types/ReactTypes'; | |
import DOMPropertyOperations from './DOMPropertyOperations'; | |
import type { | |
Props, | |
Container, | |
Instance, | |
TextInstance, | |
OpaqueHandle, | |
HostContext, | |
} from './ReactTinyTypes'; | |
const CHILDREN = 'children'; | |
const ReactFiberReconciler : ( | |
hostConfig: HostConfig<*, *, *, *, *, *, *, *> | |
) => Reconciler<*, *, *> = require('react-dom/lib/ReactFiberReconciler'); | |
const LOG_STEPS = false; | |
const log = (...args) => { | |
if (LOG_STEPS) { | |
console.log(...args); | |
} | |
}; | |
const TinyRenderer = ReactFiberReconciler({ | |
createInstance( | |
type : string, | |
props : Props, | |
rootContainerInstance : Container, | |
hostContext : HostContext, | |
internalInstanceHandle : Object | |
) { | |
const DIV = 'div'; | |
const SPAN = 'span'; | |
const supportedTags = [ | |
DIV, | |
SPAN | |
]; | |
const createElement = (tag, props) => { | |
return rootContainerInstance; | |
}; | |
if (supportedTags.indexOf(type) === -1) { | |
throw new Error('Unindentitfied type', type); | |
} else { | |
return createElement(type, props); | |
} | |
}, | |
appendInitialChild( | |
parentInstance : Instance, | |
child : Instance | TextInstance | |
) : void { | |
// Office | |
/* log('appendInitialChild', child, parentInstance);*/ | |
}, | |
appendChild( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance | |
) : void { | |
/* log('appendChild', child, parentInstance);*/ | |
}, | |
removeChild( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance | |
) : void { | |
log('removeChild', child); | |
}, | |
insertBefore( | |
parentInstance : Instance | Container, | |
child : Instance | TextInstance, | |
beforeChild : Instance | TextInstance | |
) : void { | |
log('insertBefore'); | |
}, | |
finalizeInitialChildren( | |
domElement : Instance, | |
type : string, | |
props : Props, | |
rootContainerInstance : Container | |
) : boolean { | |
const setTextContent = function(node, text) { | |
// needs a retained mode api | |
const ctx = node.getContext("2d"); | |
return ctx.fillText(text, 80, 50); | |
}; | |
for (var propKey in props) { | |
var nextProp = props[propKey]; | |
if (!props.hasOwnProperty(propKey)) { | |
continue; | |
} | |
if (propKey === CHILDREN) { | |
if (typeof nextProp === 'string') { | |
setTextContent(domElement, nextProp); | |
} else if (typeof nextProp === 'number') { | |
setTextContent(domElement, '' + nextProp); | |
} | |
} | |
} | |
return false; | |
}, | |
// prepare update is where you compute the diff for an instance. This is done | |
// here to separate computation of the diff to the applying of the diff. Fiber | |
// can reuse this work even if it pauses or aborts rendering a subset of the | |
// tree. | |
prepareUpdate( | |
instance : Instance, | |
type : string, | |
oldProps : Props, | |
newProps : Props, | |
rootContainerInstance : Container, | |
hostContext : HostContext | |
) : null | Array<mixed> { | |
log('In .prepareUpdate(): ', oldProps, newProps); | |
var updatePayload: Array<any> = []; | |
for (let propKey in oldProps) { | |
if ( | |
newProps.hasOwnProperty(propKey) || | |
!oldProps.hasOwnProperty(propKey) || | |
oldProps[propKey] == null | |
) { | |
continue; | |
} | |
(updatePayload = updatePayload || []).push(propKey, null); | |
} | |
for (let propKey in newProps) { | |
const newProp = newProps[propKey]; | |
const oldProp = oldProps != null ? oldProps[propKey] : undefined; | |
if ( | |
!newProps.hasOwnProperty(propKey) || | |
newProp === oldProp || | |
(newProp == null && oldProp == null) | |
) { | |
continue; | |
} | |
if (propKey === CHILDREN) { | |
if ( | |
oldProp !== newProp && | |
(typeof newProp === 'string' || typeof newProp === 'number') | |
) { | |
(updatePayload = updatePayload || []).push(propKey, '' + newProp); | |
} | |
} else { | |
// For any other property we always add it to the queue and then we | |
// filter it out using the whitelist during the commit. | |
(updatePayload = updatePayload || []).push(propKey, newProp); | |
} | |
} | |
return updatePayload; | |
// return diffProperties(instance, type, oldProps, newProps, rootContainerInstance, hostContext); | |
}, | |
commitUpdate( | |
instance : Instance, | |
updatePayload : Array<mixed>, | |
type : string, | |
oldProps : Props, | |
newProps : Props, | |
internalInstanceHandle : Object, | |
) : void { | |
// Apply the diff to the DOM node. | |
// updateProperties(instance, updatePayload, type, oldProps, newProps); | |
/* for (var i = 0; i < updatePayload.length; i += 2) { | |
* var propKey = updatePayload[i]; | |
* var propValue = updatePayload[i + 1]; | |
* if (propKey === CHILDREN) { | |
* setTextContent(instance, propValue); | |
* } else { | |
* DOMPropertyOperations.setValueForProperty( | |
* domElement, | |
* propKey, | |
* propValue, | |
* ); | |
* } | |
* }*/ | |
}, | |
// commitMount is called after initializeFinalChildren *if* | |
// `initializeFinalChildren` returns true. | |
commitMount( | |
instance : Instance, | |
type : string, | |
newProps : Props, | |
internalInstanceHandle : Object | |
) { | |
log('commitMount'); | |
// noop | |
}, | |
getRootHostContext(rootContainerInstance : Container) : HostContext { | |
log('getRootHostContext'); | |
return emptyObject; | |
}, | |
getChildHostContext(parentHostContext : HostContext, type: string) : HostContext { | |
log('getChildHostContext'); | |
return emptyObject; | |
}, | |
getPublicInstance(instance : Instance | TextInstance) { | |
log('getPublicInstance', instance); | |
if (instance == null) { | |
return null; | |
} | |
return instance; | |
}, | |
prepareForCommit() : void { | |
log('prepareForCommit'); | |
// noop | |
}, | |
resetAfterCommit() : void { | |
log('resetAfterCommit'); | |
// noop | |
}, | |
shouldSetTextContent(props : Props): boolean { | |
const criteria = typeof props.children === 'string' || | |
typeof props.children === 'number' | |
return criteria; | |
}, | |
resetTextContent(instance : Instance) : void { | |
log('resetTextContent'); | |
// noop | |
}, | |
createTextInstance( | |
text : string, | |
rootContainerInstance : Container, | |
hostContext : HostContext, | |
internalInstanceHandle : OpaqueHandle | |
) : TextInstance { | |
const ctx = rootContainerInstance.getContext("2d"); | |
return ctx.fillText(text, 10, 50); | |
}, | |
commitTextUpdate( | |
textInstance : TextInstance, | |
oldText : string, | |
newText : string | |
) : void { | |
log('commitTextUpdate'); | |
// noop | |
throw new Error('commitTextUpdate should not be called'); | |
}, | |
scheduleAnimationCallback() { | |
log('scheduleAnimationCallback'); | |
}, | |
scheduleDeferredCallback() { | |
log('scheduleDeferredCallback'); | |
}, | |
useSyncScheduling: true, | |
}); | |
/** | |
* Our public renderer. When someone requires your renderer, this is all they | |
* should have access to. `render` and `unmountComponentAtNode` methods should | |
* be considered required, though that isn’t strictly true. | |
*/ | |
const defaultContainer = {}; | |
const Tiny = { | |
render( | |
element : React$Element<any>, | |
container : any, | |
callback : ?Function, | |
) { | |
const containerKey = typeof container === 'undefined' ? defaultContainer : container; | |
let root = roots.get(containerKey); | |
if (!root) { | |
root = TinyRenderer.createContainer(containerKey); | |
roots.set(container, root); | |
} | |
TinyRenderer.updateContainer((element : any), root, null, callback); | |
return TinyRenderer.getPublicRootInstance(root); | |
}, | |
unmountComponentAtNode(container : any) { | |
const containerKey = typeof container === 'undefined' ? defaultContainer : container; | |
const root = roots.get(containerKey); | |
if (root) { | |
TinyRenderer.updateContainer(null, root, null, () => { | |
roots.delete(container); | |
}); | |
} | |
}, | |
// other API methods you may support, such as `renderPortal()` | |
}; | |
const roots = new Map(); | |
const emptyObject = {}; | |
export default Tiny; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment