├── Makefile
├── app.css
├── components
├── ace-editor.jsx
├── app.jsx
├── call-stack-item.jsx
├── call-stack.jsx
├── callback-queue.jsx
├── callback.jsx
├── editor.jsx
├── event-loop-spinner.jsx
├── html-editor.jsx
├── render-queue.jsx
├── settings-panel.jsx
├── web-api-query.jsx
├── web-api-timer.jsx
└── web-apis.jsx
├── demo.html
├── index.html
├── lib
├── delay.js
├── instrument-code.js
├── plugins
│ ├── console.js
│ └── query.js
├── tag.js
├── text-cursor.js
└── wrap-insertion-points.js
├── loupe.bundle.css
├── loupe.bundle.js
├── loupe.css
├── loupe.js
├── loupe.jsx
├── models
├── apis.js
├── base-collection.js
├── callback-queue.js
├── callback.js
├── callstack.js
├── code.js
├── render-queue.js
├── stack-frame.js
├── stack-frames.js
├── timeout.js
└── timeouts.js
├── npm-debug.log
├── package.json
├── router.js
├── templates.js
└── tests
└── text-cursor-test.js
/Makefile:
--------------------------------------------------------------------------------
1 | SHELL := /bin/bash
2 | PATH := node_modules/.bin:$(PATH)
3 |
4 | JS_FILES := $(shell glob-cli "lib/**/*.js" "models/**/*.js" "*.js")
5 |
6 | JSX_FILES := $(shell glob-cli "components/**/*.jsx")
7 |
8 | build: loupe.bundle.js loupe.bundle.css
9 |
10 | loupe.bundle.js: $(JS_FILES) $(JSX_FILES)
11 | browserify -t reactify loupe.js > loupe.bundle.js
12 |
13 | loupe.bundle.css: loupe.css
14 | autoprefixer loupe.css -o loupe.bundle.css
15 |
--------------------------------------------------------------------------------
/app.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | min-height: 100%;
7 | font-family: courier;
8 | font-size: 18px;
9 | }
10 |
11 | #timer-queue {
12 | width: 100px;
13 | position: absolute;
14 | top: 20px;
15 | right: 20px;
16 | }
17 | #timer-queue li {
18 | background: gray;
19 | padding: 20px
20 | }
21 |
22 | .editor {
23 | height: 100%;
24 | width: 100%;
25 | border: 1px gray solid;
26 | font-family: monospace;
27 | font-size: 16px;
28 | line-height: 1.5;
29 | padding: 10px;
30 | margin: 10px;
31 | display: inline-block;
32 | white-space: pre;
33 | }
34 |
35 | .editor span.running {
36 | background: rgba(236, 117, 74, 0.25);
37 | }
38 |
39 | .code-node {
40 | }
41 |
42 | .code-node.running {
43 | background: rgba(255,0,0,0.5);
44 | }
45 |
46 | .code-node:not(.running) {
47 | background: none
48 | transition: background 0.25s linear;
49 | -webkit-transition: background 0.25s linear;
50 | }
51 |
52 | [data-hook=code] {
53 | width: 50%;
54 | position: absolute;
55 | left: 50%;
56 | top: 0;
57 | height: 75%;
58 | }
59 |
60 | [data-hook=stack] {
61 | width: 25%;
62 | position: absolute;
63 | top: 0;
64 | left: 0%;
65 | height: 75%;
66 | border: 1px gray solid;
67 | }
68 |
69 | [data-hook=stack] ul {
70 | position: absolute;
71 | bottom: 0;
72 | list-style-type: none;
73 | padding: 0;
74 | width: 100%;
75 | margin: 0;
76 | }
77 |
78 | [data-hook=stack] ul li {
79 | padding: 10px;
80 | background: #F0E03F;
81 | margin: 10px;
82 | width: calc(100% - 20px);
83 | }
84 |
85 | [data-hook=timeouts] {
86 | width: 100%;
87 | position: absolute;
88 | top: 75%;
89 | left: 0;
90 | height: 12.5%;
91 | }
92 |
93 | [data-hook=timeouts]:before {
94 | content: "Timeouts:";
95 | opacity: 0.5;
96 | padding: 5px;
97 | margin: 5px;
98 | background: gray;
99 | color: white;
100 | }
101 |
102 | [data-hook=stack]:before {
103 | content: "Stack";
104 | opacity: 0.5;
105 | padding: 5px;
106 | margin: 5px;
107 | background: gray;
108 | color: white;
109 | }
110 |
111 | [data-hook=timeouts] li {
112 | background: coral;
113 | padding: 20px;
114 | margin: 5px;
115 | list-style-type: none;
116 | display: inline-block;
117 | }
118 |
119 | [data-hook=timeouts] li.started {
120 | background: lime;
121 | }
122 |
123 | [data-hook=timeouts] li.finished {
124 | background: lime;
125 | opacity: 0.25;
126 | }
127 |
128 |
129 | [data-hook=callbacks] {
130 | width: 100%;
131 | position: absolute;
132 | top: 87.5%;
133 | left: 0;
134 | height: 12.5%;
135 | }
136 |
--------------------------------------------------------------------------------
/components/ace-editor.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | var ace = require('brace');
4 | require('brace/mode/javascript');
5 | require('brace/mode/html');
6 | require('brace/theme/solarized_light');
7 |
8 | module.exports = React.createClass({
9 | getDefaultProps: function () {
10 | return {
11 | mode: 'javascript',
12 | initialValue: '',
13 | onBlur: function () { },
14 | onCodeChange: function (newCode) {
15 | console.log('Code changed to', newCode);
16 | }
17 | };
18 | },
19 | componentDidMount: function () {
20 | this.editor = ace.edit(this.getDOMNode());
21 | this.editSession = this.editor.getSession();
22 |
23 | this.editor.getSession().setMode('ace/mode/' + this.props.mode);
24 | this.editor.setTheme('ace/theme/solarized_light');
25 |
26 | this.editor.focus();
27 | this.editor.setValue(this.props.initialValue, -1);
28 |
29 | this.editor.on('blur', function () {
30 | this.props.onCodeChange(this.editor.getValue().split('\n'));
31 | this.props.onBlur();
32 | }.bind(this));
33 | },
34 |
35 | componentWillUnmount: function () {
36 | this.editor.destroy();
37 | },
38 |
39 | render: function () {
40 | return (
41 |
42 | );
43 | }
44 | });
45 |
--------------------------------------------------------------------------------
/components/app.jsx:
--------------------------------------------------------------------------------
1 | /* JSX: React.DOM */
2 |
3 | var React = require('react');
4 | var CallStack = require('./call-stack.jsx');
5 | var EventLoopSpinner = require('./event-loop-spinner.jsx');
6 | var WebApis = require('./web-apis.jsx');
7 | var Editor = require('./editor.jsx');
8 | var CallbackQueue = require('./callback-queue.jsx');
9 | var RenderQueue = require('./render-queue.jsx');
10 | var HTMLEditor = require('./html-editor.jsx');
11 | var SettingsPanel = require('./settings-panel.jsx');
12 | var EventMixin = require('react-backbone-events-mixin');
13 | var Modal = require('react-modal');
14 |
15 | module.exports = React.createClass({
16 | mixins: [EventMixin],
17 |
18 | getInitialState: function () {
19 | var showRenderQueue = window.location.search.match(/show-renders/);
20 |
21 | return {
22 | settingsOpen: false,
23 | code: app.store.code,
24 | modalOpen: true
25 | };
26 | },
27 |
28 | openModal: function () {
29 | this.setState({ modalOpen: true });
30 | },
31 |
32 | closeModal: function () {
33 | this.setState({ modalOpen: false });
34 | },
35 |
36 | registerListeners: function (props, state) {
37 | this.listenTo(state.code, 'change:simulateRenders', function () {
38 | this.forceUpdate();
39 | }.bind(this));
40 | },
41 |
42 | toggleSettings: function () {
43 | this.setState({
44 | settingsOpen: !this.state.settingsOpen
45 | });
46 | },
47 | render: function () {
48 | return (
49 |
50 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | { this.state.code.simulateRenders ? : null }
85 |
86 |
87 |
88 |
89 |
90 |
91 |
94 |
95 | close
96 |
97 | Loupe
98 | Intro
99 | Loupe is a little visualisation to help you understand how JavaScript's call stack/event loop/callback queue interact with each other.
100 | The best thing to do to understand how this works is watch this video, then when you are ready, go play!
101 |
102 | Instructions
103 |
111 |
112 | How does this work?
113 |
114 | - Loupe runs entirely in your browser.
115 | - It takes your code.
116 | - Runs it through esprima, a JS parser.
117 | - Instruments it a bunch so that loupe knows where function calls, timeouts, dom events, etc happen.
118 | - Adds a whole bunch of while loops everywhere to slow down the code as it runs.
119 | - This modified code is then turned back into JavaScript and sent to a webworker (in your browser) which runs it.
120 | - As it runs, the instrumentation sends messages to the visualisation about what is going on so it can animate things at the right time.
121 | - It also has some extra magic to make dom events, and timers work properly.
122 |
123 |
124 | Built by Philip Roberts from &yet. Code is on github.
125 |
126 | Got it? Close this dialog
127 |
128 |
129 | )
130 | }
131 | });
132 |
--------------------------------------------------------------------------------
/components/call-stack-item.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | render: function () {
5 | var classes = "stack-item";
6 | if (this.props.isCallback) {
7 | classes += " stack-item-callback";
8 | }
9 |
10 | return (
11 |
12 | {this.props.children}
13 |
14 | );
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/components/call-stack.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
3 | var CallStackItem = require('./call-stack-item.jsx');
4 | var EventsMixin = require('react-backbone-events-mixin');
5 |
6 | module.exports = React.createClass({
7 | mixins: [
8 | EventsMixin
9 | ],
10 |
11 | registerListeners: function (props, state) {
12 | var self = this;
13 |
14 | this.listenTo(state.stack, 'all', function () {
15 | self.forceUpdate();
16 | });
17 |
18 | },
19 |
20 | getInitialState: function () {
21 | return {
22 | stack: window.app.store.callstack
23 | };
24 | },
25 |
26 | render: function () {
27 | var calls = [];
28 |
29 | this.state.stack.each(function (call) {
30 | calls.unshift({call.code});
31 | });
32 |
33 | return (
34 |
35 |
36 |
37 | {calls}
38 |
39 |
40 |
41 | );
42 | }
43 | });
44 |
--------------------------------------------------------------------------------
/components/callback-queue.jsx:
--------------------------------------------------------------------------------
1 | /* JSX: React.DOM */
2 |
3 | var React = require('react/addons');
4 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
5 | var Callback = require('./callback.jsx');
6 | var EventsMixin = require('react-backbone-events-mixin');
7 |
8 | module.exports = React.createClass({
9 | mixins: [
10 | EventsMixin
11 | ],
12 | getInitialState: function () {
13 | return {
14 | queue: app.store.queue
15 | };
16 | },
17 | registerListeners: function (props, state) {
18 | this.listenTo(state.queue, 'all', function () {
19 | this.forceUpdate();
20 | }.bind(this));
21 | },
22 | render: function () {
23 | var queue = this.state.queue.map(function (callback) {
24 | return (
25 |
26 | {callback.code}
27 |
28 | );
29 | });
30 |
31 | return (
32 |
33 |
34 | {queue}
35 |
36 |
37 | )
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/components/callback.jsx:
--------------------------------------------------------------------------------
1 | /* JSX: React.DOM */
2 |
3 | var React = require('react');
4 |
5 | module.exports = React.createClass({
6 | render: function () {
7 | var classes = ["callback", "callback-" + this.props.state].join(' ');
8 | return (
9 |
10 | {this.props.children}
11 |
12 | );
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/components/editor.jsx:
--------------------------------------------------------------------------------
1 | /* JSX: React.DOM */
2 |
3 | var React = require('react');
4 | var EventMixin = require('react-backbone-events-mixin');
5 | var AceEditor = require('./ace-editor.jsx');
6 |
7 | module.exports = React.createClass({
8 | mixins: [
9 | EventMixin
10 | ],
11 |
12 | registerListeners: function (props, state) {
13 | var self = this;
14 |
15 | this.listenTo(state.code, 'change', function () {
16 | this.forceUpdate();
17 | }.bind(this));
18 |
19 | this.listenTo(state.code, 'node:will-run', function (id) {
20 | var node = self.refs.code.getDOMNode().querySelector('#node-' + id);
21 | node.classList.add('running');
22 | });
23 |
24 | this.listenTo(state.code, 'node:did-run', function (id) {
25 | var node = self.refs.code.getDOMNode().querySelector('#node-' + id);
26 | node.classList.remove('running');
27 | });
28 | },
29 |
30 | getInitialState: function () {
31 | return {
32 | code: app.store.code,
33 | editing: true
34 | };
35 | },
36 |
37 | onCodeChange: function (newCode) {
38 | this.state.code.codeLines = newCode;
39 | },
40 |
41 | onEditBlur: function () {
42 | this.setState({ editing: false });
43 | //var newCode = this.refs.code.getDOMNode().innerText;
44 | //this.state.code.html = newCode;
45 | //this.setState({ editing: false });
46 | },
47 |
48 | saveAndRunCode: function () {
49 | this.setState({ editing: false });
50 | this.runCode();
51 | },
52 |
53 | runCode: function () {
54 | this.state.code.run();
55 | },
56 |
57 | pauseCode: function () {
58 | this.state.code.pause();
59 | },
60 |
61 | resumeCode: function () {
62 | this.state.code.resume();
63 | },
64 |
65 | onEditFocus: function () {
66 | this.state.code.resetEverything();
67 | this.setState({ editing: true });
68 | },
69 |
70 | render: function () {
71 | if (this.state.editing) {
72 | return (
73 |
74 |
75 |
76 |
77 |
83 |
84 | );
85 | } else {
86 | var i = 0;
87 | var lines = this.state.code.codeLines.map(function () { i++; return i; }).join(String.fromCharCode(10));
88 |
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
104 |
105 | );
106 |
107 | }
108 |
109 | //var innerHTML = this.state.editing ? this.state.code.html : this.state.code.wrappedHtml;
110 |
111 | //return (
112 | //
120 | //);
121 | }
122 | });
123 |
--------------------------------------------------------------------------------
/components/event-loop-spinner.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var EventsMixin = require('react-backbone-events-mixin');
3 |
4 | module.exports = React.createClass({
5 | mixins: [
6 | EventsMixin
7 | ],
8 |
9 | getInitialState: function () {
10 | return {
11 | code: app.store.code
12 | };
13 | },
14 |
15 | registerListeners: function (props, state) {
16 | this.listenTo(state.code, 'callback:shifted', function () {
17 | var domnode = this.refs.spinner.getDOMNode();
18 | domnode.classList.add('spinner-wrapper-transition');
19 | var onTransitionEnd = function () {
20 | domnode.classList.remove('spinner-wrapper-transition');
21 | domnode.removeEventListener('transitionend', onTransitionEnd, false);
22 | };
23 | domnode.addEventListener('transitionend', onTransitionEnd, false);
24 | }.bind(this));
25 | },
26 |
27 | render: function () {
28 | return (
29 |
34 | );
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/components/html-editor.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var AceEditor = require('./ace-editor.jsx');
3 | var EventMixin = require('react-backbone-events-mixin');
4 |
5 | module.exports = React.createClass({
6 | mixins: [EventMixin],
7 |
8 | registerListeners: function (props, state) {
9 | this.listenTo(state.code, 'ready-to-run', function () {
10 | this.setState({ editing: false });
11 | });
12 |
13 | //this.listenTo(state.code, 'change:code', function () {
14 | // this.forceUpdate();
15 | //});
16 | },
17 |
18 | getInitialState: function () {
19 | return {
20 | editing: false,
21 | code: app.store.code,
22 | };
23 | },
24 |
25 | switchMode: function () {
26 | var newValue = !this.state.editing;
27 | this.setState({ editing: newValue });
28 | },
29 |
30 | onCodeChange: function (newCode) {
31 | this.state.code.htmlScratchpad = newCode;
32 | },
33 |
34 | render: function () {
35 | if (this.state.editing) {
36 | return (
37 |
46 | );
47 | };
48 |
49 | var innerHTML = { __html: this.state.code.rawHtmlScratchpad };
50 |
51 | return (
52 |
56 | );
57 | }
58 | });
59 |
--------------------------------------------------------------------------------
/components/render-queue.jsx:
--------------------------------------------------------------------------------
1 | /* JSX: React.DOM */
2 |
3 | var React = require('react/addons');
4 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
5 | var Callback = require('./callback.jsx');
6 | var EventsMixin = require('react-backbone-events-mixin');
7 |
8 | module.exports = React.createClass({
9 | mixins: [
10 | EventsMixin
11 | ],
12 |
13 | getInitialState: function () {
14 | return {
15 | queue: app.store.renderQueue
16 | };
17 | },
18 |
19 | registerListeners: function (props, state) {
20 | this.listenTo(state.queue, 'all', function () {
21 | this.forceUpdate();
22 | }.bind(this));
23 | },
24 |
25 | render: function () {
26 | var queue = this.state.queue.map(function (callback) {
27 | return (
28 |
29 | {callback.id}
30 |
31 | );
32 | });
33 |
34 | return (
35 |
36 | {queue}
37 |
38 | )
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/components/settings-panel.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var EventMixin = require('react-backbone-events-mixin');
3 |
4 | module.exports = React.createClass({
5 | mixins: [EventMixin],
6 |
7 | registerListeners: function (props, state) {
8 | this.listenTo(state.code, 'change:delay', function () {
9 | this.forceUpdate();
10 | }.bind(this));
11 | },
12 |
13 | getInitialState: function () {
14 | return {
15 | code: app.store.code
16 | };
17 | },
18 |
19 | changeDelay: function () {
20 | this.state.code.delay = parseInt(this.refs.delay.getDOMNode().value);
21 | },
22 |
23 | changeRenders: function () {
24 | app.store.code.simulateRenders = this.refs.renders.getDOMNode().checked;
25 | },
26 |
27 | render: function () {
28 | var classes = "flexChild columnParent settingsColumn";
29 | if (!this.props.open) { classes += " hidden"; }
30 |
31 | return (
32 |
44 | );
45 | }
46 | });
47 |
--------------------------------------------------------------------------------
/components/web-api-query.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | flash: function () {
5 | var el = this.getDOMNode();
6 | el.classList.add('tr-webapi-spawn');
7 | setTimeout(function () {
8 | el.classList.add('tr-webapi-spawn-active');
9 | }, 16.6);
10 |
11 | var fallbackTimeout = setTimeout(function () {
12 | try {
13 | onTransitionOutEnd();
14 | onTransitionInEnd();
15 | } catch (e) {
16 | }
17 | }, 1000);
18 |
19 | var onTransitionOutEnd = function () {
20 | el.classList.remove('tr-webapi-spawn');
21 | el.removeEventListener('transitionend', onTransitionOutEnd, false);
22 | };
23 |
24 | var onTransitionInEnd = function () {
25 | el.classList.remove('tr-webapi-spawn-active');
26 | el.removeEventListener('transitionend', onTransitionInEnd, false);
27 | el.addEventListener('transitionend', onTransitionOutEnd, false);
28 | };
29 |
30 | el.addEventListener('transitionend', onTransitionInEnd, false);
31 | },
32 |
33 | render: function () {
34 | return (
35 |
36 |
37 | {this.props.children}
38 |
39 |
40 | );
41 | }
42 | });
43 |
--------------------------------------------------------------------------------
/components/web-api-timer.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 |
3 | module.exports = React.createClass({
4 | render: function () {
5 | var animStyle = {
6 | animationDuration: this.props.timeout,
7 | WebkitAnimationDuration: this.props.timeout,
8 | animationPlayState: this.props.playState,
9 | WebkitAnimationPlayState: this.props.playState
10 | };
11 |
12 | return (
13 |
14 |
15 | {this.props.children}
16 |
17 |
22 |
23 | );
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/components/web-apis.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | var WebApiTimer = require('./web-api-timer.jsx');
3 | var WebApiQuery = require('./web-api-query.jsx');
4 | var EventMixin = require('react-backbone-events-mixin');
5 | var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
6 |
7 | module.exports = React.createClass({
8 | mixins: [
9 | EventMixin
10 | ],
11 |
12 | registerListeners: function (props, state) {
13 | this.listenTo(state.apis, 'all', function () {
14 | this.forceUpdate();
15 | }.bind(this));
16 |
17 | this.listenTo(state.apis, 'callback:spawned', function (model) {
18 | this.refs[model.id].flash();
19 | }.bind(this));
20 | },
21 |
22 | getInitialState: function () {
23 | return {
24 | apis: app.store.apis
25 | };
26 | },
27 |
28 | render: function () {
29 | var apis = this.state.apis.map(function (api) {
30 | if (api.type === 'timeout') {
31 | return {api.code};
32 | }
33 | if (api.type === 'query') {
34 | return (
35 |
36 | {api.code}
37 |
38 | );
39 | }
40 | });
41 |
42 | return (
43 |
44 |
45 | {apis}
46 |
47 |
48 | )
49 | }
50 | });
51 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/delay.js:
--------------------------------------------------------------------------------
1 | module.exports = function (delay, fromId) {
2 | var microDelay = 15;
3 | var id = 0;
4 | if (fromId) { loupe.skipDelays = true; }
5 | var nMicroDelays = delay / microDelay;
6 |
7 | return function () {
8 | id++;
9 | var microId = 0;
10 | var start, target;
11 | var countdown = Math.floor(nMicroDelays);
12 |
13 | //var wasSkipping = loupe.skipDelays;
14 | //var startedAt = new Date().getTime();
15 | while (countdown--) {
16 | weevil.send('delay', id + microId);
17 | loupe.triggerDelay(id + microId);
18 |
19 | if (!fromId || (id + microId) > fromId) {
20 | loupe.skipDelays = false;
21 |
22 | start = new Date().getTime();
23 | target = start + microDelay;
24 | while (new Date().getTime() < target) {
25 | //no-op
26 | }
27 | }
28 |
29 | microId += 0.000001;
30 | }
31 | //var actual = new Date().getTime() - startedAt;
32 | //if (loupe.skipDelays) {
33 | // console.log(['Skipped', id, 'in', actual]);
34 | //} else {
35 | // if (wasSkipping) {
36 | // console.log([id, 'Partial!', delay, 'actual', actual, 'error', actual - delay]);
37 | // } else {
38 | // console.log([id, 'Target', delay, 'actual', actual, 'error', actual - delay]);
39 | // }
40 | //}
41 | };
42 | };
43 |
--------------------------------------------------------------------------------
/lib/instrument-code.js:
--------------------------------------------------------------------------------
1 | var falafel = require('falafel');
2 |
3 | var call = function (name) {
4 | return function (arg) {
5 | return arg[name]();
6 | };
7 | };
8 |
9 | var instruments = {
10 | ExpressionStatement: function (id, node, before, after) {
11 | node.update(node.source() + ';');
12 | },
13 | CallExpression: function (id, node, before, after) {
14 | var source = node.source();
15 |
16 | if (node.callee.source() === 'console.log') {
17 | source = "_console.log(" + node.loc.start.line + ", " + node.arguments.map(call('source')).join(', ') + ")";
18 | }
19 |
20 | var newString = '(' + before(id, node) + ', ' + source + ', ' + after(id, node) + ')\n';
21 | node.update(newString);
22 | },
23 | BinaryExpression: function (id, node, before, after) {
24 | var newString = '(' + before(id, node) + ', ' + after(id, node) + ', ' + node.source() + ')';
25 | node.update(newString);
26 | },
27 | //MemberExpression: function (id, node, before, after) {
28 | // var source = node.source();
29 | // if (source === 'console.log') {
30 | // source = '_console.log';
31 | // }
32 |
33 | // node.update(source);
34 | //}
35 | };
36 |
37 | var isInstrumentable = function (node) {
38 | return !!instruments[node.type];
39 | };
40 |
41 | var instrumentNode = function (id, node, before, after) {
42 | instruments[node.type](id, node, before, after);
43 | };
44 |
45 |
46 | module.exports = function (code, options) {
47 | var before = options.before || function () { return ''; };
48 | var after = options.after || function () { return ''; };
49 |
50 | var insertionPoints = [];
51 | var id = 1;
52 | var instrumented = falafel(code, { loc: true, range: true }, function (node) {
53 | if (!isInstrumentable(node)) return;
54 | insertionPoints.push({
55 | id: id,
56 | type: 'start',
57 | loc: node.loc.start
58 | });
59 | insertionPoints.push({
60 | id: id,
61 | type: 'end',
62 | loc: node.loc.end
63 | });
64 | instrumentNode(id, node, before, after);
65 | id++;
66 | });
67 |
68 | insertionPoints.sort(function (a, b) {
69 | if (a.loc.line === b.loc.line) return a.loc.column - b.loc.column;
70 | return a.loc.line - b.loc.line;
71 | });
72 |
73 | return {
74 | code: instrumented,
75 | insertionPoints: insertionPoints
76 | };
77 | };
78 |
79 |
--------------------------------------------------------------------------------
/lib/plugins/console.js:
--------------------------------------------------------------------------------
1 | var deval = require('deval');
2 |
3 | module.exports.prependWorkerCode = function (code) {
4 | return this.server + ';\n' + code;
5 | };
6 |
7 | module.exports.server = deval(function () {
8 | var _console = {};
9 |
10 | _console.log = function () {
11 | if (loupe.paused) return;
12 |
13 | weevil.send('console:log', [].slice.call(arguments));
14 | };
15 | });
16 |
17 | module.exports.createClient = function (codeModel, emitter) {
18 | emitter.on('console:log', function (args) {
19 | args.unshift('color: coral; font-weight: bold;');
20 | args.unshift('%c Loupe @ %d >');
21 | console.log.apply(console, args);
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/lib/plugins/query.js:
--------------------------------------------------------------------------------
1 | var deval = require('deval');
2 |
3 | module.exports.prependWorkerCode = function(code) {
4 | return this.server + ';\n' + code;
5 | };
6 |
7 | module.exports.server = deval(function () {
8 | var $ = {
9 | _callbacks: {},
10 | replayCallbacks: function (cbs) {
11 | var self = this;
12 |
13 | Object.keys(cbs).forEach(function (delayId) {
14 | var args = cbs[delayId];
15 | loupe.onDelay(delayId, function () {
16 | self._callbacks[args[0]](args[1]);
17 | });
18 | });
19 | },
20 | register: function (emitter) {
21 | this.emitter = emitter;
22 | this.emitter.on('query:event', function (id, callbackId) {
23 | this._callbacks[id](callbackId);
24 | }.bind(this));
25 | },
26 |
27 | on: function (selector, event, cb) {
28 | var self = this;
29 |
30 | var id = 'query:' + this.nextId();
31 | var callbackName = (cb.name || "anonymous") + "()";
32 | this.emitter.send('query:addEventListener', { selector: selector, event: event, id: id, source: callbackName });
33 |
34 | this._callbacks[id] = function (callbackId) {
35 | delay();
36 | self.emitter.send('callback:shifted', callbackId);
37 | delay();
38 | cb();
39 | self.emitter.send('callback:completed', callbackId);
40 | };
41 | },
42 |
43 | nextId: function () {
44 | if (!this._id) this._id = 0;
45 | this._id++;
46 | return this._id;
47 | }
48 | };
49 | $.register(weevil);
50 | $.replayCallbacks(loupe.appState.query);
51 | });
52 |
53 | module.exports.createClient = function (codeModel, emitter, document) {
54 | var listeners = [
55 | ];
56 |
57 | var historyLog = {
58 | };
59 |
60 | window.historyLog = historyLog;
61 |
62 | codeModel.on('ready-to-run', function () {
63 | //historyLog = {};
64 | });
65 |
66 | emitter.on('query:addEventListener', function (data) {
67 | var els;
68 |
69 | codeModel.trigger('webapi:started', {
70 | id: data.id,
71 | type: 'query',
72 | selector: data.selector,
73 | event: data.event
74 | });
75 |
76 | //var els = document.querySelectorAll(data.selector);
77 | if (data.selector === 'document') {
78 | els = [document];
79 | } else {
80 | els = document.querySelectorAll(data.selector);
81 | }
82 |
83 | [].forEach.call(els, function (el) {
84 | var cb = function () {
85 |
86 | var callbackId = data.id + ":" + Date.now();
87 | codeModel.trigger('callback:spawn', {
88 | id: callbackId,
89 | apiId: data.id,
90 | code: "[" + data.event + "] " + data.source
91 | });
92 |
93 | historyLog[codeModel.currentExecution] = [data.id, callbackId];
94 |
95 | emitter.send('query:event', data.id, callbackId);
96 | };
97 |
98 | listeners.push([el, data.event, cb, false]);
99 | el.addEventListener(data.event, cb, false);
100 | });
101 | });
102 |
103 | codeModel.on('reset-everything', function () {
104 | listeners.forEach(function (listener) {
105 | var el = listener.shift();
106 | el.removeEventListener.apply(el, listener);
107 | });
108 | listeners = [];
109 | });
110 |
111 | return {
112 | historyLog: historyLog
113 | };
114 | };
115 |
--------------------------------------------------------------------------------
/lib/tag.js:
--------------------------------------------------------------------------------
1 | module.exports.o = function open (tagName, attrs) {
2 | attrs = attrs || {};
3 |
4 | attrs = Object.keys(attrs).map(function (attr) {
5 | return attr + '="' + attrs[attr] + '"';
6 | }).join(' ');
7 | return "<" + tagName + " " + attrs + ">";
8 | };
9 |
10 | module.exports.c = function close (tagName) {
11 | return "" + tagName + ">";
12 | };
13 |
14 | module.exports.w = function wrap (tagName, str, attrs) {
15 | return open(tagName, attrs) + str + close(tagName);
16 | };
17 |
--------------------------------------------------------------------------------
/lib/text-cursor.js:
--------------------------------------------------------------------------------
1 | module.exports = TextCursor;
2 |
3 | function TextCursor (code) {
4 | this.lines = [null].concat(code.split('\n'));
5 |
6 | this.cursor = {
7 | line: 1,
8 | column: 0
9 | };
10 | }
11 |
12 | TextCursor.prototype.stepTo = function (line, column) {
13 | if (line === 'end') {
14 | line = this.lines.length - 1;
15 | }
16 | if (!column && typeof line === 'object') {
17 | column = line.column;
18 | line = line.line;
19 | }
20 | var output = '';
21 | var currentSlice;
22 |
23 | //If we're going to a different line, grab the remainder of all
24 | //preceeding lines
25 | while (this.cursor.line < line) {
26 | currentSlice = this.lines[this.cursor.line].slice(this.cursor.column);
27 | //if (currentSlice.length) output += currentSlice + '\n';
28 | output += currentSlice + '\n';
29 | this.cursor.line++;
30 | this.cursor.column = 0;
31 | }
32 |
33 | //Grab from where we are on the line we're jumping to, to the desired
34 | //column
35 | currentSlice = this.lines[this.cursor.line].slice(this.cursor.column, column);
36 | if (currentSlice.length) output += currentSlice;
37 |
38 | // slice = lines[cursor.line].slice(cursor.column, pos.column);
39 | // if (slice.length) output += slice;
40 | if (this.lines[this.cursor.line].length <= column) output += '\n';
41 | this.cursor.column = column;
42 | return output;
43 | };
44 |
45 | //var cursor = {
46 | // line: 1,
47 | // column: 0
48 | //};
49 | //
50 | //var piecesToPosition = function (pos) {
51 | // var output = '';
52 | // var slice;
53 | // while(cursor.line < pos.line) {
54 | // slice = lines[cursor.line].slice(cursor.column);
55 | // if (slice.length) output += slice + '\n';
56 | // cursor.line++;
57 | // cursor.column = 0;
58 | // }
59 | // slice = lines[cursor.line].slice(cursor.column, pos.column);
60 | // if (slice.length) output += slice;
61 | // if (lines[cursor.line].length === pos.column) output += '\n';
62 | // cursor.column = pos.column;
63 | // return output;
64 | //};
65 |
--------------------------------------------------------------------------------
/lib/wrap-insertion-points.js:
--------------------------------------------------------------------------------
1 | var TextCursor = require('./text-cursor');
2 |
3 | module.exports = function (code, insertionPoints, options) {
4 | var before = options.before || function () { return ''; };
5 | var after = options.after || function () { return ''; };
6 | var withWrappedCode = options.withWrappedCode || function () { return ''; };
7 |
8 | var cursor = new TextCursor(code);
9 | var output = '';
10 |
11 | var currentNodeContents = {
12 | };
13 |
14 | insertionPoints.forEach(function (point) {
15 | var wrappedCode = cursor.stepTo(point.loc);
16 | output += wrappedCode;
17 |
18 | Object.keys(currentNodeContents).forEach(function (id) {
19 | currentNodeContents[id] += wrappedCode;
20 | });
21 |
22 | if (point.type === 'start') {
23 | output += before(point.id);
24 | currentNodeContents[point.id] = '';
25 | }
26 |
27 | if (point.type === 'end') {
28 | withWrappedCode(point.id, currentNodeContents[point.id]);
29 | delete currentNodeContents[point.id];
30 |
31 | output += after(point.id);
32 | }
33 | });
34 | output += cursor.stepTo('end');
35 | return output;
36 | };
37 |
38 |
--------------------------------------------------------------------------------
/loupe.bundle.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | box-sizing: border-box;
3 | }
4 |
5 | /*#flexcanvas{
6 | width: 100%;
7 | height: 600px !important;
8 | }*/
9 | html, body, .flexContainer {
10 | min-height: 100vh;
11 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
12 | }
13 | body {
14 | padding: 0;
15 | margin: 0;
16 | }
17 | h1,h2,h3,h4 {
18 | font-family: Din Alternate, Helvetica Neue, Helvetica, Arial, sans-serif;
19 | color: #666;
20 | }
21 |
22 | .flexContainer {
23 | width: 100%;
24 | height: 100vh;
25 | }
26 |
27 | .flexContainer > * {
28 | height: 100%;
29 | padding-top: 32px;
30 | }
31 |
32 | .rowParent, .columnParent{
33 | display: -webkit-box;
34 | display: -ms-flexbox;
35 | display: -webkit-flex;
36 | display: flex;
37 | -webkit-box-direction: normal;
38 | -webkit-box-orient: horizontal;
39 | -webkit-flex-direction: row;
40 | -ms-flex-direction: row;
41 | flex-direction: row;
42 | -webkit-flex-wrap: nowrap;
43 | -ms-flex-wrap: nowrap;
44 | flex-wrap: nowrap;
45 | -webkit-box-pack: start;
46 | -webkit-justify-content: flex-start;
47 | -ms-flex-pack: start;
48 | justify-content: flex-start;
49 | -webkit-align-content: stretch;
50 | -ms-flex-line-pack: stretch;
51 | align-content: stretch;
52 | -webkit-box-align: stretch;
53 | -webkit-align-items: stretch;
54 | -ms-flex-align: stretch;
55 | align-items: stretch;
56 | }
57 |
58 | .columnParent{
59 | -webkit-box-orient: vertical;
60 | -webkit-flex-direction: column;
61 | -ms-flex-direction: column;
62 | flex-direction: column;
63 | }
64 |
65 | .flexChild{
66 | -webkit-box-flex: 1;
67 | -webkit-flex: 1;
68 | -ms-flex: 1;
69 | flex: 1;
70 | -webkit-align-self: auto;
71 | -ms-flex-item-align: auto;
72 | align-self: auto;
73 | }
74 |
75 | .editorBox, .stackRow {
76 | -webkit-box-flex: 3;
77 | -webkit-flex: 3;
78 | -ms-flex: 3;
79 | flex: 3;
80 | position: relative;
81 | }
82 |
83 | .editorBox {
84 | background: #FDF6E3;
85 | }
86 |
87 | .codeColumn {
88 | -webkit-box-flex: 0.5;
89 | -webkit-flex: 0.5;
90 | -ms-flex: 0.5;
91 | flex: 0.5;
92 | margin-right: 20px;
93 | border-right: 2px #ddd solid;
94 | }
95 |
96 | .editor {
97 | min-height: 100%;
98 | width: 100%;
99 | font-family: monospace;
100 | font-size: 13px;
101 | line-height: 1.75;
102 | display: inline-block;
103 | white-space: pre;
104 | padding-left: 45px;
105 | color: #333;
106 | font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
107 | font-size: 12px;
108 | overflow-y: hidden;
109 | position: relative;
110 | }
111 |
112 | .editor:before {
113 | content: attr(data-lines);
114 | /*content: "1\a 2\A 3\A 4\A 5\A 6\A 7\A 8\A 9\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A";*/
115 | position: absolute;
116 | top: 0;
117 | left: 0;
118 | width: 41px;
119 | padding-left: 7.5px;
120 | text-align: center;
121 | background: #FBF1D3;
122 | height: 100%;
123 | white-space: pre;
124 | }
125 |
126 | .ace-editor-wrapper {
127 | -webkit-box-flex: 1;
128 | -webkit-flex: 1;
129 | -ms-flex: 1;
130 | flex: 1;
131 | }
132 |
133 | .ace_editor {
134 | line-height: 1.75!important;
135 | }
136 |
137 | .html-scratchpad {
138 | padding: 10px;
139 | background: #eee;
140 | overflow: scroll;
141 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
142 | }
143 |
144 | .webapi {
145 | padding: 5px;
146 | border: 2px #ddd solid;
147 | margin: 5px;
148 | }
149 |
150 | .webapi-code {
151 | font-family: monospace;
152 | height: 100%;
153 | vertical-align: middle;
154 | margin-right: 5px;
155 | }
156 |
157 |
158 | .webapi-code:before {
159 | content: '';
160 | display: inline-block;
161 | vertical-align: middle;
162 | height: 100%;
163 | }
164 |
165 | .webapi-timer > * {
166 | display: inline-block;
167 | vertical-align: middle;
168 | height: 44px;
169 | }
170 |
171 |
172 | @-webkit-keyframes rota {
173 | 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
174 | 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
175 | }
176 |
177 |
178 | @keyframes rota {
179 | 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
180 | 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
181 | }
182 |
183 | @-webkit-keyframes fill {
184 | 0% { opacity: 0; }
185 | 50%, 100% { opacity: 1; }
186 | }
187 |
188 | @keyframes fill {
189 | 0% { opacity: 0; }
190 | 50%, 100% { opacity: 1; }
191 | }
192 |
193 | @-webkit-keyframes mask {
194 | 0% { opacity: 1; }
195 | 50%, 100% { opacity: 0; }
196 | }
197 |
198 | @keyframes mask {
199 | 0% { opacity: 1; }
200 | 50%, 100% { opacity: 0; }
201 | }
202 |
203 | @-webkit-keyframes check {
204 | 0% { opacity: 0; }
205 | 98% { opacity: 0; }
206 | 100% { opacity: 1; }
207 | }
208 |
209 | @keyframes check {
210 | 0% { opacity: 0; }
211 | 98% { opacity: 0; }
212 | 100% { opacity: 1; }
213 | }
214 |
215 | @-webkit-keyframes background {
216 | 0% { background: rgba(148,255,148,0); }
217 | 95% { background: rgba(148,255,148,0); }
218 | 100% { background: rgba(148,255,148,1); }
219 | }
220 |
221 | @keyframes background {
222 | 0% { background: rgba(148,255,148,0); }
223 | 95% { background: rgba(148,255,148,0); }
224 | 100% { background: rgba(148,255,148,1); }
225 | }
226 |
227 | .stopwatch-wrapper {
228 | width: 44px;
229 | height: 44px;
230 | position: relative;
231 | background: rgba(148,255,148,1);
232 | border-radius: 250px;
233 | -webkit-animation: background 10s linear;
234 | animation: background 10s linear;
235 | overflow: hidden;
236 | }
237 |
238 | .stopwatch-wrapper:before {
239 | position: absolute;
240 | content: "";
241 | width: 50%;
242 | height: 28%;
243 | top: 35%;
244 | left: 25.5%;
245 | border-left: 7px white solid;
246 | border-bottom: 7px white solid;
247 | -webkit-transform: rotate(-45deg);
248 | -ms-transform: rotate(-45deg);
249 | transform: rotate(-45deg);
250 | z-index: 500;
251 | }
252 |
253 | .stopwatch-pie {
254 | border-radius: 22px;
255 | width: 50%;
256 | height: 100%;
257 | position: absolute;
258 | border: 8px solid rgb(148,255,148);
259 | }
260 |
261 | .stopwatch-spinner {
262 | border-top-right-radius: 0;
263 | border-bottom-right-radius: 0;
264 | z-index: 200;
265 | border-right: none;
266 | -webkit-animation: rota 10s linear;
267 | animation: rota 10s linear;
268 | -webkit-transform-origin: 100% 50%;
269 | -ms-transform-origin: 100% 50%;
270 | transform-origin: 100% 50%;
271 | }
272 |
273 | .stopwatch-filler {
274 | border-top-left-radius: 0;
275 | border-bottom-left-radius: 0;
276 | z-index: 100;
277 | border-left: none;
278 | -webkit-animation: fill 10s steps(1, end);
279 | animation: fill 10s steps(1, end);
280 | left: 50%;
281 | opacity: 1;
282 | }
283 |
284 | .stopwatch-mask {
285 | width: 50%;
286 | height: 100%;
287 | position: absolute;
288 | z-index: 300;
289 | opacity: 0;
290 | background: white;
291 | -webkit-animation: mask 10s steps(1, end);
292 | animation: mask 10s steps(1, end);
293 | border-radius: 250px 0 0 250px;
294 | }
295 |
296 | .stopwatch-spinner, .stopwatch-filler, .stopwatch-mask, .stopwatch-wrapper {
297 | -webkit-animation-duration: 3s;
298 | animation-duration: 3s;
299 | }
300 |
301 |
302 | .callback-queue {
303 | margin-bottom: 20px;
304 | margin-right: 20px;
305 | overflow-y: scroll;
306 | }
307 | .callback-queue.render-queue {
308 | margin-bottom: 5px;
309 | }
310 |
311 | .callback {
312 | font-family: monospace;
313 | padding: 10px;
314 | display: inline-block;
315 | margin-right: 10px;
316 | border: 2px #ddd solid;
317 | width: 200px;
318 | overflow: scroll;
319 | }
320 |
321 | .callback-active {
322 | background: rgb(220,224,255);
323 | }
324 |
325 | .stackBox {
326 | position: relative;
327 | margin-right: 20px;
328 | }
329 |
330 | .stack-wrapper {
331 | width: 244px;
332 | padding: 20px;
333 | margin-bottom: 86px;
334 | margin-top: 20px;
335 | }
336 |
337 | .stack-wrapper, .callback-queue, .webapis {
338 | border: 2px #ddd dashed;
339 | position: relative;
340 | padding: 25px 10px 10px 10px;
341 | }
342 |
343 | .stack-wrapper:before,
344 | .callback-queue:before,
345 | .webapis:before {
346 | font-family: Helvetica Neue, Arial, sans-serif;
347 | position: absolute;
348 | top: 0;
349 | left: 0;
350 | padding: 5px;
351 | color: #999;
352 | }
353 |
354 | .webapis {
355 | margin-top: 20px;
356 | margin-bottom: 86px;
357 | margin-right: 20px;
358 | }
359 |
360 | .webapis:before {
361 | content: "Web Apis";
362 | }
363 |
364 | .stack-wrapper:before {
365 | content: "Call Stack";
366 | }
367 |
368 | .callback-queue:before {
369 | content: "Callback Queue";
370 | }
371 |
372 | .callback-queue.render-queue:before {
373 | content: "Render Queue";
374 | }
375 |
376 | .stack {
377 | position: absolute;
378 | bottom: 0px;
379 | left: 0;
380 | padding-left: 10px;
381 | }
382 | .stackBox > .spinner-wrapper {
383 | position: absolute;
384 | bottom: 0;
385 | left: 0;
386 | margin-bottom: 5px;
387 | }
388 |
389 | .stack-item {
390 | font-family: monospace;
391 | padding: 10px;
392 | margin: 5px;
393 | border: 2px #ddd solid;
394 | width: 220px;
395 | margin: 10px auto;
396 | }
397 |
398 |
399 | .spinner-wrapper {
400 | height: 76px;
401 | width: 76px;
402 | position: relative;
403 | padding: 9px;
404 | -webkit-transform: rotate(0deg);
405 | -ms-transform: rotate(0deg);
406 | transform: rotate(0deg);
407 | margin-left: 82px;
408 | }
409 |
410 | .spinner-wrapper-transition {
411 | -webkit-transform: rotate(180deg);
412 | -ms-transform: rotate(180deg);
413 | transform: rotate(180deg);
414 | -webkit-transition: -webkit-transform 0.5s linear;
415 | transition: transform 0.5s linear;
416 | }
417 |
418 | .spinner-circle {
419 | height: 100%;
420 | width: 100%;
421 | border-radius: 30px;
422 | border: 6px coral solid;
423 | }
424 |
425 | .spinner-arrow {
426 | width: 0px;
427 | height: 0px;
428 | border-style: solid;
429 | border-width: 0 16px 16px 16px;
430 | border-color: transparent transparent white transparent;
431 | position: absolute;
432 | }
433 |
434 | .spinner-arrow:after {
435 | content: "";
436 | width: 0px;
437 | height: 0px;
438 | border-style: solid;
439 | border-width: 0 12px 12px 12px;
440 | border-color: transparent transparent coral transparent;
441 | position: absolute;
442 | }
443 |
444 | .spinner-arrow-left:after {
445 | left: -12px;
446 | top: 4px;
447 | }
448 |
449 | .spinner-arrow-left {
450 | left: -2.5px;
451 | top: calc(50% - 9px);
452 | }
453 |
454 | .spinner-arrow-right {
455 | right: -2px;
456 | top: calc(50% - 6px);
457 | border-width: 16px 16px 0px 16px;
458 | border-color: white transparent transparent transparent;
459 | }
460 |
461 | .spinner-arrow-right:after {
462 | border-width: 12px 12px 0px 12px;
463 | border-color: coral transparent transparent transparent;
464 |
465 | left: -12px;
466 | bottom: 4px;
467 | }
468 |
469 | .code-node {
470 | }
471 |
472 | .code-node:empty {
473 | display: none;
474 | }
475 |
476 | .code-node.running {
477 | background: rgba(236, 117, 74, 0.25);
478 | }
479 |
480 |
481 | /*********************************/
482 | .tr-webapis-enter {
483 | background: rgb(255,255,194);
484 | -webkit-transition: background 0.2s linear;
485 | transition: background 0.2s linear;
486 | }
487 |
488 | .tr-webapis-enter.tr-webapis-enter-active {
489 | background: white;
490 | }
491 |
492 | .tr-webapis-leave {
493 | background: white;
494 | -webkit-transition: background 0.01s linear;
495 | transition: background 0.01s linear;
496 | }
497 |
498 | .tr-webapis-leave.tr-webapis-leave-active {
499 | background: white;
500 | }
501 |
502 | .tr-webapi-spawn {
503 | background: white;
504 | -webkit-transition: background 0.1s linear;
505 | transition: background 0.1s linear;
506 | }
507 |
508 | .tr-webapi-spawn.tr-webapi-spawn-active {
509 | background: rgb(255, 175, 146);
510 | }
511 |
512 | /*********************************/
513 | .tr-queue-enter {
514 | background: rgb(255,255,194);
515 | -webkit-transition: background 0.2s linear;
516 | transition: background 0.2s linear;
517 | }
518 |
519 | .tr-queue-enter.tr-queue-enter-active {
520 | background: white;
521 | }
522 |
523 | .tr-queue-leave {
524 | -webkit-transform: translate(0, 0);
525 | -ms-transform: translate(0, 0);
526 | transform: translate(0, 0);
527 | opacity: 1;
528 | -webkit-transition: all 0.25s linear;
529 | transition: all 0.25s linear;
530 | }
531 |
532 | .tr-queue-leave.tr-queue-leave-active {
533 | -webkit-transform: translate(0, -100px);
534 | -ms-transform: translate(0, -100px);
535 | transform: translate(0, -100px);
536 | opacity: 0.01;
537 | }
538 |
539 |
540 | /*********************************/
541 | .tr-stack-enter {
542 | -webkit-transform: translate(0, 0);
543 | -ms-transform: translate(0, 0);
544 | transform: translate(0, 0);
545 | opacity: 0.01;
546 | -webkit-transition: all 0.1s linear;
547 | transition: all 0.1s linear;
548 | }
549 |
550 | .tr-stack-enter.stack-item-callback {
551 | -webkit-transform: translate(0, 100px);
552 | -ms-transform: translate(0, 100px);
553 | transform: translate(0, 100px);
554 | -webkit-transition: all 0.25s linear;
555 | transition: all 0.25s linear;
556 | }
557 |
558 | .tr-stack-enter.tr-stack-enter-active {
559 | -webkit-transform: translate(0, 0);
560 | -ms-transform: translate(0, 0);
561 | transform: translate(0, 0);
562 | opacity: 1;
563 | }
564 |
565 | .tr-stack-leave {
566 | opacity: 1;
567 | -webkit-transition: all 0.1s linear;
568 | transition: all 0.1s linear;
569 | }
570 |
571 | .tr-stack-leave.tr-stack-leave-active {
572 | opacity: 0.01;
573 | -webkit-transition: all 0.1s linear;
574 | transition: all 0.1s linear;
575 | }
576 |
577 | .htmlEditorBox {
578 | position: relative;
579 | }
580 |
581 | .editor-switch {
582 | position: absolute;
583 | top: 5px;
584 | right: 5px;
585 | z-index: 9999;
586 | }
587 |
588 | .render-queue .callback-queued {
589 | background: white;
590 | }
591 |
592 | .render-queue .callback-delayed {
593 | background: rgba(255,0,0,0.5);
594 | -webkit-transition: background 0.2s linear;
595 | transition: background 0.2s linear;
596 | }
597 |
598 | .render-queue .callback-rendered {
599 | background: rgba(0,255,0,0.5);
600 | -webkit-transition: background 0.4s linear;
601 | transition: background 0.4s linear;
602 | }
603 |
604 |
605 | /*
606 | .tr-render-queue-enter {
607 | background: white;
608 | transition: background 0.5s linear;
609 | }
610 |
611 | .tr-render-queue-enter.tr-render-queue-enter-active {
612 | background: red;
613 | }
614 |
615 | .tr-render-queue-leave {
616 | background: green;
617 | opacity: 1;
618 | transition: all 0.2s linear;
619 | }
620 |
621 | .tr-render-queue-leave.tr-render-queue-leave-active {
622 | background: white;
623 | opacity: 0.01;
624 | transition: all 0.2s linear;
625 | }*/
626 | /************************/
627 |
628 | .top-nav {
629 | background: #D80;
630 | padding: 5px;
631 | height: 32px;
632 | position: absolute;
633 | width: 100%;
634 | color: #eee;
635 | padding-left: 10px;
636 | }
637 |
638 | .top-nav h1 {
639 | font-size: 20px;
640 | display: inline;
641 | position: relative;
642 | top: -2px;
643 | color: #eee;
644 | }
645 |
646 | .top-nav .settings-button {
647 | border: none;
648 | background: none;
649 | color: #eee;
650 | font-size: 30px;
651 | line-height: 30px;
652 | margin: 0;
653 | padding: 0;
654 | margin-right: 10px;
655 | margin-top: -7px;
656 | }
657 |
658 | .settingsColumn {
659 | box-sizing: border-box;
660 | -webkit-box-flex: 0;
661 | -webkit-flex: none;
662 | -ms-flex: none;
663 | flex: none;
664 | width: 200px;
665 | -webkit-transition: width 0.15s linear;
666 | transition: width 0.15s linear;
667 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
668 | background: #ddd;
669 | color: #666;
670 | }
671 |
672 | .settingsColumn .setting {
673 | margin: 20px;
674 | }
675 |
676 | .settingsColumn.hidden {
677 | width: 0;
678 | overflow: hidden;
679 | }
680 |
681 | .ReactModal__Overlay {
682 | z-index: 20000;
683 | background: rgba(0,0,0,0.4);
684 | }
685 |
686 | .ReactModal__Content {
687 | left: 20%;
688 | right: 20%;
689 | border: none;
690 | font-size: 14px;
691 | }
692 |
693 | .ReactModal__Content * {
694 | color: #888;
695 | }
696 |
697 | .ReactModal__Content h1 {
698 | margin-top: 0;
699 | }
700 |
701 | .ReactModal__Content h1 + h2 {
702 | margin-top: 20px;
703 | }
704 |
705 | .ReactModal__Content h2 {
706 | margin-top: 40px;
707 | margin-bottom: 10px;
708 | }
709 |
710 | .ReactModal__Content a {
711 | color: #DF8900;
712 | }
713 |
714 | .ReactModal__Content li {
715 | margin-bottom: 5px;
716 | }
717 |
718 | .ReactModal__Content iframe {
719 | margin: 40px auto;
720 | max-width: 100%;
721 | display: block;
722 | border: none;
723 | }
724 |
725 |
726 |
727 | .modal-button {
728 | float: right;
729 | }
730 |
731 | .modal-button:hover {
732 | cursor: pointer;
733 | text-decoration: underline;
734 | }
735 |
736 | p.info {
737 | text-align: center;
738 | margin-top: 40px;
739 | }
740 |
741 | a.modalClose {
742 | position: absolute;
743 | top: 15px;
744 | right: 15px;
745 | }
746 | a:hover {
747 | cursor: pointer;
748 | }
749 |
--------------------------------------------------------------------------------
/loupe.css:
--------------------------------------------------------------------------------
1 | *, *:before, *:after {
2 | box-sizing: border-box;
3 | }
4 |
5 | /*#flexcanvas{
6 | width: 100%;
7 | height: 600px !important;
8 | }*/
9 | html, body, .flexContainer {
10 | min-height: 100vh;
11 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
12 | }
13 | body {
14 | padding: 0;
15 | margin: 0;
16 | }
17 | h1,h2,h3,h4 {
18 | font-family: Din Alternate, Helvetica Neue, Helvetica, Arial, sans-serif;
19 | color: #666;
20 | }
21 |
22 | .flexContainer {
23 | width: 100%;
24 | height: 100vh;
25 | }
26 |
27 | .flexContainer > * {
28 | height: 100%;
29 | padding-top: 32px;
30 | }
31 |
32 | .rowParent, .columnParent{
33 | display: -webkit-box;
34 | display: -ms-flexbox;
35 | display: -webkit-flex;
36 | display: flex;
37 | -webkit-box-direction: normal;
38 | -webkit-box-orient: horizontal;
39 | -webkit-flex-direction: row;
40 | -ms-flex-direction: row;
41 | flex-direction: row;
42 | -webkit-flex-wrap: nowrap;
43 | -ms-flex-wrap: nowrap;
44 | flex-wrap: nowrap;
45 | -webkit-box-pack: start;
46 | -webkit-justify-content: flex-start;
47 | -ms-flex-pack: start;
48 | justify-content: flex-start;
49 | -webkit-align-content: stretch;
50 | -ms-flex-line-pack: stretch;
51 | align-content: stretch;
52 | -webkit-box-align: stretch;
53 | -webkit-align-items: stretch;
54 | -ms-flex-align: stretch;
55 | align-items: stretch;
56 | }
57 |
58 | .columnParent{
59 | -webkit-box-orient: vertical;
60 | -webkit-flex-direction: column;
61 | -ms-flex-direction: column;
62 | flex-direction: column;
63 | }
64 |
65 | .flexChild{
66 | -webkit-box-flex: 1;
67 | -webkit-flex: 1;
68 | -ms-flex: 1;
69 | flex: 1;
70 | -webkit-align-self: auto;
71 | -ms-flex-item-align: auto;
72 | align-self: auto;
73 | }
74 |
75 | .editorBox, .stackRow {
76 | flex: 3;
77 | position: relative;
78 | }
79 |
80 | .editorBox {
81 | background: #FDF6E3;
82 | }
83 |
84 | .codeColumn {
85 | flex: 0.5;
86 | margin-right: 20px;
87 | border-right: 2px #ddd solid;
88 | }
89 |
90 | .editor {
91 | min-height: 100%;
92 | width: 100%;
93 | font-family: monospace;
94 | font-size: 13px;
95 | line-height: 1.75;
96 | display: inline-block;
97 | white-space: pre;
98 | padding-left: 45px;
99 | color: #333;
100 | font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
101 | font-size: 12px;
102 | overflow-y: hidden;
103 | position: relative;
104 | }
105 |
106 | .editor:before {
107 | content: attr(data-lines);
108 | /*content: "1\a 2\A 3\A 4\A 5\A 6\A 7\A 8\A 9\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A";*/
109 | position: absolute;
110 | top: 0;
111 | left: 0;
112 | width: 41px;
113 | padding-left: 7.5px;
114 | text-align: center;
115 | background: #FBF1D3;
116 | height: 100%;
117 | white-space: pre;
118 | }
119 |
120 | .ace-editor-wrapper {
121 | flex: 1;
122 | }
123 |
124 | .ace_editor {
125 | line-height: 1.75!important;
126 | }
127 |
128 | .html-scratchpad {
129 | padding: 10px;
130 | background: #eee;
131 | overflow: scroll;
132 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
133 | }
134 |
135 | .webapi {
136 | padding: 5px;
137 | border: 2px #ddd solid;
138 | margin: 5px;
139 | }
140 |
141 | .webapi-code {
142 | font-family: monospace;
143 | height: 100%;
144 | vertical-align: middle;
145 | margin-right: 5px;
146 | }
147 |
148 |
149 | .webapi-code:before {
150 | content: '';
151 | display: inline-block;
152 | vertical-align: middle;
153 | height: 100%;
154 | }
155 |
156 | .webapi-timer > * {
157 | display: inline-block;
158 | vertical-align: middle;
159 | height: 44px;
160 | }
161 |
162 |
163 | @keyframes rota {
164 | 0% { transform: rotate(0deg); }
165 | 100% { transform: rotate(360deg); }
166 | }
167 |
168 | @keyframes fill {
169 | 0% { opacity: 0; }
170 | 50%, 100% { opacity: 1; }
171 | }
172 |
173 | @keyframes mask {
174 | 0% { opacity: 1; }
175 | 50%, 100% { opacity: 0; }
176 | }
177 |
178 | @keyframes check {
179 | 0% { opacity: 0; }
180 | 98% { opacity: 0; }
181 | 100% { opacity: 1; }
182 | }
183 |
184 | @keyframes background {
185 | 0% { background: rgba(148,255,148,0); }
186 | 95% { background: rgba(148,255,148,0); }
187 | 100% { background: rgba(148,255,148,1); }
188 | }
189 |
190 | .stopwatch-wrapper {
191 | width: 44px;
192 | height: 44px;
193 | position: relative;
194 | background: rgba(148,255,148,1);
195 | border-radius: 250px;
196 | animation: background 10s linear;
197 | overflow: hidden;
198 | }
199 |
200 | .stopwatch-wrapper:before {
201 | position: absolute;
202 | content: "";
203 | width: 50%;
204 | height: 28%;
205 | top: 35%;
206 | left: 25.5%;
207 | border-left: 7px white solid;
208 | border-bottom: 7px white solid;
209 | transform: rotate(-45deg);
210 | z-index: 500;
211 | }
212 |
213 | .stopwatch-pie {
214 | border-radius: 22px;
215 | width: 50%;
216 | height: 100%;
217 | position: absolute;
218 | border: 8px solid rgb(148,255,148);
219 | }
220 |
221 | .stopwatch-spinner {
222 | border-top-right-radius: 0;
223 | border-bottom-right-radius: 0;
224 | z-index: 200;
225 | border-right: none;
226 | animation: rota 10s linear;
227 | transform-origin: 100% 50%;
228 | }
229 |
230 | .stopwatch-filler {
231 | border-top-left-radius: 0;
232 | border-bottom-left-radius: 0;
233 | z-index: 100;
234 | border-left: none;
235 | animation: fill 10s steps(1, end);
236 | left: 50%;
237 | opacity: 1;
238 | }
239 |
240 | .stopwatch-mask {
241 | width: 50%;
242 | height: 100%;
243 | position: absolute;
244 | z-index: 300;
245 | opacity: 0;
246 | background: white;
247 | animation: mask 10s steps(1, end);
248 | border-radius: 250px 0 0 250px;
249 | }
250 |
251 | .stopwatch-spinner, .stopwatch-filler, .stopwatch-mask, .stopwatch-wrapper {
252 | animation-duration: 3s;
253 | }
254 |
255 |
256 | .callback-queue {
257 | margin-bottom: 20px;
258 | margin-right: 20px;
259 | overflow-y: scroll;
260 | }
261 | .callback-queue.render-queue {
262 | margin-bottom: 5px;
263 | }
264 |
265 | .callback {
266 | font-family: monospace;
267 | padding: 10px;
268 | display: inline-block;
269 | margin-right: 10px;
270 | border: 2px #ddd solid;
271 | width: 200px;
272 | overflow: scroll;
273 | }
274 |
275 | .callback-active {
276 | background: rgb(220,224,255);
277 | }
278 |
279 | .stackBox {
280 | position: relative;
281 | margin-right: 20px;
282 | }
283 |
284 | .stack-wrapper {
285 | width: 244px;
286 | padding: 20px;
287 | margin-bottom: 86px;
288 | margin-top: 20px;
289 | }
290 |
291 | .stack-wrapper, .callback-queue, .webapis {
292 | border: 2px #ddd dashed;
293 | position: relative;
294 | padding: 25px 10px 10px 10px;
295 | }
296 |
297 | .stack-wrapper:before,
298 | .callback-queue:before,
299 | .webapis:before {
300 | font-family: Helvetica Neue, Arial, sans-serif;
301 | position: absolute;
302 | top: 0;
303 | left: 0;
304 | padding: 5px;
305 | color: #999;
306 | }
307 |
308 | .webapis {
309 | margin-top: 20px;
310 | margin-bottom: 86px;
311 | margin-right: 20px;
312 | }
313 |
314 | .webapis:before {
315 | content: "Web Apis";
316 | }
317 |
318 | .stack-wrapper:before {
319 | content: "Call Stack";
320 | }
321 |
322 | .callback-queue:before {
323 | content: "Callback Queue";
324 | }
325 |
326 | .callback-queue.render-queue:before {
327 | content: "Render Queue";
328 | }
329 |
330 | .stack {
331 | position: absolute;
332 | bottom: 0px;
333 | left: 0;
334 | padding-left: 10px;
335 | }
336 | .stackBox > .spinner-wrapper {
337 | position: absolute;
338 | bottom: 0;
339 | left: 0;
340 | margin-bottom: 5px;
341 | }
342 |
343 | .stack-item {
344 | font-family: monospace;
345 | padding: 10px;
346 | margin: 5px;
347 | border: 2px #ddd solid;
348 | width: 220px;
349 | margin: 10px auto;
350 | }
351 |
352 |
353 | .spinner-wrapper {
354 | height: 76px;
355 | width: 76px;
356 | position: relative;
357 | padding: 9px;
358 | transform: rotate(0deg);
359 | margin-left: 82px;
360 | }
361 |
362 | .spinner-wrapper-transition {
363 | transform: rotate(180deg);
364 | transition: transform 0.5s linear;
365 | }
366 |
367 | .spinner-circle {
368 | height: 100%;
369 | width: 100%;
370 | border-radius: 30px;
371 | border: 6px coral solid;
372 | }
373 |
374 | .spinner-arrow {
375 | width: 0px;
376 | height: 0px;
377 | border-style: solid;
378 | border-width: 0 16px 16px 16px;
379 | border-color: transparent transparent white transparent;
380 | position: absolute;
381 | }
382 |
383 | .spinner-arrow:after {
384 | content: "";
385 | width: 0px;
386 | height: 0px;
387 | border-style: solid;
388 | border-width: 0 12px 12px 12px;
389 | border-color: transparent transparent coral transparent;
390 | position: absolute;
391 | }
392 |
393 | .spinner-arrow-left:after {
394 | left: -12px;
395 | top: 4px;
396 | }
397 |
398 | .spinner-arrow-left {
399 | left: -2.5px;
400 | top: calc(50% - 9px);
401 | }
402 |
403 | .spinner-arrow-right {
404 | right: -2px;
405 | top: calc(50% - 6px);
406 | border-width: 16px 16px 0px 16px;
407 | border-color: white transparent transparent transparent;
408 | }
409 |
410 | .spinner-arrow-right:after {
411 | border-width: 12px 12px 0px 12px;
412 | border-color: coral transparent transparent transparent;
413 |
414 | left: -12px;
415 | bottom: 4px;
416 | }
417 |
418 | .code-node {
419 | }
420 |
421 | .code-node:empty {
422 | display: none;
423 | }
424 |
425 | .code-node.running {
426 | background: rgba(236, 117, 74, 0.25);
427 | }
428 |
429 |
430 | /*********************************/
431 | .tr-webapis-enter {
432 | background: rgb(255,255,194);
433 | transition: background 0.2s linear;
434 | }
435 |
436 | .tr-webapis-enter.tr-webapis-enter-active {
437 | background: white;
438 | }
439 |
440 | .tr-webapis-leave {
441 | background: white;
442 | transition: background 0.01s linear;
443 | }
444 |
445 | .tr-webapis-leave.tr-webapis-leave-active {
446 | background: white;
447 | }
448 |
449 | .tr-webapi-spawn {
450 | background: white;
451 | transition: background 0.1s linear;
452 | }
453 |
454 | .tr-webapi-spawn.tr-webapi-spawn-active {
455 | background: rgb(255, 175, 146);
456 | }
457 |
458 | /*********************************/
459 | .tr-queue-enter {
460 | background: rgb(255,255,194);
461 | transition: background 0.2s linear;
462 | }
463 |
464 | .tr-queue-enter.tr-queue-enter-active {
465 | background: white;
466 | }
467 |
468 | .tr-queue-leave {
469 | transform: translate(0, 0);
470 | opacity: 1;
471 | transition: all 0.25s linear;
472 | }
473 |
474 | .tr-queue-leave.tr-queue-leave-active {
475 | transform: translate(0, -100px);
476 | opacity: 0.01;
477 | }
478 |
479 |
480 | /*********************************/
481 | .tr-stack-enter {
482 | transform: translate(0, 0);
483 | opacity: 0.01;
484 | transition: all 0.1s linear;
485 | }
486 |
487 | .tr-stack-enter.stack-item-callback {
488 | transform: translate(0, 100px);
489 | transition: all 0.25s linear;
490 | }
491 |
492 | .tr-stack-enter.tr-stack-enter-active {
493 | transform: translate(0, 0);
494 | opacity: 1;
495 | }
496 |
497 | .tr-stack-leave {
498 | opacity: 1;
499 | transition: all 0.1s linear;
500 | }
501 |
502 | .tr-stack-leave.tr-stack-leave-active {
503 | opacity: 0.01;
504 | transition: all 0.1s linear;
505 | }
506 |
507 | .htmlEditorBox {
508 | position: relative;
509 | }
510 |
511 | .editor-switch {
512 | position: absolute;
513 | top: 5px;
514 | right: 5px;
515 | z-index: 9999;
516 | }
517 |
518 | .render-queue .callback-queued {
519 | background: white;
520 | }
521 |
522 | .render-queue .callback-delayed {
523 | background: rgba(255,0,0,0.5);
524 | transition: background 0.2s linear;
525 | }
526 |
527 | .render-queue .callback-rendered {
528 | background: rgba(0,255,0,0.5);
529 | transition: background 0.4s linear;
530 | }
531 |
532 |
533 | /*
534 | .tr-render-queue-enter {
535 | background: white;
536 | transition: background 0.5s linear;
537 | }
538 |
539 | .tr-render-queue-enter.tr-render-queue-enter-active {
540 | background: red;
541 | }
542 |
543 | .tr-render-queue-leave {
544 | background: green;
545 | opacity: 1;
546 | transition: all 0.2s linear;
547 | }
548 |
549 | .tr-render-queue-leave.tr-render-queue-leave-active {
550 | background: white;
551 | opacity: 0.01;
552 | transition: all 0.2s linear;
553 | }*/
554 | /************************/
555 |
556 | .top-nav {
557 | background: #D80;
558 | padding: 5px;
559 | height: 32px;
560 | position: absolute;
561 | width: 100%;
562 | color: #eee;
563 | padding-left: 10px;
564 | }
565 |
566 | .top-nav h1 {
567 | font-size: 20px;
568 | display: inline;
569 | position: relative;
570 | top: -2px;
571 | color: #eee;
572 | }
573 |
574 | .top-nav .settings-button {
575 | border: none;
576 | background: none;
577 | color: #eee;
578 | font-size: 30px;
579 | line-height: 30px;
580 | margin: 0;
581 | padding: 0;
582 | margin-right: 10px;
583 | margin-top: -7px;
584 | }
585 |
586 | .settingsColumn {
587 | box-sizing: border-box;
588 | flex: none;
589 | width: 200px;
590 | transition: width 0.15s linear;
591 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
592 | background: #ddd;
593 | color: #666;
594 | }
595 |
596 | .settingsColumn .setting {
597 | margin: 20px;
598 | }
599 |
600 | .settingsColumn.hidden {
601 | width: 0;
602 | overflow: hidden;
603 | }
604 |
605 | .ReactModal__Overlay {
606 | z-index: 20000;
607 | background: rgba(0,0,0,0.4);
608 | }
609 |
610 | .ReactModal__Content {
611 | left: 20%;
612 | right: 20%;
613 | border: none;
614 | font-size: 14px;
615 | }
616 |
617 | .ReactModal__Content * {
618 | color: #888;
619 | }
620 |
621 | .ReactModal__Content h1 {
622 | margin-top: 0;
623 | }
624 |
625 | .ReactModal__Content h1 + h2 {
626 | margin-top: 20px;
627 | }
628 |
629 | .ReactModal__Content h2 {
630 | margin-top: 40px;
631 | margin-bottom: 10px;
632 | }
633 |
634 | .ReactModal__Content a {
635 | color: #DF8900;
636 | }
637 |
638 | .ReactModal__Content li {
639 | margin-bottom: 5px;
640 | }
641 |
642 | .ReactModal__Content iframe {
643 | margin: 40px auto;
644 | max-width: 100%;
645 | display: block;
646 | border: none;
647 | }
648 |
649 |
650 |
651 | .modal-button {
652 | float: right;
653 | }
654 |
655 | .modal-button:hover {
656 | cursor: pointer;
657 | text-decoration: underline;
658 | }
659 |
660 | p.info {
661 | text-align: center;
662 | margin-top: 40px;
663 | }
664 |
665 | a.modalClose {
666 | position: absolute;
667 | top: 15px;
668 | right: 15px;
669 | }
670 | a:hover {
671 | cursor: pointer;
672 | }
673 |
--------------------------------------------------------------------------------
/loupe.js:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | window.React = React;
3 | var App = require('./components/app.jsx');
4 | var AmpersandCollection = require('ampersand-collection');
5 | var AmpersandState = require('ampersand-state');
6 | var deval = require('deval');
7 |
8 | var CallStack = require('./models/callstack');
9 |
10 | var Code = require('./models/code');
11 | var Apis = require('./models/apis');
12 | var CallbackQueue = require('./models/callback-queue');
13 | var RenderQueue = require('./models/render-queue');
14 |
15 | var Router = require('./router');
16 | var Modal = require('react-modal');
17 |
18 | window.app = {};
19 |
20 | window.app.router = new Router();
21 |
22 | window.app.store = {
23 | callstack: new CallStack(),
24 | code: new Code(),
25 | apis: new Apis(),
26 | queue: new CallbackQueue(),
27 | renderQueue: new RenderQueue()
28 | };
29 |
30 | app.store.code.on('change:codeLines', function () {
31 | });
32 |
33 | app.store.code.on('change:encodedSource', function () {
34 | app.router.navigate('?code=' + app.store.code.encodedSource);
35 | });
36 |
37 | //app.store.code.on('all', function () {
38 | // console.log('Code event', arguments);
39 | //});
40 |
41 | app.store.code.on('node:will-run', function (id, source, invocation) {
42 | app.store.callstack.add({
43 | _id: id,
44 | code: source
45 | });
46 | });
47 |
48 | app.store.code.on('node:did-run', function (id, invocation) {
49 | app.store.callstack.pop();
50 | //app.store.callstack.remove(app.store.callstack.at(app.store.call
51 | //app.store.callstack.remove(id + ':' + invocation);
52 | });
53 |
54 | app.store.code.on('webapi:started', function (data) {
55 | app.store.apis.add(data, { merge: true });
56 | });
57 |
58 | app.store.code.on('callback:shifted', function (id) {
59 | var callback = app.store.queue.get(id);
60 | if (!callback) {
61 | callback = app.store.apis.get(id);
62 | }
63 |
64 | app.store.callstack.add({
65 | id: callback.id.toString(),
66 | code: callback.code,
67 | isCallback: true
68 | });
69 | app.store.queue.remove(callback);
70 | });
71 |
72 | app.store.code.on('callback:completed', function (id) {
73 | //app.store.callstack.remove(id.toString());
74 | app.store.callstack.pop();
75 | });
76 |
77 | app.store.code.on('callback:spawn', function (data) {
78 | var webapi = app.store.apis.get(data.apiId);
79 |
80 | if (webapi) {
81 | webapi.trigger('callback:spawned', webapi);
82 | }
83 | app.store.queue.add(data);
84 | });
85 |
86 | app.store.apis.on('callback:spawn', function (data) {
87 | app.store.queue.add(data);
88 | });
89 |
90 | app.store.code.on('reset-everything', function () {
91 | app.store.renderQueue.reset();
92 | app.store.queue.reset();
93 | app.store.callstack.reset();
94 | app.store.apis.reset();
95 | });
96 |
97 | app.store.code.on('paused', function () {
98 | app.store.apis.pause();
99 | });
100 |
101 | app.store.code.on('resumed', function () {
102 | app.store.apis.resume();
103 | });
104 |
105 | app.store.callstack.on('all', function () {
106 | if (app.store.callstack.length === 0) {
107 | app.store.renderQueue.shift();
108 | }
109 | });
110 |
111 | app.store.renderQueue.on('add', function () {
112 | if (app.store.callstack.length === 0) {
113 | app.store.renderQueue.shift();
114 | }
115 | });
116 |
117 | if (window.location.origin.match('latentflip.com')) {
118 | window.app.router.history.start({ pushState: true, root: '/loupe/' });
119 | } else {
120 | window.app.router.history.start({ pushState: true });
121 | }
122 |
123 | Modal.setAppElement(document.body);
124 | Modal.injectCSS();
125 | React.renderComponent(App(), document.body);
126 |
--------------------------------------------------------------------------------
/loupe.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react/addons');
2 | window.React = React;
3 | var App = require('./components/app.jsx');
4 | var AmpersandCollection = require('ampersand-collection');
5 | var AmpersandState = require('ampersand-state');
6 | var deval = require('deval');
7 |
8 | var CallStack = require('./models/callstack');
9 |
10 | var Code = require('./models/code');
11 | var Apis = require('./models/apis');
12 | var CallbackQueue = require('./models/callback-queue');
13 | var RenderQueue = require('./models/render-queue');
14 |
15 | var Router = require('./router');
16 | var Modal = require('react-modal');
17 |
18 | window.app = {};
19 |
20 | window.app.router = new Router();
21 |
22 | window.app.store = {
23 | callstack: new CallStack(),
24 | code: new Code(),
25 | apis: new Apis(),
26 | queue: new CallbackQueue(),
27 | renderQueue: new RenderQueue()
28 | };
29 |
30 | app.store.code.on('change:codeLines', function () {
31 | });
32 |
33 | app.store.code.on('change:encodedSource', function () {
34 | app.router.navigate('?code=' + app.store.code.encodedSource);
35 | });
36 |
37 | //app.store.code.on('all', function () {
38 | // console.log('Code event', arguments);
39 | //});
40 |
41 | app.store.code.on('node:will-run', function (id, source, invocation) {
42 | app.store.callstack.add({
43 | _id: id,
44 | code: source
45 | });
46 | });
47 |
48 | app.store.code.on('node:did-run', function (id, invocation) {
49 | app.store.callstack.pop();
50 | //app.store.callstack.remove(app.store.callstack.at(app.store.call
51 | //app.store.callstack.remove(id + ':' + invocation);
52 | });
53 |
54 | app.store.code.on('webapi:started', function (data) {
55 | app.store.apis.add(data, { merge: true });
56 | });
57 |
58 | app.store.code.on('callback:shifted', function (id) {
59 | var callback = app.store.queue.get(id);
60 | if (!callback) {
61 | callback = app.store.apis.get(id);
62 | }
63 |
64 | app.store.callstack.add({
65 | id: callback.id.toString(),
66 | code: callback.code,
67 | isCallback: true
68 | });
69 | app.store.queue.remove(callback);
70 | });
71 |
72 | app.store.code.on('callback:completed', function (id) {
73 | //app.store.callstack.remove(id.toString());
74 | app.store.callstack.pop();
75 | });
76 |
77 | app.store.code.on('callback:spawn', function (data) {
78 | var webapi = app.store.apis.get(data.apiId);
79 |
80 | if (webapi) {
81 | webapi.trigger('callback:spawned', webapi);
82 | }
83 | app.store.queue.add(data);
84 | });
85 |
86 | app.store.apis.on('callback:spawn', function (data) {
87 | app.store.queue.add(data);
88 | });
89 |
90 | app.store.code.on('reset-everything', function () {
91 | app.store.renderQueue.reset();
92 | app.store.queue.reset();
93 | app.store.callstack.reset();
94 | app.store.apis.reset();
95 | });
96 |
97 | app.store.code.on('paused', function () {
98 | app.store.apis.pause();
99 | });
100 |
101 | app.store.code.on('resumed', function () {
102 | app.store.apis.resume();
103 | });
104 |
105 | app.store.callstack.on('all', function () {
106 | if (app.store.callstack.length === 0) {
107 | app.store.renderQueue.shift();
108 | }
109 | });
110 |
111 | app.store.renderQueue.on('add', function () {
112 | if (app.store.callstack.length === 0) {
113 | app.store.renderQueue.shift();
114 | }
115 | });
116 |
117 | if (window.location.origin.match('latentflip.com')) {
118 | window.app.router.history.start({ pushState: true, root: '/loupe/' });
119 | } else {
120 | window.app.router.history.start({ pushState: true });
121 | }
122 |
123 | Modal.setAppElement(document.body);
124 | Modal.injectCSS();
125 | React.renderComponent(App(), document.body);
126 |
--------------------------------------------------------------------------------
/models/apis.js:
--------------------------------------------------------------------------------
1 | var AmpersandCollection = require('ampersand-collection');
2 | var AmpersandState = require('ampersand-state');
3 |
4 | var Timeout = AmpersandState.extend({
5 | props: {
6 | id: ['string'],
7 | type: ['string', true, 'timeout'],
8 | timeout: ['number', true, 0],
9 | code: 'string',
10 | playState: ['string', true, 'running']
11 | },
12 | session: {
13 | timeoutId: 'number',
14 | startedAt: 'number',
15 | pausedAt: 'number',
16 | remainingTime: 'number'
17 | },
18 | derived: {
19 | timeoutString: {
20 | deps: ['timeout'],
21 | fn: function () {
22 | return this.timeout/1000 + 's';
23 | }
24 | }
25 | },
26 |
27 | pause: function () {
28 | this.pausedAt = Date.now();
29 | this.remainingTime = this.remainingTime - (this.pausedAt - this.startedAt);
30 | this.playState = 'paused';
31 | clearTimeout(this.timeoutId);
32 | },
33 |
34 | resume: function () {
35 | this.startedAt = Date.now();
36 | this.playState = 'running';
37 |
38 | this.timeoutId = setTimeout(function () {
39 | this.trigger('callback:spawn', {
40 | id: this.id,
41 | code: this.code
42 | });
43 | this.collection.remove(this);
44 | }.bind(this), this.remainingTime);
45 | },
46 |
47 | initialize: function () {
48 | this.startedAt = Date.now();
49 | this.remainingTime = this.timeout;
50 |
51 | this.timeoutId = setTimeout(function () {
52 | this.trigger('callback:spawn', {
53 | id: this.id,
54 | code: this.code
55 | });
56 | this.collection.remove(this);
57 | }.bind(this), this.remainingTime);
58 |
59 | this.on('remove', function () {
60 | clearTimeout(this.timeoutId);
61 | }.bind(this));
62 | },
63 |
64 | getPausedState: function () {
65 | return { remainingTime: this.remainingTime };
66 | }
67 | });
68 |
69 | var Query = AmpersandState.extend({
70 | props: {
71 | id: 'string',
72 | type: ['string', true, 'query'],
73 | selector: 'string',
74 | event: 'string'
75 | },
76 | derived: {
77 | code: {
78 | deps: ['selector', 'event'],
79 | fn: function () {
80 | return "$.on('" + this.selector + "', '" + this.event + "', ...)";
81 | }
82 | }
83 | },
84 | pause: function () {
85 | },
86 | resume: function () {
87 | },
88 | getPausedState: function () {
89 | return { };
90 | }
91 | });
92 |
93 | module.exports = AmpersandCollection.extend({
94 | model: function (props, opts) {
95 | if (props.type === 'timeout') {
96 | return new Timeout(props, opts);
97 | }
98 | if (props.type === 'query') {
99 | return new Query(props, opts);
100 | }
101 | throw 'Unknown prop type: ' + props.type;
102 | },
103 | pause: function () {
104 | this.each(function (model) { model.pause(); });
105 | },
106 | resume: function () {
107 | this.each(function (model) { model.resume(); });
108 | },
109 | getPausedState: function () {
110 | var data = {};
111 | this.each(function (model) {
112 | data[model.id] = model.getPausedState();
113 | });
114 | return data;
115 | }
116 | });
117 |
--------------------------------------------------------------------------------
/models/base-collection.js:
--------------------------------------------------------------------------------
1 | var Collection = require('ampersand-collection');
2 |
3 | module.exports = Collection.extend(require('ampersand-collection-underscore-mixin'));
4 |
--------------------------------------------------------------------------------
/models/callback-queue.js:
--------------------------------------------------------------------------------
1 | var AmpersandCollection = require('ampersand-collection');
2 | var Callback = require('./callback');
3 |
4 | module.exports = AmpersandCollection.extend({
5 | model: Callback
6 | });
7 |
--------------------------------------------------------------------------------
/models/callback.js:
--------------------------------------------------------------------------------
1 | var AmpersandState = require('ampersand-state');
2 |
3 | module.exports = AmpersandState.extend({
4 | props: {
5 | id: 'string',
6 | code: 'string',
7 | state: 'string'
8 | }
9 | });
10 |
--------------------------------------------------------------------------------
/models/callstack.js:
--------------------------------------------------------------------------------
1 | var AmpersandState = require('ampersand-state');
2 | var AmpersandCollection = require('ampersand-collection');
3 |
4 | var StackFrame = AmpersandState.extend({
5 | props: {
6 | _id: 'any',
7 | _key: 'string',
8 | code: 'string'
9 | },
10 | initialize: function () {
11 | this._key = this._key || Date.now().toString();
12 | }
13 | });
14 |
15 | module.exports = AmpersandCollection.extend({
16 | model: StackFrame,
17 | last: function () {
18 | return this.at(this.length - 1);
19 | },
20 | pop: function () {
21 | var removed = this.last();
22 | this.remove(removed);
23 | return removed;
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/models/code.js:
--------------------------------------------------------------------------------
1 | var AmpersandState = require('ampersand-state');
2 | var instrumentCode = require('../lib/instrument-code');
3 | var deval = require('deval');
4 | var wrapInsertionPoints = require('../lib/wrap-insertion-points');
5 | var weevil = require('weevil');
6 | var tag = require('../lib/tag');
7 | var delayMaker = require('../lib/delay');
8 | var debounce = require('lodash.debounce');
9 |
10 | var $ = require('../lib/plugins/query');
11 | var consolePlugin = require('../lib/plugins/console');
12 |
13 | var cleanupCode = function (code) {
14 | return code.replace(/
/g, '\n');
15 | };
16 |
17 | module.exports = AmpersandState.extend({
18 | props: {
19 | htmlScratchpad: ['array', true],
20 | codeLines: ['array', true],
21 | worker: 'any',
22 | delay: ['number', true, function () {
23 | return parseInt(localStorage.loupeDelay, 10) || 750;
24 | }],
25 | running: ['boolean', true, false],
26 | simulateRenders: ['boolean', true, false]
27 | },
28 | derived: {
29 | rawHtmlScratchpad: {
30 | deps: ['htmlScratchpad'],
31 | fn: function () {
32 | return this.htmlScratchpad.join('\n');
33 | }
34 | },
35 | html: {
36 | deps: ['codeLines'],
37 | fn: function () {
38 | return this.codeLines.join('
');
39 | }
40 | },
41 | rawCode: {
42 | deps: ['codeLines'],
43 | fn: function () {
44 | return this.codeLines.join('\n');
45 | }
46 | },
47 | encodedSource: {
48 | deps: ['rawCode', 'rawHtmlScratchpad'],
49 | fn: function () {
50 | return encodeURIComponent(btoa(this.rawCode) + "!!!" + btoa(this.rawHtmlScratchpad));
51 | }
52 | },
53 | cleanCode: {
54 | deps: ['rawCode'],
55 | fn: function () {
56 | return this.rawCode;
57 | }
58 | },
59 | instrumented: {
60 | deps: ['cleanCode'],
61 | fn: function () {
62 | return instrumentAndWrapHTML(this.cleanCode);
63 | }
64 | },
65 | wrappedHtml: {
66 | deps: ['instrumented'],
67 | fn: function () {
68 | return this.instrumented.html;
69 | }
70 | },
71 | runnableCode: {
72 | deps: ['instrumented'],
73 | fn: function () {
74 | return this.instrumented.code;
75 | }
76 | },
77 | nodeSourceCode: {
78 | deps: ['instrumented'],
79 | fn: function () {
80 | return this.instrumented.nodeSourceCode;
81 | }
82 | }
83 | },
84 |
85 | initialize: function () {
86 | this.on('ready-to-run', function () { this.running = true; });
87 | this.on('resumed', function () { this.running = true; });
88 | this.on('paused', function () { this.running = false; });
89 | this.on('reset-everything', function () { this.running = false; });
90 |
91 | var self = this;
92 | var pauseAndResume = debounce(function () {
93 | if (self.running) {
94 | self.pause();
95 | self.resume();
96 | }
97 | }, 100);
98 |
99 | this.on('change:delay', pauseAndResume);
100 | },
101 |
102 | makeWorkerCode: function (fromId, appState) {
103 | return makeWorkerCode(this.runnableCode, {
104 | delay: this.delay,
105 | resumeFromDelayId: fromId,
106 | appState: appState
107 | });
108 | },
109 |
110 | decodeUriSource: function (encoded) {
111 | var parts = decodeURIComponent(encoded).split('!!!');
112 | try {
113 | this.codeLines = atob(parts[0]).split('\n');
114 | } catch (e) {
115 | this.codeLines = [];
116 | }
117 | try {
118 | this.htmlScratchpad = atob(parts[1]).split('\n');
119 | } catch (e) {
120 | this.htmlScratchpad = [];
121 | }
122 | },
123 |
124 | resetEverything: function () {
125 | this.trigger('reset-everything');
126 | this.running = false;
127 | if (this.worker) { this.worker.kill(); }
128 | },
129 |
130 | pause: function () {
131 | this.trigger('paused');
132 | this.pausedExecution = this.currentExecution;
133 | this.worker.kill();
134 | },
135 |
136 | resume: function () {
137 | this.trigger('resumed');
138 | var appState = {
139 | webapis: app.store.apis.getPausedState(),
140 | query: this.queryClient.historyLog
141 | };
142 |
143 | this.ignoreEvents = true;
144 | this.run(this.pausedExecution, appState);
145 | },
146 |
147 | run: function (fromId, appState) {
148 | appState = appState || {
149 | webapis: {},
150 | query: {}
151 | };
152 |
153 | setTimeout(function () {
154 | var self = this;
155 |
156 | if (!fromId) {
157 | this.resetEverything();
158 | }
159 |
160 | this.trigger('ready-to-run');
161 | this.worker = weevil(this.makeWorkerCode(fromId, appState));
162 |
163 | //TODO this shouldn't know about the scratchpad
164 | this.queryClient = $.createClient(this, this.worker, document.querySelector('.html-scratchpad'));
165 | consolePlugin.createClient(this, this.worker);
166 |
167 | this.worker
168 | .on('node:before', function (node) {
169 | self.trigger('node:will-run', node.id, self.nodeSourceCode[node.id]);
170 | //$('#node-' + node.id).addClass('running');
171 | })
172 | .on('node:after', function (node) {
173 | self.trigger('node:did-run', node.id);
174 | //$('#node-' + node.id).removeClass('running');
175 | })
176 | .on('timeout:created', function (timer) {
177 | self.trigger('webapi:started', {
178 | id: 'timer:' + timer.id,
179 | type: 'timeout',
180 | timeout: timer.delay,
181 | code: timer.code.split('\n').join(' ')
182 | });
183 | })
184 | .on('timeout:started', function (timer) {
185 | self.trigger('callback:shifted', 'timer:' + timer.id);
186 | })
187 | .on('timeout:finished', function (timer) {
188 | self.trigger('callback:completed', 'timer:' + timer.id);
189 | })
190 | .on('callback:shifted', function (callbackId) {
191 | self.trigger('callback:shifted', callbackId);
192 | })
193 | .on('callback:completed', function (callbackId) {
194 | self.trigger('callback:completed', callbackId);
195 | })
196 | .on('delay', function (delayId) {
197 | if (self.pausedExecution) {
198 | if (delayId >= self.pausedExecution) {
199 | self.ignoreEvents = false;
200 | }
201 | }
202 | self.currentExecution = delayId;
203 | });
204 |
205 | }.bind(this), 0);
206 | }
207 | });
208 |
209 | var instrumentAndWrapHTML = function (code) {
210 | var instrumented = instrumentCode(code, {
211 | before: function (id, node) {
212 | var source = JSON.stringify(node.source());
213 | return deval(function (id, type, source) {
214 | weevil.send('node:before', { id: $id$, type: "$type$", source: $source$ }), delay()
215 | }, id, node.type, source);
216 | },
217 | after: function (id, node) {
218 | return deval(function (id) {
219 | weevil.send('node:after', { id: $id$ }), delay()
220 | }, id);
221 | }
222 | });
223 |
224 | var nodeSourceCode = {};
225 | var html = wrapInsertionPoints(code, instrumented.insertionPoints, {
226 | before: function (id) {
227 | return tag.o('span', {
228 | id: 'node-' + id,
229 | class: 'code-node',
230 | //style: "background: rgba(0,0,0,0.2);"
231 | });
232 | },
233 | after: function (id) {
234 | return tag.c('span');
235 | },
236 | withWrappedCode: function (id, code) {
237 | nodeSourceCode[id] = code;
238 | }
239 | });
240 |
241 | html = html.replace(/;\n/g, ';');
242 |
243 | return {
244 | code: instrumented.code,
245 | html: html,
246 | nodeSourceCode: nodeSourceCode
247 | };
248 | };
249 |
250 | function prependCode(prepend, code) {
251 | return prepend + ';\n' + code;
252 | }
253 |
254 | var makeWorkerCode = function (code, options) {
255 | var delayTime = options.delay;
256 | var resumeFromDelayId = options.resumeFromDelayId ? options.resumeFromDelayId.toString() : "null";
257 | var appState = JSON.stringify(options.appState || {});
258 |
259 | code = $.prependWorkerCode(code);
260 | code = consolePlugin.prependWorkerCode(code);
261 | code = prependCode(deval(function (delayMaker, delayTime, resumeFromDelayId, appState) {
262 | var loupe = {};
263 | loupe.appState = $appState$;
264 | loupe._onDelayCallbacks = {};
265 | loupe.onDelay = function (id, cb) {
266 | this._onDelayCallbacks[id] = this._onDelayCallbacks[id] || [];
267 | this._onDelayCallbacks[id].push(cb);
268 | };
269 | loupe.triggerDelay = function (id) {
270 | var cbs = this._onDelayCallbacks[id];
271 | if (cbs) {
272 | cbs.forEach(function (cb) {
273 | _setTimeout(cb, 0);
274 | });
275 | }
276 | };
277 |
278 | var _send = weevil.send;
279 | weevil.send = function (name) {
280 | if (loupe.skipDelays && name !== 'delay') { return; }
281 | return _send.apply(this, arguments);
282 | };
283 |
284 | var delayMaker = $delayMaker$;
285 |
286 | var delay = delayMaker($delayTime$, $resumeFromDelayId$);
287 |
288 | //Override setTimeout
289 | var _setTimeout = self.setTimeout;
290 | self.setTimeout = function (fn, timeout/*, args...*/) {
291 | var args = Array.prototype.slice.call(arguments);
292 | fn = args.shift();
293 | var timerId;
294 |
295 | var queued = +new Date();
296 | var data = { id: timerId, delay: timeout, created: +new Date(), state: 'timing', code: (fn.name || "anonymous") + "()" };
297 | args.unshift(function () {
298 | data.state = 'started';
299 | data.started = +new Date();
300 | data.error = (data.started - data.queued) - timeout;
301 | delay();
302 | weevil.send('timeout:started', data);
303 | delay();
304 |
305 | fn.apply(fn, arguments);
306 |
307 | data.state = 'finished';
308 | data.finished = +new Date();
309 | weevil.send('timeout:finished', data);
310 | delay();
311 | });
312 |
313 | if (loupe.appState.webapis[timerId]) {
314 | console.log('Overriding ' + args[1] + ' to ' + loupe.appState.webapis[timerId].remainingTime);
315 | args[1] = loupe.appState.webapis[timerId].remainingTime;
316 | }
317 |
318 | data.id = _setTimeout.apply(self, args);
319 | weevil.send('timeout:created', data);
320 | };
321 |
322 | }, delayMaker.toString(), delayTime, resumeFromDelayId, appState), code);
323 |
324 | return code;
325 | };
326 |
--------------------------------------------------------------------------------
/models/render-queue.js:
--------------------------------------------------------------------------------
1 | var AmpersandCollection = require('ampersand-collection');
2 | var AmpersandState = require('ampersand-state');
3 |
4 | var Render = AmpersandState.extend({
5 | props: {
6 | id: 'number',
7 | state: ['string', true, 'queued'],
8 | },
9 | initialize: function () {
10 | setTimeout(function () {
11 | this.state = 'delayed';
12 | }.bind(this), 20);
13 | }
14 | });
15 |
16 | module.exports = AmpersandCollection.extend({
17 | model: Render,
18 |
19 | initialize: function () {
20 | var id = 1;
21 |
22 | setInterval(function () {
23 | if (this.length === 0) {
24 | this.add({ id: id++ });
25 | }
26 | }.bind(this), 1000);
27 | },
28 | shift: function () {
29 | if (this.length > 0) {
30 | var model = this.at(0);
31 | setTimeout(function () {
32 | model.state = 'rendered';
33 | setTimeout(function () {
34 | this.remove(model.id);
35 | }.bind(this), 250);
36 | }.bind(this), 20);
37 | }
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/models/stack-frame.js:
--------------------------------------------------------------------------------
1 | var AndModel = require('ampersand-model');
2 |
3 | module.exports = AndModel.extend({
4 | type: 'stack-frame',
5 | props: {
6 | _id: 'string',
7 | nodeId: 'number',
8 | source: 'string',
9 | expressionType: 'string',
10 | createdAt: 'number',
11 | isCallback: ['boolean', true, false]
12 | },
13 | initialize: function () {
14 | this.id = Math.floor(Math.random() * 10000000);
15 | this.createdAt = +new Date();
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/models/stack-frames.js:
--------------------------------------------------------------------------------
1 | var Collection = require('./base-collection');
2 |
3 | var StackFrame = require('./stack-frame');
4 |
5 | module.exports = Collection.extend({
6 | model: StackFrame,
7 | comparator: function (m) {
8 | return -1 * m.createdAt;
9 | },
10 | pop: function () {
11 | this.remove(this.at(this.length - 1));
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/models/timeout.js:
--------------------------------------------------------------------------------
1 | var AndModel = require('ampersand-model');
2 |
3 | module.exports = AndModel.extend({
4 | type: 'timeout',
5 | props: {
6 | id: 'number',
7 | state: 'string',
8 | delay: 'number',
9 | created: ['date'],
10 | queued: ['date'],
11 | started: ['date'],
12 | finished: ['date']
13 | },
14 | initialize: function () {
15 | if (this.delay && this.created) {
16 | setTimeout(function () {
17 | this.state = 'queued';
18 | this.queued = +new Date();
19 | }.bind(this), this.delay);
20 | }
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/models/timeouts.js:
--------------------------------------------------------------------------------
1 | var Collection = require('./base-collection');
2 |
3 | var Timeout = require('./timeout');
4 |
5 | module.exports = Collection.extend({
6 | model: Timeout
7 | });
8 |
--------------------------------------------------------------------------------
/npm-debug.log:
--------------------------------------------------------------------------------
1 | 0 info it worked if it ends with ok
2 | 1 verbose cli [ '/Users/latentflip/.nvm/v0.10.33/bin/node',
3 | 1 verbose cli '/Users/latentflip/.nvm/v0.10.33/bin/npm',
4 | 1 verbose cli 'run',
5 | 1 verbose cli 'build' ]
6 | 2 info using npm@1.4.28
7 | 3 info using node@v0.10.33
8 | 4 verbose run-script [ 'prebuild', 'build', 'postbuild' ]
9 | 5 info prebuild trace@0.0.0
10 | 6 info build trace@0.0.0
11 | 7 verbose unsafe-perm in lifecycle true
12 | 8 info trace@0.0.0 Failed to exec build script
13 | 9 error trace@0.0.0 build: `make build`
14 | 9 error Exit status 2
15 | 10 error Failed at the trace@0.0.0 build script.
16 | 10 error This is most likely a problem with the trace package,
17 | 10 error not with npm itself.
18 | 10 error Tell the author that this fails on your system:
19 | 10 error make build
20 | 10 error You can get their info via:
21 | 10 error npm owner ls trace
22 | 10 error There is likely additional logging output above.
23 | 11 error System Darwin 14.0.0
24 | 12 error command "/Users/latentflip/.nvm/v0.10.33/bin/node" "/Users/latentflip/.nvm/v0.10.33/bin/npm" "run" "build"
25 | 13 error cwd /Users/latentflip/Code/github/latentflip/loupe
26 | 14 error node -v v0.10.33
27 | 15 error npm -v 1.4.28
28 | 16 error code ELIFECYCLE
29 | 17 verbose exit [ 1, true ]
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trace",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "trace.js",
6 | "directories": {
7 | "test": "tests"
8 | },
9 | "dependencies": {
10 | "ampersand-collection": "^1.3.15",
11 | "ampersand-collection-underscore-mixin": "^1.0.2",
12 | "ampersand-model": "^4.0.2",
13 | "ampersand-router": "^1.0.4",
14 | "ampersand-state": "^4.3.10",
15 | "ampersand-view": "^7.1.1",
16 | "brace": "^0.3.0",
17 | "deval": "~0.1.1",
18 | "escodegen": "~1.4.1",
19 | "esprima": "~1.2.2",
20 | "falafel": "~0.3.1",
21 | "hyperglue": "~1.3.1",
22 | "jquery": "~2.1.1",
23 | "lodash.debounce": "^2.4.1",
24 | "multiline": "~1.0.0",
25 | "react": "^0.12.0",
26 | "react-backbone-events-mixin": "^0.1.1",
27 | "react-modal": "0.0.5",
28 | "reactify": "^0.14.0",
29 | "tape": "~2.14.0",
30 | "underscore": "~1.7.0",
31 | "weevil": "~0.1.2"
32 | },
33 | "devDependencies": {
34 | "autoprefixer": "^3.0.0",
35 | "browserify": "^5.11.1",
36 | "building-static-server": "^2.1.0",
37 | "glob-cli": "^1.0.0",
38 | "reactify": "^0.14.0"
39 | },
40 | "scripts": {
41 | "build": "make build",
42 | "start": "building-static-server -p 3051"
43 | },
44 | "author": "",
45 | "license": "ISC"
46 | }
47 |
--------------------------------------------------------------------------------
/router.js:
--------------------------------------------------------------------------------
1 | var Router = require('ampersand-router');
2 |
3 |
4 | module.exports = Router.extend({
5 | routes: {
6 | '?code=:code': 'code',
7 | '': 'default'
8 | },
9 |
10 | code: function (code) {
11 | app.store.code.decodeUriSource(code);
12 | },
13 | default: function (code) {
14 | this.redirectTo("?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D");
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/templates.js:
--------------------------------------------------------------------------------
1 | var multiline = require('multiline');
2 |
3 | exports.timeoutItem = multiline(function () {/*
4 |
5 |
6 |
7 |
8 | */});
9 |
10 | exports.timeoutList = multiline(function () {/*
11 |
12 | */});
13 |
14 | exports.stackFrame = multiline(function () {/*
15 |
16 |
17 |
18 | */});
19 |
20 | exports.stackFrameList = multiline(function () {/*
21 |
22 | */});
23 |
24 | exports.mainView = multiline(function () {/*
25 |
31 | */});
32 |
33 | exports.code = multiline(function () {/*
34 |
35 |
36 | console.log(console.log('hi' + 2));
37 | var a = console.log('hi');
38 | [1,2,3].map(function (a) {
39 | console.log(a * 2);
40 | });
41 | console.log(a + a + a);
42 | var b = 2 + 2 + 2;
43 |
44 |
45 | */});
46 |
47 | exports.code = multiline(function () {/*
48 |
49 |
function baz () {
50 | console.log('bar');
51 | }
52 | function bar () {
53 | baz();
54 | }
55 | function foo () {
56 | bar();
57 | }
58 |
59 |
60 | setTimeout(function () {
61 | foo();
62 | }, 2000);
63 |
64 | setTimeout(function () {
65 | foo();
66 | }, 1000);
67 |
68 | setTimeout(function () {
69 | foo();
70 | }, 3000);
71 |
72 |
73 | */});
74 |
--------------------------------------------------------------------------------
/tests/text-cursor-test.js:
--------------------------------------------------------------------------------
1 | var test = require('tape');
2 |
3 | var TextCursor = require('../text-cursor');
4 |
5 | var code = [
6 | "a123456789",
7 | "b123456",
8 | "",
9 | "c12345"
10 | ].join("\n");
11 |
12 |
13 | test('within line: one at a time', function (t) {
14 | var cursor = new TextCursor(code);
15 | t.equal(cursor.stepTo(1,1), 'a');
16 | t.equal(cursor.stepTo(1,2), '1');
17 | t.equal(cursor.stepTo(1,3), '2');
18 | t.end();
19 | });
20 |
21 | test('within line: many at a time', function (t) {
22 | var cursor = new TextCursor(code);
23 | t.equal(cursor.stepTo(1,4), 'a123');
24 | t.equal(cursor.stepTo(1,5), '4');
25 | t.equal(cursor.stepTo(1,8), '567');
26 | t.end();
27 | });
28 |
29 |
30 | test('within line: to the end of the line', function (t) {
31 | var cursor = new TextCursor(code);
32 | t.equal(cursor.stepTo(1,10), 'a123456789\n');
33 | t.end();
34 | });
35 |
36 | test('within line: going beyond end of line', function (t) {
37 | var cursor = new TextCursor(code);
38 | t.equal(cursor.stepTo(1,50), 'a123456789\n');
39 | t.end();
40 | });
41 |
42 | test('across lines: to start of next line', function (t) {
43 | var cursor = new TextCursor(code);
44 | t.equal(cursor.stepTo(2,0), 'a123456789\n');
45 | t.end();
46 | });
47 |
--------------------------------------------------------------------------------