├── .babelrc
├── index.prod.js
├── index.html
├── components
├── Preview
│ ├── Page.css
│ └── Page.js
├── Editor
│ ├── Prose.js
│ ├── Code.css
│ └── Code.js
└── Inspector
│ └── Transforms.js
├── index.dev.js
├── README.md
├── server.js
├── polyfills
├── canvas.js
├── vector.js
└── css-parser.js
├── package.json
├── css
└── Root.css
├── webpack.config.js
└── containers
└── Root
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015-loose", "stage-0", "react"],
3 | "plugins": ["react-hot-loader/babel"]
4 | }
5 |
--------------------------------------------------------------------------------
/index.prod.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import Root from './containers/Root';
4 |
5 |
6 | render(
7 | ,
8 | document.body
9 | );
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
36 |
this.foo = c}
38 | className="foo"
39 | style={{ transform: this.state.transform }}
40 | />
41 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/components/Editor/Code.css:
--------------------------------------------------------------------------------
1 | div.editor {
2 | display: flex;
3 | flex-flow: column nowrap;
4 | justify-content: space-between;
5 | align-items: stretch;
6 | }
7 |
8 | div.editor header {
9 | position: relative;
10 | padding: 2px 10px;
11 | /*background: hsl(0, 0%, 95%);*/
12 | font: 200 12px/1.2 sans-serif;
13 | color: hsl(0, 0%, 20%);
14 | letter-spacing: 0.1em;
15 | }
16 |
17 |
18 | div.editor span.mode {
19 | position: absolute;
20 | top: 2px;
21 | right: 10px;
22 | }
23 |
24 |
25 | div.editor div.ReactCodeMirror {
26 | display: flex;
27 | flex-flow: row nowrap;
28 | justify-content: space-between;
29 | align-items: stretch;
30 | flex: 1 1 100%;
31 | padding: 0 10px;
32 | /*-webkit-filter: grayscale(1);
33 | filter: grayscale(1);*/
34 | }
35 |
36 | div.editor div.ReactCodeMirror--focused {
37 | /*-webkit-filter: none;
38 | filter: none;*/
39 | }
40 |
41 | div.editor .CodeMirror {
42 | flex: 1 1 100%;
43 | height: auto;
44 | font: 14px/1.5 SourceCodeProRegular, Menlo, Consolas, 'DejaVu Sans Mono', monospace;
45 | /*background: hsl(0, 0%, 96%);*/
46 | }
47 |
48 | div.editor .CodeMirror-sizer {
49 | /*background: white;*/
50 | }
51 |
52 |
53 | div.editor .CodeMirror-selected:nth-child(1) {
54 | border-top-left-radius: 3px;
55 | border-top-right-radius: 3px;
56 | }
57 | div.editor .CodeMirror-selected:nth-child(2) {
58 | border-bottom-left-radius: 3px;
59 | border-bottom-right-radius: 3px;
60 | }
61 | div.editor .CodeMirror-selected:only-child {
62 | border-radius: 2px;
63 | }
64 |
65 |
66 | div.editor .CodeMirror * {
67 | /*box-sizing: content-box;*/
68 | }
69 |
70 | div.editor .CodeMirror {
71 | contain: strict;
72 | }
73 | div.editor .CodeMirror-scroll,
74 | div.editor .CodeMirror-selected,
75 | div.editor .CodeMirror-cursors,
76 | div.editor .CodeMirror-code,
77 | div.editor .CodeMirror-linenumber {
78 | contain: size layout style;
79 | }
80 |
81 | div.editor .CodeMirror-line {
82 | contain: content;
83 | }
84 |
85 | div.editor .CodeMirror-scroll {
86 | /*margin-bottom: 0px;
87 | margin-right: 0px;
88 | padding-bottom: 0px;*/
89 | backface-visibility: hidden;
90 | }
91 |
92 |
93 | div.editor .CodeMirror-focused {
94 | /*background: hsl(0, 0%, 100%);*/
95 | }
96 |
97 | div.editor .CodeMirror-gutters {
98 | background: none;
99 | }
100 |
101 | div.editor .CodeMirror-selected {
102 | background: hsl(210, 0%, 83%);
103 | }
104 |
105 | div.editor div.ReactCodeMirror--focused .CodeMirror-selected {
106 | background-color: hsla(215, 100%, 75%, 0.5);
107 | }
108 |
109 | div.editor .CodeMirror textarea {
110 | overflow: hidden;
111 | }
112 |
113 | div.editor .CodeMirror-sizer + div {
114 | visibility: hidden;
115 | }
116 |
--------------------------------------------------------------------------------
/components/Editor/Code.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FileSystem from 'systems/FileSystem.js';
3 | import CodeMirror from 'react-codemirror';
4 | import 'codemirror/mode/htmlmixed/htmlmixed';
5 | import 'codemirror/mode/css/css';
6 | import 'codemirror/mode/javascript/javascript';
7 | import 'codemirror/keymap/sublime';
8 | import 'codemirror/lib/codemirror.css';
9 | import './Code.css';
10 |
11 |
12 | export default class CodeEditor extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | content: null,
17 | mode: null
18 | };
19 | this.watcher = null;
20 | }
21 |
22 | componentWillMount() {
23 | if(this.props.path)
24 | {
25 | this.guessMode();
26 | this.watcher = FileSystem.watch(this.props.path);
27 | FileSystem.read(this.props.path).then(::this.handleRead);
28 | }
29 | }
30 | componentDidMount() {}
31 |
32 | componentWillUnmount() {
33 | if(this.watcher)
34 | {
35 | FileSystem.unwatch(this.watcher);
36 | this.watcher = null;
37 | }
38 | }
39 |
40 | componentWillReceiveProps(nextProps) {
41 | if(nextProps.path !== this.props.path)
42 | {
43 | if(this.props.path) FileSystem.unwatch(this.watcher);
44 | this.watcher = nextProps.path? FileSystem.watch(nextProps.path) : null;
45 | }
46 | }
47 |
48 | componentWillUpdate(nextProps, nextState) {}
49 | componentDidUpdate(prevProps, prevState) {}
50 |
51 | guessMode() {
52 | if(this.props.path.endsWith('.html')) this.setState({ mode: 'htmlmixed' });
53 | else if(this.props.path.endsWith('.css')) this.setState({ mode: 'css' });
54 | else if(this.props.path.endsWith('.js')) this.setState({ mode: 'javascript' });
55 | else this.setState({ mode: 'null' });
56 | }
57 |
58 | handleRead(content) {
59 | this.setState({ content });
60 | }
61 |
62 | handleEdit(newCode) {
63 | this.watcher.pause();
64 | FileSystem.write(this.props.path, newCode).then(() => this.watcher.resume());
65 | }
66 |
67 | render() {
68 | if(!this.props.path) return (
69 |
70 | );
71 |
72 | if(this.state.content == null) return (
73 |
74 | );
75 |
76 | return (
77 |
78 |
79 | {this.props.path}
80 | {this.state.mode && {{
81 | htmlmixed: 'HTML',
82 | css: 'CSS',
83 | javascript: 'JS'
84 | }[this.state.mode]}}
85 |
86 |
98 |
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/components/Preview/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FileSystem from 'systems/FileSystem.js';
3 | import './Page.css';
4 |
5 | export default class Preview extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.watcher = null;
9 | this.tid = null;
10 | this.watchedDependencies = {};
11 | }
12 |
13 | componentWillMount() {}
14 | componentDidMount() {
15 | if(this.props.path)
16 | {
17 | this.watcher = FileSystem.watch(this.props.path);
18 | this.watcher.onUpdate = ::this.handleUpdate;
19 | this.tid = setInterval(::this.checkDependencies, 1000);
20 | }
21 | }
22 |
23 | componentWillUnmount() {
24 | if(this.tid)
25 | {
26 | clearInterval(this.tid);
27 | this.tid = null;
28 | }
29 |
30 | if(this.watcher)
31 | {
32 | FileSystem.unwatch(this.watcher);
33 | this.watcher = null;
34 | }
35 | }
36 |
37 | componentWillReceiveProps(nextProps) {
38 | if(nextProps.path !== this.props.path)
39 | {
40 | if(this.props.path) FileSystem.unwatch(this.watcher);
41 | this.watcher = nextProps.path? FileSystem.watch(nextProps.path) : null;
42 | }
43 | }
44 |
45 | componentWillUpdate(nextProps, nextState) {}
46 | componentDidUpdate(prevProps, prevState) {}
47 |
48 | checkDependencies() {
49 | if(!(this.props.path && this._iframe && this._iframe.contentWindow)) return;
50 |
51 | // Select all
s
52 | var elements = this._iframe.contentWindow.document.querySelectorAll('link[href]');
53 |
54 | // Filter down to local files
55 | var files = Array.prototype.slice.call(elements)
56 | .map(element => new URL(element.href))
57 | .filter(url => url.origin == location.origin && url.pathname.startsWith('/live/'))
58 | .map(url => url.pathname.replace('/live', ''));
59 |
60 | // Check for added dependencies
61 | files
62 | .filter(path => !(path in this.watchedDependencies))
63 | .forEach(path => {
64 | console.log('Added dependency:', path);
65 | var watcher = FileSystem.watch(path);
66 | watcher.on = (event) => console.log(event);
67 | watcher.onUpdate = ::this.handleUpdateDependency;
68 | this.watchedDependencies[path] = watcher;
69 | });
70 |
71 | // Check for removed dependencies
72 | Object.keys(this.watchedDependencies)
73 | .filter(path => files.indexOf(path) === -1)
74 | .forEach(path => {
75 | console.log('Removed dependency:', path);
76 | var watcher = this.watchedDependencies[path];
77 | FileSystem.unwatch(watcher);
78 | delete this.watchedDependencies[path];
79 | });
80 | }
81 |
82 | handleUpdate(event) {
83 | if(this._iframe)
84 | {
85 | // this._iframe.contentWindow.location.reload();
86 |
87 | fs.read(event.path).then((html) => {
88 | if(html.indexOf)
89 | html = '
' + html;
90 | this._iframe.contentDocument.open();
91 | this._iframe.contentDocument.write('');
92 | this._iframe.contentDocument.write(html);
93 | this._iframe.contentDocument.close();
94 | });
95 | }
96 | }
97 |
98 | handleUpdateDependency(event) {
99 | var path = event.path.slice(1);
100 |
101 | // Select
responsible
102 | var element = this._iframe.contentWindow.document.querySelector(`link[href^="${path}"]`);
103 | element.href = path + '?' + (new Date()).toString();
104 |
105 | // console.log('Dependency was updated:', element.href);
106 | }
107 |
108 | render() {
109 | if(!this.props.path) return (
110 | null
111 | );
112 |
113 | return (
114 |
115 | {/*
116 | {location.origin + '/live' + this.props.path}
117 | */}
118 |
126 |
127 | )
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/polyfills/css-parser.js:
--------------------------------------------------------------------------------
1 |
2 | class CSSParser {
3 | static transform(string) {
4 | if(!string) return null;
5 |
6 | var number = '[+-]?(?:\\d+\\.\\d+|\\.?\\d+)(?:e\\d+)?';
7 |
8 | var length = `${number}(px|em|ex|rem|vw|vh|vmin|vmax|in|cm|mm|pt|pc)`;
9 | var percent = `${number}(%)`;
10 | var dimension = `${number}(%|px|em|ex|rem|vw|vh|vmin|vmax|in|cm|mm|pt|pc)`;
11 | var angle = `${number}(deg|rad|turn)`;
12 |
13 | var translate = new RegExp(`\\s*(${dimension}|0)\\s*(?:,\\s*(${dimension}|0)\\s*)?`);
14 | var translateXYZ = new RegExp(`\\s*(${dimension}|0)\\s*`);
15 | var translate3d = new RegExp(`\\s*(${dimension}|0)\\s*,\\s*(${dimension}|0)\\s*,\\s*(${dimension}|0)\\s*`);
16 |
17 | var rotate = new RegExp(`\\s*(${angle}|0)\\s*`);
18 | var rotate3d = new RegExp(`\\s*${number}\\s*,\\s*${number}\\s*,\\s*${number}\\s*,\\s*(${angle}|0)\\s*`);
19 |
20 | var skew = new RegExp(`\\s*(${angle}|0)\\s*(?:,\\s*(${angle}|0)\\s*)?`);
21 | var skewXY = new RegExp(`\\s*(${angle}|0)\\s*`);
22 |
23 | var perspective = new RegExp(`\\s*(${length}|0)\\s*`);
24 |
25 | var functions = /\b((?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|Z|3d)?)\((.*?)\)/g;
26 |
27 | var results = [];
28 | var result;
29 | while(result = functions.exec(string))
30 | {
31 | var name = result[1];
32 | var args = result[2];
33 | var values;
34 | switch(name) {
35 | case 'matrix': values = args.split(','); if(values.length !== 6) return null; break;
36 | case 'matrix3d': values = args.split(','); if(values.length !== 16) return null; break;
37 | case 'translate': values = args.match(translate); break;
38 | case 'translateX':
39 | case 'translateY':
40 | case 'translateZ': values = args.match(translateXYZ); break;
41 | case 'translate3d': values = args.match(translate3d); break;
42 | case 'scale':
43 | case 'scaleX':
44 | case 'scaleY':
45 | case 'scaleZ':
46 | case 'scale3d': values = args.split(','); break;
47 | case 'rotate':
48 | case 'rotateX':
49 | case 'rotateY':
50 | case 'rotateZ': values = args.match(rotate); break;
51 | case 'rotate3d': values = args.match(rotate3d); break;
52 | case 'skew': values = args.match(skew); break;
53 | case 'skewX':
54 | case 'skewY': values = args.match(skewXY); break;
55 | case 'perspective': values = args.match(perspective); break;
56 | default: return null; // An invalid or unimplemented transform function? (E.g. skewZ, perspectiveX)
57 | }
58 |
59 | // Parse values into floats
60 | if(/^(matrix|scale)/.test(name)) // unit-less
61 | {
62 | values = values.map(value => parseFloat(value));
63 | }
64 |
65 | else // Some functions have tuples of [value, unit]
66 | {
67 | values = (values => {
68 | var temp = [];
69 | for(var iter = 0, total = values.length; iter < total; iter += 2)
70 | {
71 | var value = values[iter];
72 | var unit = values[iter + 1];
73 | if(typeof value === 'string')
74 | {
75 | var pair = [parseFloat(value), unit];
76 | temp.push(pair);
77 | }
78 | else return temp;
79 | }
80 | return temp;
81 | })(values.slice(1));
82 |
83 | switch(name) {
84 | case 'translate':
85 | values[0].push('width');
86 | if(values[1]) values[1].push('height');
87 | break;
88 | case 'translateX': values[0].push('width'); break;
89 | case 'translateY': values[0].push('height'); break;
90 | case 'translate3d':
91 | values[0].push('width');
92 | values[1].push('height');
93 | break;
94 | }
95 | }
96 |
97 | results.push({
98 | name,
99 | values
100 | });
101 | }
102 |
103 | return results;
104 | }
105 |
106 | static value(input) {
107 | if(!input || !input.length) return null;
108 | var cardinal = input[2] || 'width';
109 |
110 | if(typeof input === 'string')
111 | {
112 | input = input.match(/([+-]?\d+?(?:\.\d+)?)(px|em|rem|ex|%|vw|vh|vmin|vmax|in|cm|mm|pt|pc|deg|rad|turn)?/);
113 | var value = parseFloat(input[1]);
114 | var unit = input[2];
115 | }
116 |
117 | else
118 | {
119 | var [value, unit] = input;
120 | }
121 |
122 | switch (unit) {
123 | case 'em': return value * CSSParser.base.fontSize;
124 | case 'rem': return value * parseFloat(getComputedStyle(document.documentElement).fontSize);
125 | case 'ch': return value * CSSParser.base.fontSize * 0.5;
126 | case 'ex': return value * CSSParser.base.fontSize * 0.45;
127 | case '%': return value * CSSParser.base[cardinal] / 100;
128 | case 'vw': return value * window.innerWidth / 100;
129 | case 'vh': return value * window.innerHeight / 100;
130 | case 'vmin': return value * Math.min(window.innerWidth, window.innerHeight) / 100;
131 | case 'vmax': return value * Math.max(window.innerWidth, window.innerHeight) / 100;
132 | case 'in': return value * 72;
133 | case 'cm': return value / 2.54 * 96;
134 | case 'mm': return value / 2.54 * 96 / 10;
135 | case 'pt': return value * 96 / 72;
136 | case 'pc': return value * CSSParser.base[cardinal];
137 | case 'deg': return value * 0.017453292519943295;
138 | case 'rad': return value;
139 | case 'turn': return value * 6.283185307179586;
140 | default: return value;
141 | }
142 | }
143 | }
144 |
145 | CSSParser.base = { width: 1, height: 1, fontSize: 16 };
146 |
147 | export default CSSParser;
148 |
--------------------------------------------------------------------------------
/components/Inspector/Transforms.js:
--------------------------------------------------------------------------------
1 | // import { glMatrix, mat2, mat2d, mat3, mat4, quat, vec2, vec3, vec4 } from 'polyfills/gl-matrix.js';
2 | import mat4 from 'gl-mat4';
3 | import decompose from 'mat4-decompose';
4 | mat4.decompose = decompose;
5 | import quat from 'gl-quat';
6 | import vec3 from 'gl-vec3';
7 |
8 | import 'polyfills/canvas.js';
9 | import Vector from 'polyfills/vector.js';
10 | import CSSParser from 'polyfills/css-parser.js';
11 | import React from 'react';
12 |
13 | /*
14 | [ 0, 1, 2, 3, [m00,m10,m20,m30, [sx, , ,
15 | 4, 5, 6, 7, m01,m11,m21,m31, , sy, ,
16 | 8, 9,10,11, m02,m12,m22,m32, , , sz,-1/perspective
17 | 12,13,14,15] m03,m13,m23,m33] tx, ty, tz, ]
18 | */
19 |
20 |
21 | mat4.getCSSMatrix2d = function(out, mat) {
22 | out[0] = mat[0];
23 | out[1] = mat[1];
24 | out[2] = mat[4];
25 | out[3] = mat[5];
26 | out[4] = mat[12];
27 | out[5] = mat[13];
28 | return out;
29 | };
30 |
31 | mat4.getScaling = function(out, mat) {
32 | out[0] = mat[0];
33 | out[1] = mat[5];
34 | out[2] = mat[10];
35 | };
36 |
37 | quat.getEuler = function(out, q) {
38 | var sx = 2 * (q[0] * q[3] - q[1] * q[2]);
39 | var sy = 2 * (q[1] * q[3] + q[0] * q[2]);
40 | var ys = q[3] * q[3] - q[1] * q[1];
41 | var xz = q[0] * q[0] - q[2] * q[2];
42 | var cx = ys - xz;
43 | var cy = Math.sqrt(sx * sx + cx * cx);
44 |
45 | if (cy > 0.00034906584331009674)
46 | {
47 | out[0] = Math.atan2(sx, cx);
48 | out[1] = Math.atan2(sy, cy);
49 | out[2] = Math.atan2(2 * (q[2] * q[3] - q[0] * q[1]), ys + xz);
50 | }
51 |
52 | else
53 | {
54 | out[0] = 0;
55 | if (sy > 0)
56 | {
57 | out[1] = Math.PI/2;
58 | out[2] = 2 * Math.atan2(q[2] + q[0], q[3] + q[1]);
59 | }
60 | else
61 | {
62 | out[1] = -Math.PI/2;
63 | out[2] = 2 * Math.atan2(q[2] - q[0], q[3] - q[1]);
64 | }
65 | }
66 | };
67 |
68 |
69 |
70 |
71 | export default class Transforms extends React.Component {
72 | constructor(props) {
73 | super(props);
74 |
75 | var transform = null;
76 | if(props.transform) transform = CSSParser.transform(props.transform);
77 |
78 | this.state = {
79 | transform
80 | };
81 |
82 | window.addEventListener('resize', ::this.handleResize);
83 | }
84 |
85 | componentWillMount() {}
86 |
87 | componentDidMount() {
88 | this.layout();
89 | this.paint();
90 | }
91 |
92 | componentWillUnmount() {}
93 |
94 | componentWillReceiveProps(nextProps) {
95 | var state = {};
96 | if(this.props.transform !== nextProps.transform)
97 | {
98 | state.transform = nextProps.transform? CSSParser.transform(nextProps.transform) : null;
99 | }
100 |
101 | if(this.props.reference !== nextProps.reference)
102 | {
103 | state.reference = nextProps.reference || null;
104 | }
105 |
106 | this.setState(state);
107 | }
108 |
109 | componentWillUpdate(nextProps, nextState) {
110 |
111 | }
112 |
113 | componentDidUpdate(prevProps, prevState) {
114 | this.paint();
115 | }
116 |
117 | handleResize() {
118 | this.layout();
119 | this.paint();
120 | }
121 |
122 | layout() {
123 | var rect = this.canvas.getBoundingClientRect();
124 | this.canvas.width = rect.width * window.devicePixelRatio;
125 | this.canvas.height = rect.height * window.devicePixelRatio;
126 | this.context = this.canvas.getContext('2d');
127 | }
128 |
129 | // sandwich(stack) {
130 | // return stack.reduceRight((b, a) => mat4.multiply(b, a, b), mat4.create());
131 |
132 | /*return stack.reduce((m, op) => {
133 | switch(op[0])
134 | {
135 | case 'translate': mat4.translate(m, m, op[1]); break;
136 | case 'scale': mat4.scale(m, m, op[1]); break;
137 | case 'rotate': mat4.rotate(m, m, op[2], op[1]); break;
138 | }
139 | return m;
140 | }, mat4.create());*/
141 | // }
142 |
143 | paint() {
144 | var origin = this.canvas.getBoundingClientRect();
145 | var reference = this.state.reference;
146 | var ctx = this.context;
147 |
148 | ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
149 |
150 | if(!reference || !this.state.transform) return;
151 |
152 | var styles = getComputedStyle(reference);
153 | CSSParser.base = {
154 | width: parseFloat(styles.width),
155 | height: parseFloat(styles.height),
156 | fontSize: parseFloat(styles.fontSize)
157 | };
158 |
159 | var base = {
160 | top: reference.offsetTop,
161 | left: reference.offsetLeft,
162 | width: parseFloat(styles.width),
163 | height: parseFloat(styles.height)
164 | };
165 |
166 |
167 | ctx.save();
168 |
169 | if(window.devicePixelRatio !== 1) ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
170 |
171 | // var stack = [];
172 | var matrix = mat4.create();
173 | var pos = vec3.create();
174 | var scl = vec3.fromValues(1, 1, 1);
175 | var skew = vec3.create();
176 | var perspective = null; // vec4.create();
177 | var rot = quat.create();
178 | var euler = vec3.create();
179 |
180 |
181 | // Origin of DOM layout
182 | var boxModelOrigin = vec3.fromValues(base.left - origin.left, base.top - origin.top, 0);
183 | // stack.push(mat4.fromTranslation(mat4.create(), boxModelOrigin)); // ['translate', boxModelOrigin]);
184 | mat4.translate(matrix, matrix, boxModelOrigin);
185 |
186 |
187 | // Origin of transform
188 | var transformOrigin = vec3.fromValues(base.width * 0.5, base.height * 0.5, 0); // assuming default for now, which is center
189 | // stack.push(mat4.fromTranslation(mat4.create(), transformOrigin)); // ['translate', transformOrigin]);
190 | mat4.translate(matrix, matrix, transformOrigin);
191 |
192 |
193 | // Export components
194 | // matrix = this.sandwich(stack);
195 | // mat4.getTranslation(pos, matrix);
196 | // mat4.getScaling(scl, matrix);
197 | // mat4.getRotation(rot, matrix);
198 | mat4.decompose(matrix, pos, scl, skew, perspective, rot);
199 | quat.getEuler(euler, rot);
200 |
201 |
202 | /// Style defaults
203 | ctx.lineWidth = 1;
204 | ctx.fillStyle = 'hsl(215, 100%, 50%)';
205 | ctx.strokeStyle = 'hsl(215, 100%, 50%)';
206 |
207 | /// Origin widget
208 | ctx.beginPath();
209 | ctx.arc(pos[0], pos[1], 5, 0, Math.PI*2);
210 | ctx.closePath();
211 | ctx.stroke();
212 |
213 |
214 | /// Draw pre-transform box
215 | var radius = CSSParser.value(styles.borderRadius);
216 | ctx.save();
217 | ctx.beginPath();
218 | ctx.translate(pos[0], pos[1]);
219 | ctx.roundedRect(base.width * -.5, base.height * -.5, base.width, base.height, radius);
220 | ctx.closePath();
221 | ctx.strokeStyle = 'hsla(215, 100%, 50%, 0.1)';
222 | ctx.stroke();
223 | ctx.restore();
224 |
225 |
226 |
227 | this.state.transform.map(fn => {
228 | switch(fn.name) {
229 | case 'matrix':
230 | return { name, type: 'matrix', m: fn.values };
231 |
232 | case 'matrix3d':
233 | return { name, type: 'matrix', m: fn.values };
234 |
235 | case 'translate':
236 | var x = CSSParser.value(fn.values[0]);
237 | var y = fn.values[1]? CSSParser.value(fn.values[1]) : 0;
238 | return { name, type: 'translate', x, y };
239 |
240 | case 'translateX':
241 | var x = CSSParser.value(fn.values[0]);
242 | return { name, type: 'translate', x, y: 0 };
243 |
244 | case 'translateY':
245 | var y = CSSParser.value(fn.values[0]);
246 | return { name, type: 'translate', x: 0, y };
247 |
248 | case 'translateZ':
249 | var z = CSSParser.value(fn.values[0]);
250 | return { name, type: 'translate', x: 0, y: 0, z };
251 |
252 | case 'translate3d':
253 | var x = CSSParser.value(fn.values[0]);
254 | var y = CSSParser.value(fn.values[1]);
255 | var z = CSSParser.value(fn.values[2]);
256 | return { name, type: 'translate', x, y, z };
257 |
258 | case 'scale':
259 | if(fn.values[1] === undefined)
260 | {
261 | var scale = fn.values[0];
262 | return { name, type: 'scale', x: scale, y: scale };
263 | }
264 | else
265 | {
266 | var x = fn.values[0];
267 | var y = fn.values[1];
268 | return { name, type: 'scale', x, y };
269 | }
270 | case 'scaleX':
271 | var x = fn.values[0];
272 | var y = 1;
273 | return { name, type: 'scale', x, y };
274 |
275 | case 'scaleY':
276 | var x = 1;
277 | var y = fn.values[0];
278 | return { name, type: 'scale', x, y };
279 |
280 | case 'scaleZ':
281 | var x = 1;
282 | var y = 1;
283 | var z = fn.values[0];
284 | return { name, type: 'scale', x, y, z };
285 |
286 | case 'scale3d':
287 | var x = fn.values[0];
288 | var y = fn.values[1];
289 | var z = fn.values[2];
290 | return { name, type: 'scale', x, y, z };
291 |
292 | case 'rotate':
293 | case 'rotateZ':
294 | var a = CSSParser.value(fn.values[0]);
295 | return { name, type: 'rotate', x: 0, y: 0, z: 1, a };
296 |
297 | case 'rotateX':
298 | var a = CSSParser.value(fn.values[0]);
299 | return { name, type: 'rotate', x: 1, y: 0, z: 0, a };
300 |
301 | case 'rotateY':
302 | var a = CSSParser.value(fn.values[0]);
303 | return { name, type: 'rotate', x: 0, y: 1, z: 0, a };
304 |
305 | case 'rotate3d':
306 | var x = CSSParser.value(fn.values[0]);
307 | var y = CSSParser.value(fn.values[1]);
308 | var z = CSSParser.value(fn.values[2]);
309 | var a = CSSParser.value(fn.values[3]);
310 | return { name, type: 'rotate', x, y, z, a };
311 |
312 | case 'skew':
313 | var x = CSSParser.value(fn.values[0]);
314 | var y = fn.values[1]? CSSParser.value(fn.values[1]) : 0;
315 | return { name, type: 'skew', x, y };
316 |
317 | case 'skewX':
318 | var x = CSSParser.value(fn.values[0]);
319 | return { name, type: 'skew', x, y: 0 };
320 |
321 | case 'skewY':
322 | var y = CSSParser.value(fn.values[0]);
323 | return { name, type: 'skew', x: 0, y };
324 |
325 | case 'perspective':
326 | var v = CSSParser.value(fn.values[0]);
327 | return { name, type: 'perspective', v };
328 |
329 | default: return null; // Invalid fn
330 | }
331 |
332 | return null;
333 | }).forEach(fn => {
334 | if(!fn) return;
335 |
336 | ctx.save();
337 |
338 | switch(fn.type) {
339 | case 'translate':
340 | var a = vec3.clone(pos);
341 | var v = vec3.fromValues(fn.x, fn.y, 0);
342 | vec3.multiply(v, v, scl);
343 | vec3.transformQuat(v, v, rot);
344 | var b = vec3.add(vec3.create(), pos, v);
345 | var delta = vec3.subtract(vec3.create(), b, a);
346 | var dir = vec3.normalize(vec3.create(), delta);
347 |
348 | /*var a = new Vector(pos[0], pos[1]).clone;
349 | var v = new Vector(fn.x, fn.y).scl(scl).rot(rot);
350 | var b = pos.clone.add(v);
351 | var delta = b.clone.sub(a);
352 | var dist = delta.mag;
353 | var dir = delta.clone.div(dist);
354 | var angle = delta.angle;*/
355 |
356 | ctx.save();
357 | ctx.beginPath();
358 |
359 | ctx.translate(b[0], b[1]);
360 | ctx.rotate(euler[2]);
361 | ctx.rect(-3, -3, 6, 6);
362 | ctx.closePath();
363 | ctx.fill();
364 | ctx.restore();
365 |
366 | var offset = vec3.scale(vec3.create(), dir, 6);
367 | vec3.add(a, a, offset);
368 | vec3.subtract(b, b, offset);
369 |
370 | // var offset = dir.clone.scl(6);
371 | // a.add(offset);
372 | // b.sub(offset);
373 |
374 | ctx.beginPath();
375 |
376 | ctx.dashedLine(
377 | Math.round(a[0]) - .5, Math.round(a[1]) - .5,
378 | Math.round(b[0]) - .5, Math.round(b[1]) - .5,
379 | 5, 10
380 | );
381 |
382 | ctx.closePath();
383 |
384 | ctx.stroke();
385 |
386 | break;
387 | case 'scale':
388 | ctx.save();
389 | ctx.beginPath();
390 |
391 | ctx.translate(pos[0], pos[1]);
392 | ctx.rotate(euler[2]);
393 |
394 | if(fn.x !== 1)
395 | {
396 | // ctx.moveTo(-18, 4);
397 | // ctx.lineTo(-18, -4);
398 | //
399 | // ctx.moveTo(18, 4);
400 | // ctx.lineTo(18, -4);
401 |
402 | ctx.clearRect(-18, -3, 14, 6);
403 | ctx.clearRect(4, -3, 14, 6);
404 |
405 | if(fn.x > 1)
406 | {
407 | ctx.moveTo(-6, 0);
408 | ctx.lineTo(-12, 0);
409 |
410 | ctx.moveTo(-13, 2);
411 | ctx.lineTo(-16, 0);
412 | ctx.lineTo(-13, -2);
413 |
414 | ctx.moveTo(6, 0);
415 | ctx.lineTo(12, 0);
416 |
417 | ctx.moveTo(13, 2);
418 | ctx.lineTo(16, 0);
419 | ctx.lineTo(13, -2);
420 | }
421 | else
422 | {
423 | ctx.moveTo(-10, 0);
424 | ctx.lineTo(-16, 0);
425 |
426 | ctx.moveTo(-9, 2);
427 | ctx.lineTo(-6, 0);
428 | ctx.lineTo(-9, -2);
429 |
430 | ctx.moveTo(10, 0);
431 | ctx.lineTo(16, 0);
432 |
433 | ctx.moveTo(9, 2);
434 | ctx.lineTo(6, 0);
435 | ctx.lineTo(9, -2);
436 | }
437 | }
438 |
439 | if(fn.y !== 1)
440 | {
441 | // ctx.moveTo(4, -18);
442 | // ctx.lineTo(-4, -18);
443 | //
444 | // ctx.moveTo(4, 18);
445 | // ctx.lineTo(-4, 18);
446 |
447 | ctx.clearRect(-3, -18, 6, 14);
448 | ctx.clearRect(-3, 4, 6, 14);
449 |
450 | if(fn.y > 1)
451 | {
452 | ctx.moveTo(0, -6);
453 | ctx.lineTo(0, -12);
454 |
455 | ctx.moveTo(2, -13);
456 | ctx.lineTo(0, -16);
457 | ctx.lineTo(-2, -13);
458 |
459 | ctx.moveTo(0, 6);
460 | ctx.lineTo(0, 12);
461 |
462 | ctx.moveTo(2, 13);
463 | ctx.lineTo(0, 16);
464 | ctx.lineTo(-2, 13);
465 |
466 | }
467 | else
468 | {
469 | ctx.moveTo(0, -10);
470 | ctx.lineTo(0, -16);
471 |
472 | ctx.moveTo(2, -9);
473 | ctx.lineTo(0, -6);
474 | ctx.lineTo(-2, -9);
475 |
476 | ctx.moveTo(0, 10);
477 | ctx.lineTo(0, 16);
478 |
479 | ctx.moveTo(2, 9);
480 | ctx.lineTo(0, 6);
481 | ctx.lineTo(-2, 9);
482 | }
483 | }
484 |
485 | ctx.stroke();
486 | ctx.restore();
487 |
488 |
489 | // ctx.beginPath();
490 | // ctx.arc(pos[0], pos[1], 1, 0, Math.PI*2);
491 | // ctx.closePath();
492 | // ctx.fill();
493 | break;
494 | case 'rotate':
495 | ctx.save();
496 | ctx.beginPath();
497 | ctx.translate(pos[0], pos[1]);
498 | ctx.rotate(euler[2]);
499 |
500 | if(Math.abs(fn.a) < (Math.PI/180 * 15))
501 | {
502 | var start = Math.PI/2;
503 | var end = Math.PI/2 + fn.a;
504 |
505 | ctx.arc(0, 0, 40, start, end, fn.a < 0);
506 | }
507 |
508 | else
509 | {
510 | ctx.clearRect(-1, 33, 3, 47 - 33);
511 | ctx.moveTo(0, 35);
512 | ctx.lineTo(0, 45);
513 |
514 | ctx.moveTo(fn.a > 0? -2 : 2, 40);
515 | var start = Math.PI/2 + (Math.PI/180 * (fn.a > 0? 3 : -3));
516 | var end = Math.PI/2 + (fn.a + (Math.PI/180 * (fn.a > 0? -5 : 5)));
517 |
518 | ctx.arc(0, 0, 40, start, end, fn.a < 0);
519 |
520 | ctx.rotate(fn.a);
521 |
522 |
523 | if(fn.a > 0)
524 | {
525 | ctx.moveTo(5, 35);
526 | ctx.lineTo(0, 40);
527 | ctx.lineTo(5, 45);
528 | }
529 | else if(fn.a < 0)
530 | {
531 | ctx.moveTo(-5, 35);
532 | ctx.lineTo(0, 40);
533 | ctx.lineTo(-5, 45);
534 | }
535 | }
536 |
537 | // for(var iter = 2; iter < 96; iter += iter)
538 | // {
539 | // ctx.rotate(fn.a/iter);
540 | // ctx.moveTo(0, 37);
541 | // ctx.lineTo(0, 38);
542 | // ctx.moveTo(0, 42);
543 | // ctx.lineTo(0, 43);
544 | // }
545 |
546 | // ctx.rotate(fn.a/2);
547 | //
548 | // ctx.moveTo(0, 37);
549 | // ctx.lineTo(0, 38);
550 | // ctx.moveTo(0, 42);
551 | // ctx.lineTo(0, 43);
552 |
553 | ctx.stroke();
554 | ctx.restore();
555 | break;
556 | case 'skew': break;
557 | case 'matrix': break;
558 | case 'perspective': break;
559 | }
560 |
561 | ctx.restore();
562 |
563 | switch(fn.type) {
564 | case 'translate':
565 | // Apply transform
566 | var t = vec3.fromValues(fn.x, fn.y, 0);
567 | // stack.push(mat4.fromTranslation(mat4.create(), t)); // ['translate', t]);
568 | mat4.translate(matrix, matrix, vec3.fromValues(fn.x, fn.y, 0));
569 |
570 | // Export components
571 | // matrix = this.sandwich(stack);
572 | // mat4.getTranslation(pos, matrix);
573 | // mat4.getScaling(scl, matrix);
574 | // mat4.getRotation(rot, matrix);
575 | mat4.decompose(matrix, pos, scl, skew, perspective, rot);
576 | quat.getEuler(euler, rot);
577 |
578 |
579 | // var v = new Vector(fn.x, fn.y).scl(scl).rot(rot);
580 | // pos.add(v);
581 | break;
582 | case 'scale':
583 | // Apply transform
584 | var s = vec3.fromValues(fn.x, fn.y, fn.z || 1);
585 | // stack.push(mat4.fromScaling(mat4.create(), s)); // ['scale', s]);
586 | mat4.scale(matrix, matrix, s);
587 |
588 | // Export components
589 | // matrix = this.sandwich(stack);
590 | // mat4.getTranslation(pos, matrix);
591 | // mat4.getScaling(scl, matrix);
592 | // mat4.getRotation(rot, matrix);
593 | mat4.decompose(matrix, pos, scl, skew, perspective, rot);
594 | quat.getEuler(euler, rot);
595 |
596 | // scl.scl(fn.x, fn.y);
597 | break;
598 | case 'rotate':
599 | // Apply transform
600 | var axis = vec3.fromValues(fn.x, fn.y, fn.z);
601 | var angle = fn.a;
602 | // stack.push(mat4.fromRotation(mat4.create(), angle, axis)); // ['rotate', axis, angle]);
603 | mat4.rotate(matrix, matrix, angle, axis);
604 |
605 | // Export components
606 | // matrix = this.sandwich(stack);
607 | // mat4.getTranslation(pos, matrix);
608 | // mat4.getScaling(scl, matrix);
609 | // mat4.getRotation(rot, matrix);
610 | mat4.decompose(matrix, pos, scl, skew, perspective, rot);
611 | quat.getEuler(euler, rot);
612 |
613 |
614 | // if(fn.z === 1) rot += fn.a;
615 | break;
616 | case 'skew':
617 | // ctx.transform(1, Math.tan(fn.y), Math.tan(fn.x), 1, 0, 0);
618 | break;
619 | case 'matrix':
620 | // if(fn.m.length === 6) ctx.transform.apply(ctx, fn.m);
621 | // else ctx.transform.apply(ctx, fn.m.filter((c, i) => i < 12 && i%4 < 2))
622 | break;
623 | case 'perspective':
624 | // ctx.transform() is only a transform matrix, need to figure out how to apply a projection matrix
625 | break;
626 | }
627 | });
628 |
629 | if(reference instanceof HTMLElement)
630 | {
631 | var radius = CSSParser.value(styles.borderRadius);
632 |
633 | ctx.save();
634 | // ctx.translate(pos[0], pos[1]);
635 | // ctx.rotate(euler[2]);
636 | ctx.resetTransform();
637 | var cssMatrix = mat4.getCSSMatrix2d([], matrix);
638 | ctx.setTransform.apply(ctx, cssMatrix);
639 | // ctx.setTransform(cssMatrix[0], cssMatrix[1], cssMatrix[4], cssMatrix[5], cssMatrix[12], cssMatrix[13]);
640 |
641 | var w = base.width;
642 | var h = base.height;
643 |
644 | ctx.beginPath();
645 | ctx.roundedRect(w * -.5, h * -.5, w, h, radius);
646 | ctx.closePath();
647 | ctx.stroke();
648 |
649 | // ctx.beginPath();
650 | // ctx.arc(w * .5, h * .5, 3, 0, Math.PI*2);
651 | // ctx.closePath();
652 | // ctx.stroke();
653 | //
654 | // ctx.beginPath();
655 | // ctx.arc(w * -.5, h * .5, 3, 0, Math.PI*2);
656 | // ctx.closePath();
657 | // ctx.stroke();
658 | //
659 | // ctx.beginPath();
660 | // ctx.arc(w * -.5, h * -.5, 3, 0, Math.PI*2);
661 | // ctx.closePath();
662 | // ctx.stroke();
663 | //
664 | // ctx.beginPath();
665 | // ctx.arc(w * .5, h * -.5, 3, 0, Math.PI*2);
666 | // ctx.closePath();
667 | // ctx.stroke();
668 |
669 | ctx.restore();
670 | }
671 |
672 | else
673 | {
674 | ctx.beginPath();
675 | ctx.rect(0, 0, base.width, base.height);
676 | ctx.closePath();
677 | ctx.stroke();
678 | }
679 |
680 |
681 | ctx.restore();
682 | }
683 |
684 | render() {
685 | return (
686 |