Skip to content

Instantly share code, notes, and snippets.

@ManasJayanth
Created April 19, 2017 05:50
Show Gist options
  • Save ManasJayanth/6e2c75ea5df988c4ae6ea778710287ba to your computer and use it in GitHub Desktop.
Save ManasJayanth/6e2c75ea5df988c4ae6ea778710287ba to your computer and use it in GitHub Desktop.
Canvas Immediate mode rendering using React
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