-
-
Save brigand/72f31e90bb1bf3a0af88 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<script src="https://cdn.rawgit.com/zloirock/core-js/master/client/shim.js"></script> | |
<!-- uncomment for React master | |
<script src="http://react.zpao.com/builds/master/latest/react.js"></script> | |
--> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/codemirror.min.css"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/theme/solarized.min.css"> | |
<script src="http://fb.me/react-with-addons-0.13.1.js"></script> | |
<script src="http://www.parsecdn.com/js/parse-1.4.2.min.js"></script> | |
<script> | |
Parse.initialize("gHYJ6NIvgNO1kaEX1OgA1WnbCe2Muj5mrCTlKxLP", "DzlycTD5di7CirpgX9AyPTzBcLuf28FTqHtX4sT1"); | |
</script> | |
<meta charset="utf-8"> | |
<title>JS Bin</title> | |
<style id="jsbin-css"> | |
html, body { | |
margin: 0; | |
height: 100%; | |
} | |
body { | |
display: flex; | |
padding: 1em; | |
align-items: stretch; | |
flex-direction: column; | |
} | |
.App { | |
display: flex; | |
flex-wrap: wrap; | |
align-items: stretch; | |
width: 95%; | |
} | |
.App-section { | |
display: flex; | |
flex-direction: column; | |
align-items: stretch; | |
flex: 1; | |
flex-basis: 25em; | |
overflow: auto; | |
} | |
.App-section-input { | |
flex: 2; | |
flex-basis: 25em; | |
} | |
.App-section > textarea { | |
flex: 1; | |
flex-basis: 10em; | |
font-size: 1.2em; | |
font-family: monospace; | |
} | |
/* ReactJSONInspector */ | |
.json-inspector, | |
.json-inspector__selection { | |
font: 14px/1.4 Consolas, monospace; | |
} | |
.json-inspector__leaf { | |
padding-left: 10px; | |
} | |
.json-inspector__line { | |
display: block; | |
position: relative; | |
cursor: default; | |
} | |
.json-inspector__line:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: -200px; | |
right: -50px; | |
bottom: 0; | |
z-index: -1; | |
pointer-events: none; | |
} | |
.json-inspector__line:hover:after { | |
background: rgba(0, 0, 0, 0.06); | |
} | |
.json-inspector__leaf_composite > .json-inspector__line { | |
cursor: pointer; | |
} | |
.json-inspector__radio, | |
.json-inspector__flatpath { | |
display: none; | |
} | |
.json-inspector__value { | |
margin-left: 5px; | |
} | |
.json-inspector__search { | |
min-width: 300px; | |
margin: 0 10px 10px 0; | |
padding: 2px; | |
} | |
.json-inspector__key { | |
color: #505050; | |
} | |
.json-inspector__value_helper, | |
.json-inspector__value_null, | |
.json-inspector__not-found { | |
color: #b0b0b0; | |
} | |
.json-inspector__value_string { | |
color: #798953; | |
} | |
.json-inspector__value_boolean { | |
color: #75b5aa; | |
} | |
.json-inspector__value_number { | |
color: #d28445; | |
} | |
.json-inspector__hl { | |
background: #ff0; | |
box-shadow: 0 -1px 0 2px #ff0; | |
border-radius: 2px; | |
} | |
.json-inspector__show-original { | |
display: inline-block; | |
padding: 0 6px; | |
color: #666; | |
cursor: pointer; | |
} | |
.json-inspector__show-original:hover { | |
color: #111; | |
} | |
.json-inspector__show-original:before { | |
content: '⥂'; | |
} | |
.json-inspector__show-original:hover:after { | |
content: ' expand' | |
} | |
</style> | |
</head> | |
<body> | |
<div style="margin: auto; font-size: 20vh;" id="loading">Loading <div class="log" style="margin: auto; font-size: 10vh;">...</div></div> | |
<script src="https://cdn.rawgit.com/anonymous/e67adee9cbb2fc2a26a5/raw/a1df870c2eff4212efc4f262834cc4fe86ecebc8/babel.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/codemirror.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/mode/javascript/javascript.min.js"></script> | |
<!-- Utils --> | |
<script src="https://wzrd.in/standalone/circular-json"></script> | |
<script src="https://wzrd.in/standalone/lodash.debounce"></script> | |
<script src="https://www.parsecdn.com/js/parse-react.js"></script> | |
<!-- React Components --> | |
<script src="https://cdn.rawgit.com/anonymous/0e3b2440d50a2b7b0cd3/raw/5bff05d9e66803a85d87f4d9bb8be558b77e4a38/ReactJSONInspector.js"></script> | |
<script src="https://cdn.rawgit.com/ForbesLindesay/react-code-mirror/34629fd97ed48c356fc6462c63ab208b62938fac/standalone.js"></script> | |
<script id="jsbin-javascript"> | |
'use strict'; | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | |
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } | |
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } | |
var EDITOR_PROPS = { | |
mode: 'javascript', | |
theme: 'solarized', | |
lineNumbers: true | |
}; | |
var HASH_PREFIX = '#/'; | |
var DEFAULT_PLUGIN = 'module.exports = function (babel) {\n return new babel.Transformer("foo-bar", {\n FunctionDeclaration(node, parent) {\n return node;\n }\n });\n};'; | |
var DEFAULT_CODE = 'const z = (x) => y'; | |
function shallowCompareObj(a, b) { | |
return Object.keys(a).some(function (key) { | |
return a[key] !== b[key]; | |
}); | |
} | |
function getDefaultState() { | |
return { | |
plugin: DEFAULT_PLUGIN, | |
code: DEFAULT_CODE, | |
babelOptions: {} | |
}; | |
}; | |
var App = (function (_React$Component) { | |
function App(props) { | |
_classCallCheck(this, App); | |
_get(Object.getPrototypeOf(App.prototype), 'constructor', this).call(this, props); | |
this.state = Object.assign({ | |
resultAst: {}, | |
resultCode: '', | |
pluginResult: null, | |
// these come from props.initialData | |
plugin: null, | |
code: null, | |
babelOptions: null | |
}, props.initialData); | |
this.updatePlugin = lodash.debounce(this.updatePlugin.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
this.updateTransform = lodash.debounce(this.updateTransform.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
} | |
_inherits(App, _React$Component); | |
_createClass(App, [{ | |
key: 'componentWillMount', | |
value: function componentWillMount() { | |
this._lastKnownState = {}; | |
this.updateStuff(this.state); | |
} | |
}, { | |
key: 'componentWillUpdate', | |
value: function componentWillUpdate(nextProps, nextState) { | |
var sourceKeysOnly = function sourceKeysOnly(_ref) { | |
var plugin = _ref.plugin; | |
var code = _ref.code; | |
return { plugin: plugin, code: code }; | |
}; | |
var changed = shallowCompareObj(sourceKeysOnly(this.state), sourceKeysOnly(nextState)) || shallowCompareObj(this.state.babelOptions, nextState.babelOptions); | |
if (changed) { | |
this.updateStuff(nextState); | |
} | |
} | |
}, { | |
key: 'updateStuff', | |
value: function updateStuff(state) { | |
var pluginResult = this.updatePlugin(state.plugin); | |
this.updateTransform(Object.assign({}, state, { pluginResult: pluginResult })); | |
} | |
}, { | |
key: 'updatePlugin', | |
value: function updatePlugin(plugin) { | |
var makePlugin = new Function('module', plugin); | |
localStorage.babelPluginThingy = plugin; | |
var _module = {}; | |
makePlugin(_module); | |
this.setState({ pluginResult: _module.exports }); | |
return _module.exports; | |
} | |
}, { | |
key: 'updateTransform', | |
value: function updateTransform(state) { | |
state = Object.assign({}, this.state || {}, state); | |
var babelOptions = Object.assign({}, state.babelOptions, { | |
ast: true, | |
code: true, | |
plugins: (state.babelOptions.plugins || []).concat[{ | |
transformer: state.pluginResult, | |
position: 'before' | |
}] | |
}); | |
// attempt to transform | |
var ast = undefined, | |
code = undefined; | |
try { | |
var result = babel.transform(state.code, babelOptions); | |
ast = result.ast.program; | |
code = result.code; | |
} | |
// if this fails, just use the code output to place the error | |
catch (e) { | |
code = e.message + '\n' + e.stack; | |
} | |
var replacer = function replacer(k, v) { | |
return k[0] === '_' ? undefined : k === 'loc' || k === 'start' || k === 'end' || k === 'range' ? undefined : v; | |
}; | |
this.setState({ resultCode: code }); | |
this.setState({ resultAst: false }); | |
// todo: optimize? | |
if (ast) { | |
var json = JSON.stringify(ast, replacer); | |
var cleanAst = JSON.parse(json); | |
this.setState({ resultAst: cleanAst }); | |
this.props.onValid({ code: state.code, plugin: state.plugin, babelOptions: babelOptions }); | |
} | |
} | |
}, { | |
key: 'render', | |
value: function render() { | |
var _this = this; | |
return React.createElement( | |
'div', | |
{ className: 'App' }, | |
React.createElement( | |
'div', | |
{ className: 'App-section App-section-input' }, | |
React.createElement( | |
'h2', | |
null, | |
'Plugin ', | |
location.hash.slice(HASH_PREFIX.length) | |
), | |
React.createElement(CodeMirrorEditor, _extends({ | |
value: this.state.plugin, | |
onChange: function (e) { | |
_this.setState({ plugin: e.target.value }); | |
}, | |
onBlur: function () { | |
return _this.setState({}); | |
} | |
}, EDITOR_PROPS)) | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section App-section-input' }, | |
React.createElement( | |
'h2', | |
null, | |
'Code' | |
), | |
React.createElement(CodeMirrorEditor, _extends({ | |
value: this.state.code, | |
onChange: function (e) { | |
_this.setState({ code: e.target.value }); | |
}, | |
onBlur: function () { | |
return _this.setState({}); | |
} | |
}, EDITOR_PROPS)) | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section' }, | |
React.createElement( | |
'h2', | |
null, | |
'AST' | |
), | |
this.state.resultAst ? React.createElement(ReactJSONInspector, { data: this.state.resultAst }) : 'Failed to compile' | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section' }, | |
React.createElement( | |
'h2', | |
null, | |
'Result' | |
), | |
React.createElement( | |
'pre', | |
null, | |
this.state.resultCode | |
) | |
) | |
); | |
} | |
}], [{ | |
key: 'propTypes', | |
value: { | |
initialData: React.PropTypes.object.isRequired, | |
onValid: React.PropTypes.func.isRequired | |
}, | |
enumerable: true | |
}]); | |
return App; | |
})(React.Component); | |
var ParseBabelPlugin = Parse.Object.extend('BabelPlugin'); | |
var backendState = { | |
activeObject: null, | |
timesLoaded: 0 | |
}; | |
var backendService = { | |
_keys: ['code', 'plugin', 'babelOptions'], | |
assign: function assign(dest, source) { | |
var get = function get(obj, key) { | |
return typeof obj.get === 'function' ? obj.get(key) : obj[key]; | |
}; | |
var set = function set(obj, key, value) { | |
return typeof obj.set === 'function' ? obj.set(key, value) : obj[key] = value; | |
}; | |
backendService._keys.forEach(function (key) { | |
set(dest, key, get(source, key)); | |
}); | |
return dest; | |
}, | |
getMyACL: function getMyACL() { | |
var acl = new Parse.ACL(Parse.User.current()); | |
acl.setPublicReadAccess(true); | |
return acl; | |
}, | |
makeObject: function makeObject() { | |
var obj = new ParseBabelPlugin(); | |
var acl = this.getMyACL(); | |
obj.setACL(acl); | |
return backendService.assign(obj, getDefaultState()); | |
}, | |
ensureActiveObject: function ensureActiveObject() { | |
if (!backendService.activeObject) { | |
backendService.activeObject = backendService.makeObject(); | |
} | |
return backendService.activeObject; | |
}, | |
cloneIfCantWrite: function cloneIfCantWrite(obj) { | |
var canWrite = obj.getACL().getWriteAccess(Parse.User.current()); | |
if (canWrite) { | |
return obj; | |
} | |
return backendService.assign(backendService.makeObject(), obj); | |
}, | |
getDataForPage: function getDataForPage(cb) { | |
var hash = location.hash.slice(HASH_PREFIX.length); | |
if (!hash) { | |
backendService.log('Using defaults'); | |
return cb(null, getDefaultState()); | |
} | |
backendService.log('Requesting BabelPlugin' + hash); | |
var query = new Parse.Query(ParseBabelPlugin); | |
return query.get(hash).then(function (obj) { | |
backendService.log('Found BabelPlugin' + hash + ': ' + JSON.stringify(backendService.assign({}, obj))); | |
backendService.activeObject = obj; | |
return obj; | |
}).then(function (data) { | |
return cb(null, data); | |
}, function () { | |
cb(null, backendService.ensureActiveObject()); | |
}); | |
}, | |
eventuallyRender: function eventuallyRender() { | |
backendService.getDataForPage(function (err) { | |
backendService.timesLoaded++; | |
backendService.renderNow(); | |
}); | |
}, | |
renderNow: function renderNow() { | |
var simpleData = backendService.assign({}, backendService.ensureActiveObject()); | |
renderApp(simpleData, function onValid(newData) { | |
backendService.log('onValid'); | |
backendService.eventuallyPersist(newData); | |
}); | |
}, | |
eventuallyPersist: lodash.debounce(function (data) { | |
backendService.ensureActiveObject(); | |
backendService.assign(backendService.activeObject, data); | |
backendService.activeObject = backendService.cloneIfCantWrite(backendService.activeObject); | |
backendService.activeObject.save().then(function (obj) { | |
backendService.activeObject = obj; | |
backendService.updateUrl(); | |
backendService.renderNow(); | |
}, console.error.bind(console)); | |
}, 10000), | |
updateUrl: function updateUrl() { | |
var hash = location.hash; | |
if (backendService.activeObject) { | |
var id = backendService.activeObject.id; | |
if (hash !== HASH_PREFIX + id) { | |
location.hash = HASH_PREFIX + id; | |
} | |
} | |
}, | |
log: function log(msg) { | |
var logEl = document.getElementById('log'); | |
if (logEl) { | |
logEl.appendChild(document.createTextNode(msg)); | |
} | |
console.log(msg); | |
} | |
}; | |
function renderApp(data, onValid) { | |
var app = React.createElement(App, { | |
initialData: data, | |
onValid: onValid, | |
key: backendState.timesLoaded | |
}); | |
React.render(app, document.body); | |
} | |
location.hash = '#/dpsW2RxIxI'; | |
backendService.eventuallyRender(); | |
</script> | |
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html> | |
<html> | |
<head> | |
<script src="https://cdn.rawgit.com/zloirock/core-js/master/client/shim.js"><\/script> | |
<\!-- uncomment for React master | |
<script src="http://react.zpao.com/builds/master/latest/react.js"><\/script> | |
--> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/codemirror.min.css"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/theme/solarized.min.css"> | |
<script src="//fb.me/react-with-addons-0.13.1.js"><\/script> | |
<script src="//www.parsecdn.com/js/parse-1.4.2.min.js"><\/script> | |
<script> | |
Parse.initialize("gHYJ6NIvgNO1kaEX1OgA1WnbCe2Muj5mrCTlKxLP", "DzlycTD5di7CirpgX9AyPTzBcLuf28FTqHtX4sT1"); | |
<\/script> | |
<meta charset="utf-8"> | |
<title>JS Bin</title> | |
</head> | |
<body> | |
<div style="margin: auto; font-size: 20vh;" id="loading">Loading <div class="log" style="margin: auto; font-size: 10vh;">...</div></div> | |
<script src="https://cdn.rawgit.com/anonymous/e67adee9cbb2fc2a26a5/raw/a1df870c2eff4212efc4f262834cc4fe86ecebc8/babel.min.js"><\/script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/codemirror.min.js"><\/script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/4.7.0/mode/javascript/javascript.min.js"><\/script> | |
<\!-- Utils --> | |
<script src="https://wzrd.in/standalone/circular-json"><\/script> | |
<script src="https://wzrd.in/standalone/lodash.debounce"><\/script> | |
<script src="https://www.parsecdn.com/js/parse-react.js"><\/script> | |
<\!-- React Components --> | |
<script src="https://cdn.rawgit.com/anonymous/0e3b2440d50a2b7b0cd3/raw/5bff05d9e66803a85d87f4d9bb8be558b77e4a38/ReactJSONInspector.js"><\/script> | |
<script src="https://cdn.rawgit.com/ForbesLindesay/react-code-mirror/34629fd97ed48c356fc6462c63ab208b62938fac/standalone.js"><\/script> | |
</body> | |
</html></script> | |
<script id="jsbin-source-css" type="text/css">html, body { | |
margin: 0; | |
height: 100%; | |
} | |
body { | |
display: flex; | |
padding: 1em; | |
align-items: stretch; | |
flex-direction: column; | |
} | |
.App { | |
display: flex; | |
flex-wrap: wrap; | |
align-items: stretch; | |
width: 95%; | |
} | |
.App-section { | |
display: flex; | |
flex-direction: column; | |
align-items: stretch; | |
flex: 1; | |
flex-basis: 25em; | |
overflow: auto; | |
} | |
.App-section-input { | |
flex: 2; | |
flex-basis: 25em; | |
} | |
.App-section > textarea { | |
flex: 1; | |
flex-basis: 10em; | |
font-size: 1.2em; | |
font-family: monospace; | |
} | |
/* ReactJSONInspector */ | |
.json-inspector, | |
.json-inspector__selection { | |
font: 14px/1.4 Consolas, monospace; | |
} | |
.json-inspector__leaf { | |
padding-left: 10px; | |
} | |
.json-inspector__line { | |
display: block; | |
position: relative; | |
cursor: default; | |
} | |
.json-inspector__line:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: -200px; | |
right: -50px; | |
bottom: 0; | |
z-index: -1; | |
pointer-events: none; | |
} | |
.json-inspector__line:hover:after { | |
background: rgba(0, 0, 0, 0.06); | |
} | |
.json-inspector__leaf_composite > .json-inspector__line { | |
cursor: pointer; | |
} | |
.json-inspector__radio, | |
.json-inspector__flatpath { | |
display: none; | |
} | |
.json-inspector__value { | |
margin-left: 5px; | |
} | |
.json-inspector__search { | |
min-width: 300px; | |
margin: 0 10px 10px 0; | |
padding: 2px; | |
} | |
.json-inspector__key { | |
color: #505050; | |
} | |
.json-inspector__value_helper, | |
.json-inspector__value_null, | |
.json-inspector__not-found { | |
color: #b0b0b0; | |
} | |
.json-inspector__value_string { | |
color: #798953; | |
} | |
.json-inspector__value_boolean { | |
color: #75b5aa; | |
} | |
.json-inspector__value_number { | |
color: #d28445; | |
} | |
.json-inspector__hl { | |
background: #ff0; | |
box-shadow: 0 -1px 0 2px #ff0; | |
border-radius: 2px; | |
} | |
.json-inspector__show-original { | |
display: inline-block; | |
padding: 0 6px; | |
color: #666; | |
cursor: pointer; | |
} | |
.json-inspector__show-original:hover { | |
color: #111; | |
} | |
.json-inspector__show-original:before { | |
content: '⥂'; | |
} | |
.json-inspector__show-original:hover:after { | |
content: ' expand' | |
}</script> | |
<script id="jsbin-source-javascript" type="text/javascript">var EDITOR_PROPS = { | |
mode: 'javascript', | |
theme: 'solarized', | |
lineNumbers: true | |
}; | |
const HASH_PREFIX = '#/'; | |
const DEFAULT_PLUGIN = "module.exports = function (babel) {\n return new babel.Transformer(\"foo-bar\", {\n FunctionDeclaration(node, parent) {\n return node;\n }\n });\n};"; | |
const DEFAULT_CODE = 'const z = (x) => y'; | |
function shallowCompareObj(a, b){ | |
return Object.keys(a).some((key) => a[key] !== b[key]); | |
} | |
function getDefaultState() { | |
return { | |
plugin: DEFAULT_PLUGIN, | |
code: DEFAULT_CODE, | |
babelOptions: {} | |
} | |
}; | |
class App extends React.Component { | |
static propTypes = { | |
initialData: React.PropTypes.object.isRequired, | |
onValid: React.PropTypes.func.isRequired | |
}; | |
constructor(props){ | |
super(props); | |
this.state = Object.assign({ | |
resultAst: {}, | |
resultCode: '', | |
pluginResult: null, | |
// these come from props.initialData | |
plugin: null, | |
code: null, | |
babelOptions: null | |
}, props.initialData); | |
this.updatePlugin = lodash.debounce(this.updatePlugin.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
this.updateTransform = lodash.debounce(this.updateTransform.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
} | |
componentWillMount(){ | |
this._lastKnownState = {}; | |
this.updateStuff(this.state); | |
} | |
componentWillUpdate(nextProps, nextState){ | |
const sourceKeysOnly = ({plugin, code}) => ({plugin, code}); | |
const changed = ( | |
shallowCompareObj(sourceKeysOnly(this.state), sourceKeysOnly(nextState)) | |
|| shallowCompareObj(this.state.babelOptions, nextState.babelOptions) | |
); | |
if (changed) { | |
this.updateStuff(nextState); | |
} | |
} | |
updateStuff(state){ | |
const pluginResult = this.updatePlugin(state.plugin); | |
this.updateTransform(Object.assign({}, state, {pluginResult})); | |
} | |
updatePlugin(plugin){ | |
const makePlugin = new Function('module', plugin); | |
localStorage.babelPluginThingy = plugin; | |
const _module = {}; | |
makePlugin(_module); | |
this.setState({pluginResult: _module.exports}); | |
return _module.exports; | |
} | |
updateTransform(state){ | |
state = Object.assign({}, this.state || {}, state); | |
const babelOptions = Object.assign({}, state.babelOptions, { | |
ast: true, | |
code: true, | |
plugins: (state.babelOptions.plugins || []).concat[{ | |
transformer: state.pluginResult, | |
position: 'before' | |
}] | |
}); | |
// attempt to transform | |
let ast, code; | |
try { | |
let result = babel.transform(state.code, babelOptions); | |
ast = result.ast.program; | |
code = result.code; | |
} | |
// if this fails, just use the code output to place the error | |
catch (e) { | |
code = e.message + "\n" + e.stack; | |
} | |
const replacer = function(k, v){ | |
return k[0] === '_' ? undefined | |
: k === 'loc' || k === 'start' || k === 'end' || k === 'range' ? undefined | |
: v; | |
}; | |
this.setState({resultCode: code}); | |
this.setState({resultAst: false}); | |
// todo: optimize? | |
if (ast) { | |
const json = JSON.stringify(ast, replacer); | |
const cleanAst = JSON.parse(json); | |
this.setState({resultAst: cleanAst}); | |
this.props.onValid({code: state.code, plugin: state.plugin, babelOptions: babelOptions}); | |
} | |
} | |
render(){ | |
return ( | |
<div className="App"> | |
<div className="App-section App-section-input"> | |
<h2>Plugin {location.hash.slice(HASH_PREFIX.length)}</h2> | |
<CodeMirrorEditor | |
value={this.state.plugin} | |
onChange={(e) => { | |
this.setState({plugin: e.target.value}); | |
}} | |
onBlur={() => this.setState({})} | |
{...EDITOR_PROPS} | |
/> | |
</div> | |
<div className="App-section App-section-input"> | |
<h2>Code</h2> | |
<CodeMirrorEditor | |
value={this.state.code} | |
onChange={(e) => { | |
this.setState({code: e.target.value}); | |
}} | |
onBlur={() => this.setState({})} | |
{...EDITOR_PROPS} | |
/> | |
</div> | |
<div className="App-section"> | |
<h2>AST</h2> | |
{this.state.resultAst | |
? <ReactJSONInspector data={this.state.resultAst } /> | |
: 'Failed to compile'} | |
</div> | |
<div className="App-section"> | |
<h2>Result</h2> | |
<pre>{this.state.resultCode}</pre> | |
</div> | |
</div> | |
); | |
} | |
} | |
const ParseBabelPlugin = Parse.Object.extend("BabelPlugin"); | |
const backendState = { | |
activeObject: null, | |
timesLoaded: 0 | |
}; | |
const backendService = { | |
_keys: ['code', 'plugin', 'babelOptions'], | |
assign(dest, source){ | |
const get = (obj, key) => typeof obj.get === 'function' | |
? obj.get(key) : obj[key]; | |
const set = (obj, key, value) => typeof obj.set === 'function' | |
? obj.set(key, value) : (obj[key] = value); | |
backendService._keys.forEach((key) => { | |
set(dest, key, get(source, key)); | |
}); | |
return dest; | |
}, | |
getMyACL(){ | |
const acl = new Parse.ACL(Parse.User.current()); | |
acl.setPublicReadAccess(true); | |
return acl; | |
}, | |
makeObject(){ | |
var obj = new ParseBabelPlugin(); | |
var acl = this.getMyACL(); | |
obj.setACL(acl); | |
return backendService.assign(obj, getDefaultState()); | |
}, | |
ensureActiveObject(){ | |
if (!backendService.activeObject) { | |
backendService.activeObject = backendService.makeObject(); | |
} | |
return backendService.activeObject; | |
}, | |
cloneIfCantWrite(obj){ | |
const canWrite = obj.getACL().getWriteAccess(Parse.User.current()); | |
if (canWrite) { | |
return obj; | |
} | |
return backendService.assign( | |
backendService.makeObject(), | |
obj | |
); | |
}, | |
getDataForPage(cb){ | |
const hash = location.hash.slice(HASH_PREFIX.length); | |
if (!hash) { | |
backendService.log('Using defaults'); | |
return cb(null, getDefaultState()); | |
} | |
backendService.log('Requesting BabelPlugin' + hash); | |
var query = new Parse.Query(ParseBabelPlugin); | |
return query.get(hash) | |
.then((obj) => { | |
backendService.log('Found BabelPlugin' + hash + ': ' + JSON.stringify(backendService.assign({}, obj))); | |
backendService.activeObject = obj; | |
return obj; | |
}) | |
.then((data) => cb(null, data), () => { | |
cb(null, backendService.ensureActiveObject()); | |
}); | |
}, | |
eventuallyRender(){ | |
backendService.getDataForPage(function(err){ | |
backendService.timesLoaded++; | |
backendService.renderNow(); | |
}); | |
}, | |
renderNow(){ | |
const simpleData = backendService.assign({}, backendService.ensureActiveObject()); | |
renderApp(simpleData, function onValid(newData){ | |
backendService.log('onValid'); | |
backendService.eventuallyPersist(newData); | |
}); | |
}, | |
eventuallyPersist: lodash.debounce(function(data){ | |
backendService.ensureActiveObject(); | |
backendService.assign(backendService.activeObject, data); | |
backendService.activeObject = backendService.cloneIfCantWrite(backendService.activeObject); | |
backendService.activeObject.save() | |
.then((obj) => { | |
backendService.activeObject = obj; | |
backendService.updateUrl(); | |
backendService.renderNow(); | |
}, console.error.bind(console)); | |
}, 10e3), | |
updateUrl(){ | |
const hash = location.hash; | |
if (backendService.activeObject) { | |
const id = backendService.activeObject.id; | |
if (hash !== HASH_PREFIX + id) { | |
location.hash = HASH_PREFIX + id; | |
} | |
} | |
}, | |
log(msg){ | |
const logEl = document.getElementById('log'); | |
if (logEl) { | |
logEl.appendChild(document.createTextNode(msg)); | |
} | |
console.log(msg); | |
} | |
}; | |
function renderApp(data, onValid){ | |
var app = ( | |
<App | |
initialData={data} | |
onValid={onValid} | |
key={backendState.timesLoaded} | |
/> | |
); | |
React.render(app, document.body); | |
} | |
location.hash = '#/dpsW2RxIxI'; | |
backendService.eventuallyRender(); | |
</script></body> | |
</html> |
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
html, body { | |
margin: 0; | |
height: 100%; | |
} | |
body { | |
display: flex; | |
padding: 1em; | |
align-items: stretch; | |
flex-direction: column; | |
} | |
.App { | |
display: flex; | |
flex-wrap: wrap; | |
align-items: stretch; | |
width: 95%; | |
} | |
.App-section { | |
display: flex; | |
flex-direction: column; | |
align-items: stretch; | |
flex: 1; | |
flex-basis: 25em; | |
overflow: auto; | |
} | |
.App-section-input { | |
flex: 2; | |
flex-basis: 25em; | |
} | |
.App-section > textarea { | |
flex: 1; | |
flex-basis: 10em; | |
font-size: 1.2em; | |
font-family: monospace; | |
} | |
/* ReactJSONInspector */ | |
.json-inspector, | |
.json-inspector__selection { | |
font: 14px/1.4 Consolas, monospace; | |
} | |
.json-inspector__leaf { | |
padding-left: 10px; | |
} | |
.json-inspector__line { | |
display: block; | |
position: relative; | |
cursor: default; | |
} | |
.json-inspector__line:after { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: -200px; | |
right: -50px; | |
bottom: 0; | |
z-index: -1; | |
pointer-events: none; | |
} | |
.json-inspector__line:hover:after { | |
background: rgba(0, 0, 0, 0.06); | |
} | |
.json-inspector__leaf_composite > .json-inspector__line { | |
cursor: pointer; | |
} | |
.json-inspector__radio, | |
.json-inspector__flatpath { | |
display: none; | |
} | |
.json-inspector__value { | |
margin-left: 5px; | |
} | |
.json-inspector__search { | |
min-width: 300px; | |
margin: 0 10px 10px 0; | |
padding: 2px; | |
} | |
.json-inspector__key { | |
color: #505050; | |
} | |
.json-inspector__value_helper, | |
.json-inspector__value_null, | |
.json-inspector__not-found { | |
color: #b0b0b0; | |
} | |
.json-inspector__value_string { | |
color: #798953; | |
} | |
.json-inspector__value_boolean { | |
color: #75b5aa; | |
} | |
.json-inspector__value_number { | |
color: #d28445; | |
} | |
.json-inspector__hl { | |
background: #ff0; | |
box-shadow: 0 -1px 0 2px #ff0; | |
border-radius: 2px; | |
} | |
.json-inspector__show-original { | |
display: inline-block; | |
padding: 0 6px; | |
color: #666; | |
cursor: pointer; | |
} | |
.json-inspector__show-original:hover { | |
color: #111; | |
} | |
.json-inspector__show-original:before { | |
content: '⥂'; | |
} | |
.json-inspector__show-original:hover:after { | |
content: ' expand' | |
} |
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
'use strict'; | |
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; | |
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | |
var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } | |
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } | |
var EDITOR_PROPS = { | |
mode: 'javascript', | |
theme: 'solarized', | |
lineNumbers: true | |
}; | |
var HASH_PREFIX = '#/'; | |
var DEFAULT_PLUGIN = 'module.exports = function (babel) {\n return new babel.Transformer("foo-bar", {\n FunctionDeclaration(node, parent) {\n return node;\n }\n });\n};'; | |
var DEFAULT_CODE = 'const z = (x) => y'; | |
function shallowCompareObj(a, b) { | |
return Object.keys(a).some(function (key) { | |
return a[key] !== b[key]; | |
}); | |
} | |
function getDefaultState() { | |
return { | |
plugin: DEFAULT_PLUGIN, | |
code: DEFAULT_CODE, | |
babelOptions: {} | |
}; | |
}; | |
var App = (function (_React$Component) { | |
function App(props) { | |
_classCallCheck(this, App); | |
_get(Object.getPrototypeOf(App.prototype), 'constructor', this).call(this, props); | |
this.state = Object.assign({ | |
resultAst: {}, | |
resultCode: '', | |
pluginResult: null, | |
// these come from props.initialData | |
plugin: null, | |
code: null, | |
babelOptions: null | |
}, props.initialData); | |
this.updatePlugin = lodash.debounce(this.updatePlugin.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
this.updateTransform = lodash.debounce(this.updateTransform.bind(this), 300, { | |
leading: true, | |
trailing: true, | |
maxWait: 1000 | |
}); | |
} | |
_inherits(App, _React$Component); | |
_createClass(App, [{ | |
key: 'componentWillMount', | |
value: function componentWillMount() { | |
this._lastKnownState = {}; | |
this.updateStuff(this.state); | |
} | |
}, { | |
key: 'componentWillUpdate', | |
value: function componentWillUpdate(nextProps, nextState) { | |
var sourceKeysOnly = function sourceKeysOnly(_ref) { | |
var plugin = _ref.plugin; | |
var code = _ref.code; | |
return { plugin: plugin, code: code }; | |
}; | |
var changed = shallowCompareObj(sourceKeysOnly(this.state), sourceKeysOnly(nextState)) || shallowCompareObj(this.state.babelOptions, nextState.babelOptions); | |
if (changed) { | |
this.updateStuff(nextState); | |
} | |
} | |
}, { | |
key: 'updateStuff', | |
value: function updateStuff(state) { | |
var pluginResult = this.updatePlugin(state.plugin); | |
this.updateTransform(Object.assign({}, state, { pluginResult: pluginResult })); | |
} | |
}, { | |
key: 'updatePlugin', | |
value: function updatePlugin(plugin) { | |
var makePlugin = new Function('module', plugin); | |
localStorage.babelPluginThingy = plugin; | |
var _module = {}; | |
makePlugin(_module); | |
this.setState({ pluginResult: _module.exports }); | |
return _module.exports; | |
} | |
}, { | |
key: 'updateTransform', | |
value: function updateTransform(state) { | |
state = Object.assign({}, this.state || {}, state); | |
var babelOptions = Object.assign({}, state.babelOptions, { | |
ast: true, | |
code: true, | |
plugins: (state.babelOptions.plugins || []).concat[{ | |
transformer: state.pluginResult, | |
position: 'before' | |
}] | |
}); | |
// attempt to transform | |
var ast = undefined, | |
code = undefined; | |
try { | |
var result = babel.transform(state.code, babelOptions); | |
ast = result.ast.program; | |
code = result.code; | |
} | |
// if this fails, just use the code output to place the error | |
catch (e) { | |
code = e.message + '\n' + e.stack; | |
} | |
var replacer = function replacer(k, v) { | |
return k[0] === '_' ? undefined : k === 'loc' || k === 'start' || k === 'end' || k === 'range' ? undefined : v; | |
}; | |
this.setState({ resultCode: code }); | |
this.setState({ resultAst: false }); | |
// todo: optimize? | |
if (ast) { | |
var json = JSON.stringify(ast, replacer); | |
var cleanAst = JSON.parse(json); | |
this.setState({ resultAst: cleanAst }); | |
this.props.onValid({ code: state.code, plugin: state.plugin, babelOptions: babelOptions }); | |
} | |
} | |
}, { | |
key: 'render', | |
value: function render() { | |
var _this = this; | |
return React.createElement( | |
'div', | |
{ className: 'App' }, | |
React.createElement( | |
'div', | |
{ className: 'App-section App-section-input' }, | |
React.createElement( | |
'h2', | |
null, | |
'Plugin ', | |
location.hash.slice(HASH_PREFIX.length) | |
), | |
React.createElement(CodeMirrorEditor, _extends({ | |
value: this.state.plugin, | |
onChange: function (e) { | |
_this.setState({ plugin: e.target.value }); | |
}, | |
onBlur: function () { | |
return _this.setState({}); | |
} | |
}, EDITOR_PROPS)) | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section App-section-input' }, | |
React.createElement( | |
'h2', | |
null, | |
'Code' | |
), | |
React.createElement(CodeMirrorEditor, _extends({ | |
value: this.state.code, | |
onChange: function (e) { | |
_this.setState({ code: e.target.value }); | |
}, | |
onBlur: function () { | |
return _this.setState({}); | |
} | |
}, EDITOR_PROPS)) | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section' }, | |
React.createElement( | |
'h2', | |
null, | |
'AST' | |
), | |
this.state.resultAst ? React.createElement(ReactJSONInspector, { data: this.state.resultAst }) : 'Failed to compile' | |
), | |
React.createElement( | |
'div', | |
{ className: 'App-section' }, | |
React.createElement( | |
'h2', | |
null, | |
'Result' | |
), | |
React.createElement( | |
'pre', | |
null, | |
this.state.resultCode | |
) | |
) | |
); | |
} | |
}], [{ | |
key: 'propTypes', | |
value: { | |
initialData: React.PropTypes.object.isRequired, | |
onValid: React.PropTypes.func.isRequired | |
}, | |
enumerable: true | |
}]); | |
return App; | |
})(React.Component); | |
var ParseBabelPlugin = Parse.Object.extend('BabelPlugin'); | |
var backendState = { | |
activeObject: null, | |
timesLoaded: 0 | |
}; | |
var backendService = { | |
_keys: ['code', 'plugin', 'babelOptions'], | |
assign: function assign(dest, source) { | |
var get = function get(obj, key) { | |
return typeof obj.get === 'function' ? obj.get(key) : obj[key]; | |
}; | |
var set = function set(obj, key, value) { | |
return typeof obj.set === 'function' ? obj.set(key, value) : obj[key] = value; | |
}; | |
backendService._keys.forEach(function (key) { | |
set(dest, key, get(source, key)); | |
}); | |
return dest; | |
}, | |
getMyACL: function getMyACL() { | |
var acl = new Parse.ACL(Parse.User.current()); | |
acl.setPublicReadAccess(true); | |
return acl; | |
}, | |
makeObject: function makeObject() { | |
var obj = new ParseBabelPlugin(); | |
var acl = this.getMyACL(); | |
obj.setACL(acl); | |
return backendService.assign(obj, getDefaultState()); | |
}, | |
ensureActiveObject: function ensureActiveObject() { | |
if (!backendService.activeObject) { | |
backendService.activeObject = backendService.makeObject(); | |
} | |
return backendService.activeObject; | |
}, | |
cloneIfCantWrite: function cloneIfCantWrite(obj) { | |
var canWrite = obj.getACL().getWriteAccess(Parse.User.current()); | |
if (canWrite) { | |
return obj; | |
} | |
return backendService.assign(backendService.makeObject(), obj); | |
}, | |
getDataForPage: function getDataForPage(cb) { | |
var hash = location.hash.slice(HASH_PREFIX.length); | |
if (!hash) { | |
backendService.log('Using defaults'); | |
return cb(null, getDefaultState()); | |
} | |
backendService.log('Requesting BabelPlugin' + hash); | |
var query = new Parse.Query(ParseBabelPlugin); | |
return query.get(hash).then(function (obj) { | |
backendService.log('Found BabelPlugin' + hash + ': ' + JSON.stringify(backendService.assign({}, obj))); | |
backendService.activeObject = obj; | |
return obj; | |
}).then(function (data) { | |
return cb(null, data); | |
}, function () { | |
cb(null, backendService.ensureActiveObject()); | |
}); | |
}, | |
eventuallyRender: function eventuallyRender() { | |
backendService.getDataForPage(function (err) { | |
backendService.timesLoaded++; | |
backendService.renderNow(); | |
}); | |
}, | |
renderNow: function renderNow() { | |
var simpleData = backendService.assign({}, backendService.ensureActiveObject()); | |
renderApp(simpleData, function onValid(newData) { | |
backendService.log('onValid'); | |
backendService.eventuallyPersist(newData); | |
}); | |
}, | |
eventuallyPersist: lodash.debounce(function (data) { | |
backendService.ensureActiveObject(); | |
backendService.assign(backendService.activeObject, data); | |
backendService.activeObject = backendService.cloneIfCantWrite(backendService.activeObject); | |
backendService.activeObject.save().then(function (obj) { | |
backendService.activeObject = obj; | |
backendService.updateUrl(); | |
backendService.renderNow(); | |
}, console.error.bind(console)); | |
}, 10000), | |
updateUrl: function updateUrl() { | |
var hash = location.hash; | |
if (backendService.activeObject) { | |
var id = backendService.activeObject.id; | |
if (hash !== HASH_PREFIX + id) { | |
location.hash = HASH_PREFIX + id; | |
} | |
} | |
}, | |
log: function log(msg) { | |
var logEl = document.getElementById('log'); | |
if (logEl) { | |
logEl.appendChild(document.createTextNode(msg)); | |
} | |
console.log(msg); | |
} | |
}; | |
function renderApp(data, onValid) { | |
var app = React.createElement(App, { | |
initialData: data, | |
onValid: onValid, | |
key: backendState.timesLoaded | |
}); | |
React.render(app, document.body); | |
} | |
location.hash = '#/dpsW2RxIxI'; | |
backendService.eventuallyRender(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment