├── .giignore ├── README.md ├── demo ├── index.html ├── js │ ├── app.jsx │ ├── footer.jsx │ ├── todoItem.jsx │ ├── todoModel.js │ └── utils.js ├── node_modules │ ├── director │ │ └── build │ │ │ └── director.js │ ├── react │ │ └── dist │ │ │ ├── JSXTransformer.js │ │ │ └── react-with-addons.js │ ├── todomvc-app-css │ │ └── index.css │ └── todomvc-common │ │ ├── base.css │ │ └── base.js ├── package.json └── readme.md ├── package.json └── src └── react-gl.jsx /.giignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-GL 2 | Render React components in WebGL for 60 FPS animations 3 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React • TodoMVC 6 | 7 | 8 | 9 | 10 |
11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demo/js/app.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | /*global React, Router*/ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | app.ALL_TODOS = 'all'; 12 | app.ACTIVE_TODOS = 'active'; 13 | app.COMPLETED_TODOS = 'completed'; 14 | var TodoFooter = app.TodoFooter; 15 | var TodoItem = app.TodoItem; 16 | 17 | var ENTER_KEY = 13; 18 | 19 | var TodoApp = React.createClass({ 20 | getInitialState: function () { 21 | return { 22 | nowShowing: app.ALL_TODOS, 23 | editing: null 24 | }; 25 | }, 26 | 27 | componentDidMount: function () { 28 | var setState = this.setState; 29 | var router = Router({ 30 | '/': setState.bind(this, {nowShowing: app.ALL_TODOS}), 31 | '/active': setState.bind(this, {nowShowing: app.ACTIVE_TODOS}), 32 | '/completed': setState.bind(this, {nowShowing: app.COMPLETED_TODOS}) 33 | }); 34 | router.init('/'); 35 | }, 36 | 37 | handleNewTodoKeyDown: function (event) { 38 | if (event.keyCode !== ENTER_KEY) { 39 | return; 40 | } 41 | 42 | event.preventDefault(); 43 | 44 | var val = React.findDOMNode(this.refs.newField).value.trim(); 45 | 46 | if (val) { 47 | this.props.model.addTodo(val); 48 | React.findDOMNode(this.refs.newField).value = ''; 49 | } 50 | }, 51 | 52 | toggleAll: function (event) { 53 | var checked = event.target.checked; 54 | this.props.model.toggleAll(checked); 55 | }, 56 | 57 | toggle: function (todoToToggle) { 58 | this.props.model.toggle(todoToToggle); 59 | }, 60 | 61 | destroy: function (todo) { 62 | this.props.model.destroy(todo); 63 | }, 64 | 65 | edit: function (todo) { 66 | this.setState({editing: todo.id}); 67 | }, 68 | 69 | save: function (todoToSave, text) { 70 | this.props.model.save(todoToSave, text); 71 | this.setState({editing: null}); 72 | }, 73 | 74 | cancel: function () { 75 | this.setState({editing: null}); 76 | }, 77 | 78 | clearCompleted: function () { 79 | this.props.model.clearCompleted(); 80 | }, 81 | 82 | render: function () { 83 | var footer; 84 | var main; 85 | var todos = this.props.model.todos; 86 | 87 | var shownTodos = todos.filter(function (todo) { 88 | switch (this.state.nowShowing) { 89 | case app.ACTIVE_TODOS: 90 | return !todo.completed; 91 | case app.COMPLETED_TODOS: 92 | return todo.completed; 93 | default: 94 | return true; 95 | } 96 | }, this); 97 | 98 | var todoItems = shownTodos.map(function (todo) { 99 | return ( 100 | 110 | ); 111 | }, this); 112 | 113 | var activeTodoCount = todos.reduce(function (accum, todo) { 114 | return todo.completed ? accum : accum + 1; 115 | }, 0); 116 | 117 | var completedCount = todos.length - activeTodoCount; 118 | 119 | if (activeTodoCount || completedCount) { 120 | footer = 121 | ; 127 | } 128 | 129 | if (todos.length) { 130 | main = ( 131 |
132 | 138 |
    139 | {todoItems} 140 |
141 |
142 | ); 143 | } 144 | 145 | return ( 146 |
147 |
148 |

todos

149 | 156 |
157 | {main} 158 | {footer} 159 |
160 | ); 161 | } 162 | }); 163 | 164 | var model = new app.TodoModel('react-todos'); 165 | 166 | function render() { 167 | React.render( 168 | , 169 | document.getElementsByClassName('todoapp')[0] 170 | ); 171 | } 172 | 173 | model.subscribe(render); 174 | render(); 175 | })(); 176 | -------------------------------------------------------------------------------- /demo/js/footer.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | /*global React */ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | app.TodoFooter = React.createClass({ 12 | render: function () { 13 | var activeTodoWord = app.Utils.pluralize(this.props.count, 'item'); 14 | var clearButton = null; 15 | 16 | if (this.props.completedCount > 0) { 17 | clearButton = ( 18 | 23 | ); 24 | } 25 | 26 | // React idiom for shortcutting to `classSet` since it'll be used often 27 | var cx = React.addons.classSet; 28 | var nowShowing = this.props.nowShowing; 29 | return ( 30 | 31 | 63 | 64 | ); 65 | } 66 | }); 67 | })(); 68 | -------------------------------------------------------------------------------- /demo/js/todoItem.jsx: -------------------------------------------------------------------------------- 1 | /*jshint quotmark: false */ 2 | /*jshint white: false */ 3 | /*jshint trailing: false */ 4 | /*jshint newcap: false */ 5 | /*global React */ 6 | var app = app || {}; 7 | 8 | (function () { 9 | 'use strict'; 10 | 11 | var ESCAPE_KEY = 27; 12 | var ENTER_KEY = 13; 13 | 14 | app.TodoItem = React.createClass({ 15 | handleSubmit: function (event) { 16 | var val = this.state.editText.trim(); 17 | if (val) { 18 | this.props.onSave(val); 19 | this.setState({editText: val}); 20 | } else { 21 | this.props.onDestroy(); 22 | } 23 | }, 24 | 25 | handleEdit: function () { 26 | this.props.onEdit(); 27 | this.setState({editText: this.props.todo.title}); 28 | }, 29 | 30 | handleKeyDown: function (event) { 31 | if (event.which === ESCAPE_KEY) { 32 | this.setState({editText: this.props.todo.title}); 33 | this.props.onCancel(event); 34 | } else if (event.which === ENTER_KEY) { 35 | this.handleSubmit(event); 36 | } 37 | }, 38 | 39 | handleChange: function (event) { 40 | this.setState({editText: event.target.value}); 41 | }, 42 | 43 | getInitialState: function () { 44 | return {editText: this.props.todo.title}; 45 | }, 46 | 47 | /** 48 | * This is a completely optional performance enhancement that you can 49 | * implement on any React component. If you were to delete this method 50 | * the app would still work correctly (and still be very performant!), we 51 | * just use it as an example of how little code it takes to get an order 52 | * of magnitude performance improvement. 53 | */ 54 | shouldComponentUpdate: function (nextProps, nextState) { 55 | return ( 56 | nextProps.todo !== this.props.todo || 57 | nextProps.editing !== this.props.editing || 58 | nextState.editText !== this.state.editText 59 | ); 60 | }, 61 | 62 | /** 63 | * Safely manipulate the DOM after updating the state when invoking 64 | * `this.props.onEdit()` in the `handleEdit` method above. 65 | * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate 66 | * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate 67 | */ 68 | componentDidUpdate: function (prevProps) { 69 | if (!prevProps.editing && this.props.editing) { 70 | var node = React.findDOMNode(this.refs.editField); 71 | node.focus(); 72 | node.setSelectionRange(node.value.length, node.value.length); 73 | } 74 | }, 75 | 76 | render: function () { 77 | return ( 78 |
  • 82 |
    83 | 89 | 92 |
    94 | 102 |
  • 103 | ); 104 | } 105 | }); 106 | })(); 107 | -------------------------------------------------------------------------------- /demo/js/todoModel.js: -------------------------------------------------------------------------------- 1 | /*jshint quotmark:false */ 2 | /*jshint white:false */ 3 | /*jshint trailing:false */ 4 | /*jshint newcap:false */ 5 | var app = app || {}; 6 | 7 | (function () { 8 | 'use strict'; 9 | 10 | var Utils = app.Utils; 11 | // Generic "model" object. You can use whatever 12 | // framework you want. For this application it 13 | // may not even be worth separating this logic 14 | // out, but we do this to demonstrate one way to 15 | // separate out parts of your application. 16 | app.TodoModel = function (key) { 17 | this.key = key; 18 | this.todos = Utils.store(key); 19 | this.onChanges = []; 20 | }; 21 | 22 | app.TodoModel.prototype.subscribe = function (onChange) { 23 | this.onChanges.push(onChange); 24 | }; 25 | 26 | app.TodoModel.prototype.inform = function () { 27 | Utils.store(this.key, this.todos); 28 | this.onChanges.forEach(function (cb) { cb(); }); 29 | }; 30 | 31 | app.TodoModel.prototype.addTodo = function (title) { 32 | this.todos = this.todos.concat({ 33 | id: Utils.uuid(), 34 | title: title, 35 | completed: false 36 | }); 37 | 38 | this.inform(); 39 | }; 40 | 41 | app.TodoModel.prototype.toggleAll = function (checked) { 42 | // Note: it's usually better to use immutable data structures since they're 43 | // easier to reason about and React works very well with them. That's why 44 | // we use map() and filter() everywhere instead of mutating the array or 45 | // todo items themselves. 46 | this.todos = this.todos.map(function (todo) { 47 | return Utils.extend({}, todo, {completed: checked}); 48 | }); 49 | 50 | this.inform(); 51 | }; 52 | 53 | app.TodoModel.prototype.toggle = function (todoToToggle) { 54 | this.todos = this.todos.map(function (todo) { 55 | return todo !== todoToToggle ? 56 | todo : 57 | Utils.extend({}, todo, {completed: !todo.completed}); 58 | }); 59 | 60 | this.inform(); 61 | }; 62 | 63 | app.TodoModel.prototype.destroy = function (todo) { 64 | this.todos = this.todos.filter(function (candidate) { 65 | return candidate !== todo; 66 | }); 67 | 68 | this.inform(); 69 | }; 70 | 71 | app.TodoModel.prototype.save = function (todoToSave, text) { 72 | this.todos = this.todos.map(function (todo) { 73 | return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text}); 74 | }); 75 | 76 | this.inform(); 77 | }; 78 | 79 | app.TodoModel.prototype.clearCompleted = function () { 80 | this.todos = this.todos.filter(function (todo) { 81 | return !todo.completed; 82 | }); 83 | 84 | this.inform(); 85 | }; 86 | 87 | })(); 88 | -------------------------------------------------------------------------------- /demo/js/utils.js: -------------------------------------------------------------------------------- 1 | var app = app || {}; 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | app.Utils = { 7 | uuid: function () { 8 | /*jshint bitwise:false */ 9 | var i, random; 10 | var uuid = ''; 11 | 12 | for (i = 0; i < 32; i++) { 13 | random = Math.random() * 16 | 0; 14 | if (i === 8 || i === 12 || i === 16 || i === 20) { 15 | uuid += '-'; 16 | } 17 | uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) 18 | .toString(16); 19 | } 20 | 21 | return uuid; 22 | }, 23 | 24 | pluralize: function (count, word) { 25 | return count === 1 ? word : word + 's'; 26 | }, 27 | 28 | store: function (namespace, data) { 29 | if (data) { 30 | return localStorage.setItem(namespace, JSON.stringify(data)); 31 | } 32 | 33 | var store = localStorage.getItem(namespace); 34 | return (store && JSON.parse(store)) || []; 35 | }, 36 | 37 | extend: function () { 38 | var newObj = {}; 39 | for (var i = 0; i < arguments.length; i++) { 40 | var obj = arguments[i]; 41 | for (var key in obj) { 42 | if (obj.hasOwnProperty(key)) { 43 | newObj[key] = obj[key]; 44 | } 45 | } 46 | } 47 | return newObj; 48 | } 49 | }; 50 | })(); 51 | -------------------------------------------------------------------------------- /demo/node_modules/director/build/director.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // 4 | // Generated on Tue Dec 16 2014 12:13:47 GMT+0100 (CET) by Charlie Robbins, Paolo Fragomeni & the Contributors (Using Codesurgeon). 5 | // Version 1.2.6 6 | // 7 | 8 | (function (exports) { 9 | 10 | /* 11 | * browser.js: Browser specific functionality for director. 12 | * 13 | * (C) 2011, Charlie Robbins, Paolo Fragomeni, & the Contributors. 14 | * MIT LICENSE 15 | * 16 | */ 17 | 18 | var dloc = document.location; 19 | 20 | function dlocHashEmpty() { 21 | // Non-IE browsers return '' when the address bar shows '#'; Director's logic 22 | // assumes both mean empty. 23 | return dloc.hash === '' || dloc.hash === '#'; 24 | } 25 | 26 | var listener = { 27 | mode: 'modern', 28 | hash: dloc.hash, 29 | history: false, 30 | 31 | check: function () { 32 | var h = dloc.hash; 33 | if (h != this.hash) { 34 | this.hash = h; 35 | this.onHashChanged(); 36 | } 37 | }, 38 | 39 | fire: function () { 40 | if (this.mode === 'modern') { 41 | this.history === true ? window.onpopstate() : window.onhashchange(); 42 | } 43 | else { 44 | this.onHashChanged(); 45 | } 46 | }, 47 | 48 | init: function (fn, history) { 49 | var self = this; 50 | this.history = history; 51 | 52 | if (!Router.listeners) { 53 | Router.listeners = []; 54 | } 55 | 56 | function onchange(onChangeEvent) { 57 | for (var i = 0, l = Router.listeners.length; i < l; i++) { 58 | Router.listeners[i](onChangeEvent); 59 | } 60 | } 61 | 62 | //note IE8 is being counted as 'modern' because it has the hashchange event 63 | if ('onhashchange' in window && (document.documentMode === undefined 64 | || document.documentMode > 7)) { 65 | // At least for now HTML5 history is available for 'modern' browsers only 66 | if (this.history === true) { 67 | // There is an old bug in Chrome that causes onpopstate to fire even 68 | // upon initial page load. Since the handler is run manually in init(), 69 | // this would cause Chrome to run it twise. Currently the only 70 | // workaround seems to be to set the handler after the initial page load 71 | // http://code.google.com/p/chromium/issues/detail?id=63040 72 | setTimeout(function() { 73 | window.onpopstate = onchange; 74 | }, 500); 75 | } 76 | else { 77 | window.onhashchange = onchange; 78 | } 79 | this.mode = 'modern'; 80 | } 81 | else { 82 | // 83 | // IE support, based on a concept by Erik Arvidson ... 84 | // 85 | var frame = document.createElement('iframe'); 86 | frame.id = 'state-frame'; 87 | frame.style.display = 'none'; 88 | document.body.appendChild(frame); 89 | this.writeFrame(''); 90 | 91 | if ('onpropertychange' in document && 'attachEvent' in document) { 92 | document.attachEvent('onpropertychange', function () { 93 | if (event.propertyName === 'location') { 94 | self.check(); 95 | } 96 | }); 97 | } 98 | 99 | window.setInterval(function () { self.check(); }, 50); 100 | 101 | this.onHashChanged = onchange; 102 | this.mode = 'legacy'; 103 | } 104 | 105 | Router.listeners.push(fn); 106 | 107 | return this.mode; 108 | }, 109 | 110 | destroy: function (fn) { 111 | if (!Router || !Router.listeners) { 112 | return; 113 | } 114 | 115 | var listeners = Router.listeners; 116 | 117 | for (var i = listeners.length - 1; i >= 0; i--) { 118 | if (listeners[i] === fn) { 119 | listeners.splice(i, 1); 120 | } 121 | } 122 | }, 123 | 124 | setHash: function (s) { 125 | // Mozilla always adds an entry to the history 126 | if (this.mode === 'legacy') { 127 | this.writeFrame(s); 128 | } 129 | 130 | if (this.history === true) { 131 | window.history.pushState({}, document.title, s); 132 | // Fire an onpopstate event manually since pushing does not obviously 133 | // trigger the pop event. 134 | this.fire(); 135 | } else { 136 | dloc.hash = (s[0] === '/') ? s : '/' + s; 137 | } 138 | return this; 139 | }, 140 | 141 | writeFrame: function (s) { 142 | // IE support... 143 | var f = document.getElementById('state-frame'); 144 | var d = f.contentDocument || f.contentWindow.document; 145 | d.open(); 146 | d.write("