node.
126 | */
127 | artworkToXmlNode_(artwork, destinationNode, xmlDoc) {
128 | destinationNode.setAttributeNS(XMLNS_NS, 'xmlns:android', ANDROID_NS);
129 | destinationNode.setAttributeNS(ANDROID_NS, 'android:width', `${artwork.width}dp`);
130 | destinationNode.setAttributeNS(ANDROID_NS, 'android:height', `${artwork.height}dp`);
131 | destinationNode.setAttributeNS(ANDROID_NS, 'android:viewportWidth', `${artwork.width}`);
132 | destinationNode.setAttributeNS(ANDROID_NS, 'android:viewportHeight', `${artwork.height}`);
133 | conditionalAttr_(destinationNode, 'android:alpha', artwork.alpha, 1);
134 |
135 | artwork.walk((layer, parentNode) => {
136 | if (layer instanceof Artwork) {
137 | return parentNode;
138 |
139 | } else if (layer instanceof PathLayer) {
140 | let node = xmlDoc.createElement('path');
141 | conditionalAttr_(node, 'android:name', layer.id);
142 | conditionalAttr_(node, 'android:pathData', layer.pathData.pathString);
143 | conditionalAttr_(node, 'android:fillColor', layer.fillColor, '');
144 | conditionalAttr_(node, 'android:fillAlpha', layer.fillAlpha, 1);
145 | conditionalAttr_(node, 'android:strokeColor', layer.strokeColor, '');
146 | conditionalAttr_(node, 'android:strokeAlpha', layer.strokeAlpha, 1);
147 | conditionalAttr_(node, 'android:strokeWidth', layer.strokeWidth, 0);
148 | conditionalAttr_(node, 'android:trimPathStart', layer.trimPathStart, 0);
149 | conditionalAttr_(node, 'android:trimPathEnd', layer.trimPathEnd, 1);
150 | conditionalAttr_(node, 'android:trimPathOffset', layer.trimPathOffset, 0);
151 | conditionalAttr_(node, 'android:strokeLineCap', layer.strokeLinecap, DefaultValues.LINECAP);
152 | conditionalAttr_(node, 'android:strokeLineJoin', layer.strokeLinejoin,
153 | DefaultValues.LINEJOIN);
154 | conditionalAttr_(node, 'android:strokeMiterLimit', layer.strokeMiterLimit,
155 | DefaultValues.MITER_LIMIT);
156 | parentNode.appendChild(node);
157 | return parentNode;
158 |
159 | } else if (layer instanceof MaskLayer) {
160 | let node = xmlDoc.createElement('clip-path');
161 | conditionalAttr_(node, 'android:name', layer.id);
162 | conditionalAttr_(node, 'android:pathData', layer.pathData.pathString);
163 | parentNode.appendChild(node);
164 | return parentNode;
165 |
166 | } else if (layer instanceof LayerGroup) {
167 | let node = xmlDoc.createElement('group');
168 | conditionalAttr_(node, 'android:name', layer.id);
169 | conditionalAttr_(node, 'android:pivotX', layer.pivotX, 0);
170 | conditionalAttr_(node, 'android:pivotY', layer.pivotY, 0);
171 | conditionalAttr_(node, 'android:translateX', layer.translateX, 0);
172 | conditionalAttr_(node, 'android:translateY', layer.translateY, 0);
173 | conditionalAttr_(node, 'android:scaleX', layer.scaleX, 1);
174 | conditionalAttr_(node, 'android:scaleY', layer.scaleY, 1);
175 | conditionalAttr_(node, 'android:rotation', layer.rotation, 0);
176 | parentNode.appendChild(node);
177 | return node;
178 | }
179 | }, destinationNode);
180 | },
181 | };
182 |
--------------------------------------------------------------------------------
/app/scripts/ColorUtil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {default as tinycolor} from 'tinycolor2';
18 |
19 | const BRIGHTNESS_THRESHOLD = 130; // for isColorDark
20 |
21 |
22 | export const ColorUtil = {
23 | parseAndroidColor(val) {
24 | val = (val || '').replace(/^\s*#?|\s*$/g, '');
25 | let dict = {a:255};
26 |
27 | if (val.length == 3) {
28 | dict.r = parseInt(val.substring(0, 1), 16) * 17;
29 | dict.g = parseInt(val.substring(1, 2), 16) * 17;
30 | dict.b = parseInt(val.substring(2, 3), 16) * 17;
31 | } else if (val.length == 4) {
32 | dict.a = parseInt(val.substring(0, 1), 16) * 17;
33 | dict.r = parseInt(val.substring(1, 2), 16) * 17;
34 | dict.g = parseInt(val.substring(2, 3), 16) * 17;
35 | dict.b = parseInt(val.substring(3, 4), 16) * 17;
36 | } else if (val.length == 6) {
37 | dict.r = parseInt(val.substring(0, 2), 16);
38 | dict.g = parseInt(val.substring(2, 4), 16);
39 | dict.b = parseInt(val.substring(4, 6), 16);
40 | } else if (val.length == 8) {
41 | dict.a = parseInt(val.substring(0, 2), 16);
42 | dict.r = parseInt(val.substring(2, 4), 16);
43 | dict.g = parseInt(val.substring(4, 6), 16);
44 | dict.b = parseInt(val.substring(6, 8), 16);
45 | } else {
46 | return null;
47 | }
48 |
49 | return (isNaN(dict.r) || isNaN(dict.g) || isNaN(dict.b) || isNaN(dict.a))
50 | ? null
51 | : dict;
52 | },
53 |
54 | toAndroidString(dict) {
55 | let str = '#';
56 | if (dict.a != 255) {
57 | str += ((dict.a < 16) ? '0' : '') + dict.a.toString(16);
58 | }
59 |
60 | str += ((dict.r < 16) ? '0' : '') + dict.r.toString(16)
61 | + ((dict.g < 16) ? '0' : '') + dict.g.toString(16)
62 | + ((dict.b < 16) ? '0' : '') + dict.b.toString(16);
63 | return str;
64 | },
65 |
66 | svgToAndroidColor(color) {
67 | if (color == 'none') {
68 | return null;
69 | }
70 | color = tinycolor(color);
71 | let colorHex = color.toHex();
72 | let alphaHex = color.toHex8().substr(6);
73 | return '#' + (alphaHex != 'ff' ? alphaHex : '') + colorHex;
74 | },
75 |
76 | androidToCssColor(androidColor, multAlpha) {
77 | multAlpha = (multAlpha === undefined) ? 1 : multAlpha;
78 | if (!androidColor) {
79 | return 'transparent';
80 | }
81 |
82 | let d = ColorUtil.parseAndroidColor(androidColor);
83 | return `rgba(${d.r},${d.g},${d.b},${(d.a * multAlpha / 255).toFixed(2)})`;
84 | },
85 |
86 | isAndroidColorDark(androidColor) {
87 | if (!androidColor) {
88 | return false;
89 | }
90 |
91 | let d = ColorUtil.parseAndroidColor(androidColor);
92 | return ((30 * d.r + 59 * d.g + 11 * d.b) / 100) <= BRIGHTNESS_THRESHOLD;
93 | }
94 | };
95 |
--------------------------------------------------------------------------------
/app/scripts/DragHelper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const DRAG_SLOP = 4; // pixels
18 |
19 |
20 | export class DragHelper {
21 | constructor(opts) {
22 | opts = opts || {};
23 |
24 | this.direction_ = opts.direction || 'both';
25 | this.downX_ = opts.downEvent.clientX;
26 | this.downY_ = opts.downEvent.clientY;
27 | this.skipSlopCheck_ = !!opts.skipSlopCheck;
28 |
29 | this.onBeginDrag_ = opts.onBeginDrag || (() => {});
30 | this.onDrag_ = opts.onDrag || (() => {});
31 | this.onDrop_ = opts.onDrop || (() => {});
32 |
33 | this.dragging_ = false;
34 | this.draggingScrim_ = null;
35 |
36 | this.draggingCursor = opts.draggingCursor || 'grabbing';
37 |
38 | let mouseMoveHandler_ = event => {
39 | if (!this.dragging_ && this.shouldBeginDragging_(event)) {
40 | this.dragging_ = true;
41 | this.draggingScrim_ = this.buildDraggingScrim_().appendTo(document.body);
42 | this.draggingCursor = this.draggingCursor_;
43 | this.onBeginDrag_(event);
44 | }
45 |
46 | if (this.dragging_) {
47 | this.onDrag_(event, {
48 | x: event.clientX - this.downX_,
49 | y: event.clientY - this.downY_
50 | });
51 | }
52 | };
53 |
54 | let mouseUpHandler_ = event => {
55 | $(window)
56 | .off('mousemove', mouseMoveHandler_)
57 | .off('mouseup', mouseUpHandler_);
58 | if (this.dragging_) {
59 | this.onDrag_(event, {
60 | x: event.clientX - this.downX_,
61 | y: event.clientY - this.downY_
62 | });
63 |
64 | this.onDrop_();
65 |
66 | this.draggingScrim_.remove();
67 | this.draggingScrim_ = null;
68 | this.dragging_ = false;
69 |
70 | event.stopPropagation();
71 | event.preventDefault();
72 | return false;
73 | }
74 | };
75 |
76 | $(window)
77 | .on('mousemove', mouseMoveHandler_)
78 | .on('mouseup', mouseUpHandler_);
79 | }
80 |
81 | shouldBeginDragging_(mouseMoveEvent) {
82 | if (this.skipSlopCheck_) {
83 | return true;
84 | }
85 |
86 | let begin = false;
87 | if (this.direction_ == 'both' || this.direction_ == 'horizontal') {
88 | begin = begin || (Math.abs(mouseMoveEvent.clientX - this.downX_) > DRAG_SLOP);
89 | }
90 | if (this.direction_ == 'both' || this.direction_ == 'vertical') {
91 | begin = begin || (Math.abs(mouseMoveEvent.clientY - this.downY_) > DRAG_SLOP);
92 | }
93 | return begin;
94 | }
95 |
96 | set draggingCursor(cursor) {
97 | if (cursor == 'grabbing') {
98 | cursor = `-webkit-${cursor}`;
99 | }
100 |
101 | this.draggingCursor_ = cursor;
102 | if (this.draggingScrim_) {
103 | this.draggingScrim_.css({cursor});
104 | }
105 | }
106 |
107 | buildDraggingScrim_() {
108 | return $('')
109 | .css({
110 | position: 'fixed',
111 | left: 0,
112 | top: 0,
113 | right: 0,
114 | bottom: 0,
115 | zIndex: 9999
116 | });
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/app/scripts/ElementResizeWatcher.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Based on http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
18 |
19 | export class ElementResizeWatcher {
20 | constructor(element, listener) {
21 | this.element_ = $(element);
22 |
23 | // create resize listener
24 | let rafHandle;
25 |
26 | this.onResize_ = event => {
27 | var el = event.target || event.srcElement;
28 | if (rafHandle) {
29 | el.cancelAnimationFrame(rafHandle);
30 | }
31 |
32 | rafHandle = el.requestAnimationFrame(() => listener());
33 | };
34 |
35 | // add listener
36 | if (getComputedStyle(this.element_.get(0)).position == 'static') {
37 | this.element_.css({position: 'relative'});
38 | }
39 |
40 | this.proxyElement_ = $('')
41 | .css({
42 | display: 'block',
43 | position: 'absolute',
44 | left: 0,
45 | top: 0,
46 | width: '100%',
47 | height: '100%',
48 | overflow: 'hidden',
49 | pointerEvents: 'none',
50 | zIndex: -1
51 | })
52 | .attr('type', 'text/html')
53 | .attr('data', 'about:blank')
54 | .on('load', () => {
55 | this.proxyDefaultView_ = this.proxyElement_.get(0).contentDocument.defaultView;
56 | this.proxyDefaultView_.addEventListener('resize', this.onResize_);
57 | })
58 | .appendTo(this.element_);
59 | }
60 |
61 | destroy() {
62 | if (this.proxyDefaultView_) {
63 | this.proxyDefaultView_.removeEventListener('resize', this.onResize_);
64 | }
65 |
66 | this.proxyElement_.remove();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/scripts/MathUtil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export const MathUtil = {
18 | progress(val, min, max) {
19 | return MathUtil.constrain((val - min) / (max - min), 0, 1);
20 | },
21 |
22 | constrain(val, min, max) {
23 | if (val < min) {
24 | return min;
25 | } else if (val > max) {
26 | return max;
27 | } else {
28 | return val;
29 | }
30 | },
31 |
32 | interpolate(start, end, f) {
33 | return start + (end - start) * f;
34 | },
35 |
36 | dist(x1, y1, x2, y2) {
37 | return Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/app/scripts/ModelUtil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export const ModelUtil = {
18 | getOrderedAnimationBlocksByLayerIdAndProperty(animation) {
19 | let animationBlocksByLayerId = {};
20 |
21 | animation.blocks.forEach(block => {
22 | let blocksByProperty = animationBlocksByLayerId[block.layerId];
23 | if (!blocksByProperty) {
24 | blocksByProperty = {};
25 | animationBlocksByLayerId[block.layerId] = blocksByProperty;
26 | }
27 |
28 | blocksByProperty[block.propertyName] = blocksByProperty[block.propertyName] || [];
29 | blocksByProperty[block.propertyName].push(block);
30 | });
31 |
32 | for (let layerId in animationBlocksByLayerId) {
33 | let blocksByProperty = animationBlocksByLayerId[layerId];
34 | for (let propertyName in blocksByProperty) {
35 | blocksByProperty[propertyName].sort((a, b) => a.startTime - b.startTime);
36 | }
37 | }
38 |
39 | return animationBlocksByLayerId;
40 | },
41 |
42 | getUniqueId(opts) {
43 | opts = opts || {};
44 | opts.prefix = opts.prefix || '';
45 | opts.objectById = opts.objectById || (() => null);
46 | opts.targetObject = opts.targetObject || null;
47 |
48 | let n = 0;
49 | let id_ = () => opts.prefix + (n ? `_${n}` : '');
50 | while (true) {
51 | let o = opts.objectById(id_());
52 | if (!o || o == opts.targetObject) {
53 | break;
54 | }
55 |
56 | ++n;
57 | }
58 |
59 | return id_();
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/app/scripts/RenderUtil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const IDENTITY_TRANSFORM_MATRIX = [1, 0, 0, 1, 0, 0];
18 |
19 | export const RenderUtil = {
20 | transformMatrixForLayer(layer) {
21 | let cosR = Math.cos(layer.rotation * Math.PI / 180);
22 | let sinR = Math.sin(layer.rotation * Math.PI / 180);
23 |
24 | // first negative pivot, then scale, rotate, translate, and pivot
25 | // notes:
26 | // translate: [1, 0, 0, 1, x, y]
27 | // scale: [sx, 0, 0, sy, 0, 0]
28 | // rotate: [cos, sin, -sin, cos, 0, 0]
29 |
30 | return [
31 | cosR * layer.scaleX,
32 | sinR * layer.scaleX,
33 | -sinR * layer.scaleY,
34 | cosR * layer.scaleY,
35 | (layer.pivotX + layer.translateX)
36 | - cosR * layer.scaleX * layer.pivotX
37 | + sinR * layer.scaleY * layer.pivotY,
38 | (layer.pivotY + layer.translateY)
39 | - cosR * layer.scaleY * layer.pivotY
40 | - sinR * layer.scaleX * layer.pivotX
41 | ];
42 | },
43 |
44 | flattenTransforms(transforms) {
45 | return (transforms || []).reduce(
46 | (m, transform) => transformMatrix_(transform, m),
47 | IDENTITY_TRANSFORM_MATRIX);
48 | },
49 |
50 | transformPoint(matrices, p) {
51 | if (!matrices || !matrices.length) {
52 | return Object.assign({}, p);
53 | }
54 |
55 | return matrices.reduce((p, m) => ({
56 | // [a c e] [p.x]
57 | // [b d f] * [p.y]
58 | // [0 0 1] [ 1 ]
59 | x: m[0] * p.x + m[2] * p.y + m[4],
60 | y: m[1] * p.x + m[3] * p.y + m[5]
61 | }), p);
62 | },
63 |
64 | computeStrokeWidthMultiplier(transformMatrix) {
65 | // from getMatrixScale in
66 | // https://android.googlesource.com/platform/frameworks/base/+/master/libs/hwui/VectorDrawable.cpp
67 |
68 | // Given unit vectors A = (0, 1) and B = (1, 0).
69 | // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
70 | // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
71 | // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
72 | // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
73 | //
74 | // For non-skew case, which is most of the cases, matrix scale is computing exactly the
75 | // scale on x and y axis, and take the minimal of these two.
76 | // For skew case, an unit square will mapped to a parallelogram. And this function will
77 | // return the minimal height of the 2 bases.
78 |
79 | // first remove translate elements from matrix
80 | transformMatrix[4] = transformMatrix[5] = 0;
81 |
82 | let vecA = RenderUtil.transformPoint([transformMatrix], {x:0, y:1});
83 | let vecB = RenderUtil.transformPoint([transformMatrix], {x:1, y:0});
84 | let scaleX = Math.hypot(vecA.x, vecA.y);
85 | let scaleY = Math.hypot(vecB.x, vecB.y);
86 | let crossProduct = vecA.y * vecB.x - vecA.x * vecB.y; // vector cross product
87 | let maxScale = Math.max(scaleX, scaleY);
88 | let matrixScale = 0;
89 | if (maxScale > 0) {
90 | matrixScale = Math.abs(crossProduct) / maxScale;
91 | }
92 | return matrixScale;
93 | }
94 | };
95 |
96 |
97 | // formula generated w/ wolfram alpha
98 | // returns the product of 2D transformation matrices s and t
99 |
100 | function transformMatrix_(s, t) {
101 | return [t[0] * s[0] + t[1] * s[2],
102 | t[0] * s[1] + t[1] * s[3],
103 | s[0] * t[2] + s[2] * t[3],
104 | s[1] * t[2] + t[3] * s[3],
105 | s[0] * t[4] + s[4] + s[2] * t[5],
106 | s[1] * t[4] + s[3] * t[5] + s[5]];
107 | }
108 |
--------------------------------------------------------------------------------
/app/scripts/SvgLoader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Artwork, DefaultValues} from './model';
18 | import {IdProperty} from './model/properties';
19 | import {ColorUtil} from './ColorUtil';
20 | import {SvgPathData} from './SvgPathData';
21 | import {ModelUtil} from './ModelUtil';
22 |
23 |
24 | export const SvgLoader = {
25 | loadArtworkFromSvgString(svgString) {
26 | let parser = new DOMParser();
27 | let doc = parser.parseFromString(svgString, 'image/svg+xml');
28 |
29 | let usedIds = {};
30 |
31 | let nodeToLayerData_ = (node, context) => {
32 | if (!node) {
33 | return null;
34 | }
35 |
36 | if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.COMMENT_NODE) {
37 | return null;
38 | }
39 |
40 | let makeFinalNodeId_ = typeIdPrefix => {
41 | let finalId = ModelUtil.getUniqueId({
42 | prefix: IdProperty.sanitize(node.id || typeIdPrefix),
43 | objectById: id => usedIds[id],
44 | });
45 | usedIds[finalId] = true;
46 | return finalId;
47 | };
48 |
49 | let layerData = {};
50 |
51 | let simpleAttr_ = (nodeAttr, contextAttr) => {
52 | if (node.attributes[nodeAttr]) {
53 | context[contextAttr] = node.attributes[nodeAttr].value;
54 | }
55 | };
56 |
57 | // set attributes
58 | simpleAttr_('stroke', 'strokeColor');
59 | simpleAttr_('stroke-width', 'strokeWidth');
60 | simpleAttr_('stroke-linecap', 'strokeLinecap');
61 | simpleAttr_('stroke-linejoin', 'strokeLinejoin');
62 | simpleAttr_('stroke-miterlimit', 'strokeMiterLimit');
63 | simpleAttr_('stroke-opacity', 'strokeAlpha');
64 | simpleAttr_('fill', 'fillColor');
65 | simpleAttr_('fill-opacity', 'fillAlpha');
66 |
67 | // add transforms
68 |
69 | if (node.transform) {
70 | let transforms = Array.from(node.transform.baseVal);
71 | transforms.reverse();
72 | context.transforms = context.transforms ? context.transforms.slice() : [];
73 | context.transforms.splice(0, 0, ...transforms);
74 | }
75 |
76 | // see if this is a path
77 | let path;
78 | if (node instanceof SVGPathElement) {
79 | path = node.attributes.d.value;
80 |
81 | } else if (node instanceof SVGRectElement) {
82 | let l = lengthPx_(node.x),
83 | t = lengthPx_(node.y),
84 | r = l + lengthPx_(node.width),
85 | b = t + lengthPx_(node.height);
86 | // TODO: handle corner radii
87 | path = `M ${l},${t} ${r},${t} ${r},${b} ${l},${b} Z`;
88 |
89 | } else if (node instanceof SVGLineElement) {
90 | let x1 = lengthPx_(node.x1),
91 | y1 = lengthPx_(node.y1),
92 | x2 = lengthPx_(node.x2),
93 | y2 = lengthPx_(node.y2);
94 | path = `M ${x1},${y1} ${x2},${y2} Z`;
95 |
96 | } else if (node instanceof SVGPolygonElement || node instanceof SVGPolylineElement) {
97 | path = 'M ' + Array.from(node.points).map(pt => pt.x +',' + pt.y).join(' ');
98 | if (node instanceof SVGPolygonElement) {
99 | path += ' Z';
100 | }
101 |
102 | } else if (node instanceof SVGCircleElement) {
103 | let cx = lengthPx_(node.cx),
104 | cy = lengthPx_(node.cy),
105 | r = lengthPx_(node.r);
106 | path = `M ${cx},${cy-r} A ${r} ${r} 0 1 0 ${cx},${cy+r} A ${r} ${r} 0 1 0 ${cx},${cy-r} Z`;
107 |
108 | } else if (node instanceof SVGEllipseElement) {
109 | let cx = lengthPx_(node.cx),
110 | cy = lengthPx_(node.cy),
111 | rx = lengthPx_(node.rx),
112 | ry = lengthPx_(node.ry);
113 | path = `M ${cx},${cy-ry} A ${rx} ${ry} 0 1 0 ${cx},${cy+ry} ` +
114 | `A ${rx} ${ry} 0 1 0 ${cx},${cy-ry} Z`;
115 | }
116 |
117 | if (path) {
118 | // transform all points
119 | if (context.transforms && context.transforms.length) {
120 | let pathData = new SvgPathData(path);
121 | pathData.transform(context.transforms);
122 | path = pathData.pathString;
123 | }
124 |
125 | // create a path layer
126 | return Object.assign(layerData, {
127 | id: makeFinalNodeId_('path'),
128 | pathData: path,
129 | fillColor: ('fillColor' in context) ? ColorUtil.svgToAndroidColor(context.fillColor) : "#ff000000",
130 | fillAlpha: ('fillAlpha' in context) ? context.fillAlpha : 1,
131 | strokeColor: ('strokeColor' in context) ? ColorUtil.svgToAndroidColor(context.strokeColor) : null,
132 | strokeAlpha: ('strokeAlpha' in context) ? context.strokeAlpha : 1,
133 | strokeWidth: ('strokeWidth' in context) ? context.strokeWidth : 1,
134 | strokeLinecap: context.strokeLinecap || DefaultValues.LINECAP,
135 | strokeLinejoin: context.strokeLinejoin || DefaultValues.LINEJOIN,
136 | strokeMiterLimit: ('strokeMiterLimit' in context) ? context.strokeMiterLimit : DefaultValues.MITER_LIMIT,
137 | });
138 | }
139 |
140 | if (node.childNodes.length) {
141 | let layers = Array.from(node.childNodes)
142 | .map(child => nodeToLayerData_(child, Object.assign({}, context)))
143 | .filter(layer => !!layer);
144 | if (layers && layers.length) {
145 | // create a group (there are valid children)
146 | return Object.assign(layerData, {
147 | id: makeFinalNodeId_('group'),
148 | type: 'group',
149 | layers: layers
150 | });
151 | } else {
152 | return null;
153 | }
154 | }
155 | };
156 |
157 | let docElContext = {};
158 | let width = lengthPx_(doc.documentElement.width);
159 | let height = lengthPx_(doc.documentElement.height);
160 |
161 | if (doc.documentElement.viewBox) {
162 | width = doc.documentElement.viewBox.baseVal.width;
163 | height = doc.documentElement.viewBox.baseVal.height;
164 |
165 | // fake a translate transform for the viewbox
166 | docElContext.transforms = [
167 | {
168 | matrix: {
169 | a: 1,
170 | b: 0,
171 | c: 0,
172 | d: 1,
173 | e: -doc.documentElement.viewBox.baseVal.x,
174 | f: -doc.documentElement.viewBox.baseVal.y
175 | }
176 | }
177 | ];
178 | }
179 |
180 | let rootLayer = nodeToLayerData_(doc.documentElement, docElContext);
181 |
182 | let artwork = {
183 | width,
184 | height,
185 | layers: (rootLayer ? rootLayer.layers : null) || [],
186 | alpha: doc.documentElement.getAttribute('opacity') || 1,
187 | };
188 |
189 | return new Artwork(artwork);
190 | }
191 | };
192 |
193 |
194 | function lengthPx_(svgLength) {
195 | if (svgLength.baseVal) {
196 | svgLength = svgLength.baseVal;
197 | }
198 | svgLength.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX);
199 | return svgLength.valueInSpecifiedUnits;
200 | }
201 |
202 |
--------------------------------------------------------------------------------
/app/scripts/UiUtil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export const UiUtil = {
18 | waitForElementWidth_(el, timeout = 1000) {
19 | let start = Number(new Date());
20 | let $el = $(el);
21 | return new Promise((resolve, reject) => {
22 | let tryResolve_ = () => {
23 | if (Number(new Date()) - start > timeout) {
24 | reject();
25 | return;
26 | }
27 |
28 | let width = $el.width();
29 | if (width) {
30 | resolve(width);
31 | } else {
32 | setTimeout(() => tryResolve_(), 0);
33 | }
34 | };
35 |
36 | tryResolve_();
37 | });
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/app/scripts/VectorDrawableLoader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Artwork, DefaultValues} from './model';
18 | import {IdProperty} from './model/properties';
19 | import {ModelUtil} from './ModelUtil';
20 |
21 |
22 | export const VectorDrawableLoader = {
23 | loadArtworkFromXmlString(xmlString) {
24 | let parser = new DOMParser();
25 | let doc = parser.parseFromString(xmlString, 'application/xml');
26 |
27 | let usedIds = {};
28 |
29 | let nodeToLayerData_ = (node) => {
30 | if (!node) {
31 | return null;
32 | }
33 | if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.COMMENT_NODE) {
34 | return null;
35 | }
36 |
37 | let makeFinalNodeId_ = (node, typeIdPrefix) => {
38 | let name = node.getAttribute('android:name');
39 | let finalId = ModelUtil.getUniqueId({
40 | prefix: IdProperty.sanitize(name || typeIdPrefix),
41 | objectById: id => usedIds[id],
42 | });
43 | usedIds[finalId] = true;
44 | return finalId;
45 | };
46 |
47 | let layerData = {};
48 |
49 | if (node.tagName === 'path') {
50 | return Object.assign(layerData, {
51 | id: makeFinalNodeId_(node, 'path'),
52 | pathData: node.getAttribute('android:pathData') || null,
53 | fillColor: node.getAttribute('android:fillColor') || null,
54 | fillAlpha: node.getAttribute('android:fillAlpha') || 1,
55 | strokeColor: node.getAttribute('android:strokeColor') || null,
56 | strokeAlpha: node.getAttribute('android:strokeAlpha') || 1,
57 | strokeWidth: node.getAttribute('android:strokeWidth') || 0,
58 | strokeLinecap: node.getAttribute('android:strokeLineCap') || DefaultValues.LINECAP,
59 | strokeLinejoin: node.getAttribute('android:strokeLineJoin') || DefaultValues.LINEJOIN,
60 | strokeMiterLimit:
61 | node.getAttribute('android:strokeMiterLimit') || DefaultValues.MITER_LIMIT,
62 | trimPathStart: node.getAttribute('android:trimPathStart') || 0,
63 | trimPathEnd: node.getAttribute('android:trimPathEnd') || 1,
64 | trimPathOffset: node.getAttribute('android:trimPathOffset') || 0,
65 | });
66 | }
67 |
68 | if (node.childNodes.length) {
69 | let layers = Array.from(node.childNodes)
70 | .map(child => nodeToLayerData_(child))
71 | .filter(layer => !!layer);
72 | if (layers && layers.length) {
73 | // create a group (there are valid children)
74 | return Object.assign(layerData, {
75 | id: makeFinalNodeId_(node, 'group'),
76 | type: 'group',
77 | rotation: node.getAttribute('android:rotation') || 0,
78 | scaleX: node.getAttribute('android:scaleX') || 1,
79 | scaleY: node.getAttribute('android:scaleY') || 1,
80 | pivotX: node.getAttribute('android:pivotX') || 0,
81 | pivotY: node.getAttribute('android:pivotY') || 0,
82 | translateX: node.getAttribute('android:translateX') || 0,
83 | translateY: node.getAttribute('android:translateY') || 0,
84 | layers,
85 | });
86 | } else {
87 | return null;
88 | }
89 | }
90 | };
91 |
92 | let rootLayer = nodeToLayerData_(doc.documentElement);
93 | let id = IdProperty.sanitize(doc.documentElement.getAttribute('android:name') || 'vector');
94 | usedIds[id] = true;
95 | let width = doc.documentElement.getAttribute('android:viewportWidth');
96 | let height = doc.documentElement.getAttribute('android:viewportHeight');
97 | let alpha = doc.documentElement.getAttribute('android:alpha') || 1;
98 | let artwork = {
99 | id,
100 | width,
101 | height,
102 | layers: (rootLayer ? rootLayer.layers : null) || [],
103 | alpha,
104 | };
105 | return new Artwork(artwork);
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | angular.module('AVDStudio', ['ngMaterial', 'ngRoute'])
18 | .config(require('./materialtheme'))
19 | .config(require('./icons'))
20 | .config(require('./routes').routeConfig);
21 |
22 | // core app
23 | angular.module('AVDStudio').controller('AppCtrl', class AppCtrl {
24 | constructor($scope) {}
25 | });
26 |
27 | // all components
28 | require('../components/**/*.js', {mode: 'expand'});
29 |
30 | // all pages
31 | require('../pages/**/*.js', {mode: 'expand'});
32 |
--------------------------------------------------------------------------------
/app/scripts/icons.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | module.exports = function($mdIconProvider) {
18 | $mdIconProvider.iconSet('avdstudio', 'images/icons.svg');
19 | };
20 |
--------------------------------------------------------------------------------
/app/scripts/materialtheme.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | module.exports = function($mdThemingProvider) {
18 | $mdThemingProvider.theme('default')
19 | .primaryPalette('blue')
20 | .accentPalette('blue');
21 | $mdThemingProvider.theme('dark')
22 | .primaryPalette('blue')
23 | .accentPalette('blue')
24 | .dark();
25 | $mdThemingProvider.setDefaultTheme('default');
26 | };
--------------------------------------------------------------------------------
/app/scripts/model/Animation.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, IdProperty, NumberProperty} from './properties';
18 |
19 | import {AnimationBlock} from './AnimationBlock';
20 |
21 | /**
22 | * An animation represents a collection of layer property tweens for a given artwork.
23 | */
24 | @Property.register([
25 | new IdProperty('id'),
26 | new NumberProperty('duration', {min:100, max:60000}),
27 | ])
28 | export class Animation {
29 | constructor(obj = {}) {
30 | this.id = obj.id || null;
31 | this.blocks = (obj.blocks || []).map(obj => new AnimationBlock(obj));
32 | this.duration = obj.duration || 100;
33 | }
34 |
35 | get blocks() {
36 | return this.blocks_ || [];
37 | }
38 |
39 | set blocks(blocks) {
40 | this.blocks_ = blocks;
41 | this.blocks_.forEach(block => block.parent = this);
42 | }
43 |
44 | get typeString() {
45 | return 'animation';
46 | }
47 |
48 | get typeIdPrefix() {
49 | return 'anim';
50 | }
51 |
52 | get typeIcon() {
53 | return 'animation';
54 | }
55 |
56 | toJSON() {
57 | return {
58 | id: this.id,
59 | duration: this.duration,
60 | blocks: this.blocks.map(block => block.toJSON())
61 | };
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/scripts/model/AnimationBlock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {default as bezierEasing} from 'bezier-easing';
18 |
19 | import {SvgPathData} from '../SvgPathData';
20 | import {Property, StubProperty, NumberProperty, EnumProperty} from './properties';
21 |
22 | const FAST_OUT_SLOW_IN_EASING = bezierEasing(.4, 0, .2, 1);
23 | const FAST_OUT_LINEAR_IN_EASING = bezierEasing(.4, 0, 1, 1);
24 | const LINEAR_OUT_SLOW_IN_EASING = bezierEasing(0, 0, .2, 1);
25 |
26 | const ENUM_INTERPOLATOR_OPTIONS = [
27 | {
28 | value: 'ACCELERATE_DECELERATE',
29 | label: 'Accelerate/decelerate',
30 | androidRef: '@android:anim/accelerate_decelerate_interpolator',
31 | interpolate: f => Math.cos((f + 1) * Math.PI) / 2.0 + 0.5,
32 | },
33 | {
34 | value: 'ACCELERATE',
35 | label: 'Accelerate',
36 | androidRef: '@android:anim/accelerate_interpolator',
37 | interpolate: f => f * f,
38 | },
39 | {
40 | value: 'DECELERATE',
41 | label: 'Decelerate',
42 | androidRef: '@android:anim/decelerate_interpolator',
43 | interpolate: f => (1 - (1 - f) * (1 - f)),
44 | },
45 | {
46 | value: 'ANTICIPATE',
47 | label: 'Anticipate',
48 | androidRef: '@android:anim/anticipate_interpolator',
49 | interpolate: f => f * f * ((2 + 1) * f - 2),
50 | },
51 | {
52 | value: 'LINEAR',
53 | label: 'Linear',
54 | androidRef: '@android:anim/linear_interpolator',
55 | interpolate: f => f,
56 | },
57 | {
58 | value: 'OVERSHOOT',
59 | label: 'Overshoot',
60 | androidRef: '@android:anim/overshoot_interpolator',
61 | interpolate: f => (f - 1) * (f - 1) * ((2 + 1) * (f - 1) + 2) + 1
62 | },
63 | {
64 | value: 'FAST_OUT_SLOW_IN',
65 | label: 'Fast out, slow in',
66 | androidRef: '@android:interpolator/fast_out_slow_in',
67 | interpolate: f => FAST_OUT_SLOW_IN_EASING(f)
68 | },
69 | {
70 | value: 'FAST_OUT_LINEAR_IN',
71 | label: 'Fast out, linear in',
72 | androidRef: '@android:interpolator/fast_out_linear_in',
73 | interpolate: f => FAST_OUT_LINEAR_IN_EASING(f)
74 | },
75 | {
76 | value: 'LINEAR_OUT_SLOW_IN',
77 | label: 'Linear out, slow in',
78 | androidRef: '@android:interpolator/linear_out_slow_in',
79 | interpolate: f => LINEAR_OUT_SLOW_IN_EASING(f)
80 | },
81 | //BOUNCE: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/animation/BounceInterpolator.java
82 | //ANTICIPATE_OVERSHOOT: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/animation/AnticipateOvershootInterpolator.java
83 | //PATH: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/animation/PathInterpolator.java
84 | ];
85 |
86 | /**
87 | * An animation block is an individual layer property tween (property animation).
88 | */
89 | @Property.register([
90 | new StubProperty('fromValue'),
91 | new StubProperty('toValue'),
92 | new NumberProperty('startTime', {min:0, integer:true}),
93 | new NumberProperty('endTime', {min:0, integer:true}),
94 | new EnumProperty('interpolator', ENUM_INTERPOLATOR_OPTIONS, {storeEntireOption:true}),
95 | ])
96 | export class AnimationBlock {
97 | constructor(obj = {}) {
98 | this.layerId = obj.layerId || null;
99 | this.propertyName = obj.propertyName || null;
100 | let isPathData = (this.propertyName == 'pathData');
101 | if ('fromValue' in obj) {
102 | this.fromValue = isPathData ? new SvgPathData(obj.fromValue) : obj.fromValue;
103 | }
104 | this.toValue = isPathData ? new SvgPathData(obj.toValue) : obj.toValue;
105 | this.startTime = obj.startTime || 0;
106 | this.endTime = obj.endTime || 0;
107 | if (this.startTime > this.endTime) {
108 | let tmp = this.endTime;
109 | this.endTime = this.startTime;
110 | this.startTime = tmp;
111 | }
112 | this.interpolator = obj.interpolator || 'ACCELERATE_DECELERATE';
113 | }
114 |
115 | get typeString() {
116 | return 'block';
117 | }
118 |
119 | get typeIdPrefix() {
120 | return 'block';
121 | }
122 |
123 | get typeIcon() {
124 | return 'animation_block';
125 | }
126 |
127 | toJSON() {
128 | return {
129 | layerId: this.layerId,
130 | propertyName: this.propertyName,
131 | fromValue: valueToJson_(this.fromValue),
132 | toValue: valueToJson_(this.toValue),
133 | startTime: this.startTime,
134 | endTime: this.endTime,
135 | interpolator: this.interpolator.value,
136 | };
137 | }
138 | }
139 |
140 | function valueToJson_(val) {
141 | if (typeof val == 'object' && 'toJSON' in val) {
142 | return val.toJSON();
143 | }
144 |
145 | return val;
146 | }
147 |
--------------------------------------------------------------------------------
/app/scripts/model/Artwork.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, IdProperty, ColorProperty, NumberProperty, FractionProperty} from './properties';
18 | import {BaseLayer} from './BaseLayer';
19 | import {LayerGroup} from './LayerGroup';
20 |
21 | /**
22 | * An artwork is the root layer group for a vector, defined mostly by
23 | * a width, height, and its children.
24 | */
25 | @Property.register([
26 | new IdProperty('id'),
27 | new ColorProperty('canvasColor'),
28 | new NumberProperty('width', {min:4, max:1024, integer:true}),
29 | new NumberProperty('height', {min:4, max:1024, integer:true}),
30 | new FractionProperty('alpha', {animatable: true}),
31 | ], {reset:true})
32 | export class Artwork extends LayerGroup {
33 | constructor(obj = {}, opts = {}) {
34 | super(obj, opts);
35 | this.id = this.id || this.typeIdPrefix;
36 | this.canvasColor = obj.fillColor || null;
37 | this.width = obj.width || 100;
38 | this.height = obj.height || 100;
39 | this.alpha = obj.alpha || 1;
40 | }
41 |
42 | computeBounds() {
43 | return { l: 0, t: 0, r: this.width, b: this.height };
44 | }
45 |
46 | get typeString() {
47 | return 'artwork';
48 | }
49 |
50 | get typeIdPrefix() {
51 | return 'vector';
52 | }
53 |
54 | get typeIcon() {
55 | return 'artwork';
56 | }
57 |
58 | findLayerById(id) {
59 | if (this.id === id) {
60 | return this;
61 | }
62 | return super.findLayerById(id);
63 | }
64 |
65 | toJSON() {
66 | return {
67 | id: this.id,
68 | canvasColor: this.canvasColor,
69 | width: this.width,
70 | height: this.height,
71 | alpha: this.alpha,
72 | layers: this.layers.map(layer => layer.toJSON())
73 | };
74 | }
75 | }
76 |
77 | BaseLayer.LAYER_CLASSES_BY_TYPE['artwork'] = Artwork;
78 |
--------------------------------------------------------------------------------
/app/scripts/model/BaseLayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, IdProperty} from './properties';
18 |
19 | /**
20 | * Base class for any node in the tree, including path layers, layer groups, and artworks.
21 | */
22 | @Property.register([
23 | new IdProperty('id')
24 | ])
25 | export class BaseLayer {
26 | constructor(obj = {}, opts = {}) {
27 | this.parent = null;
28 | this.id = obj.id || null;
29 | if (opts && opts.linkSelectedState) {
30 | this.selectedStateLinkedObj_ = obj;
31 | }
32 |
33 | // meta
34 | this.visible = ('visible' in obj) ? obj.visible : true;
35 | this.expanded = true;
36 | }
37 |
38 | get selected() {
39 | return this.selectedStateLinkedObj_
40 | ? this.selectedStateLinkedObj_.selected_
41 | : this.selected_;
42 | }
43 |
44 | computeBounds() {
45 | return null;
46 | }
47 |
48 | getSibling_(offs) {
49 | if (!this.parent || !this.parent.layers) {
50 | return null;
51 | }
52 |
53 | let index = this.parent.layers.indexOf(this);
54 | if (index < 0) {
55 | return null;
56 | }
57 |
58 | index += offs;
59 | if (index < 0 || index >= this.parent.layers.length) {
60 | return null;
61 | }
62 |
63 | return this.parent.layers[index];
64 | }
65 |
66 | get previousSibling() {
67 | return this.getSibling_(-1);
68 | }
69 |
70 | get nextSibling() {
71 | return this.getSibling_(1);
72 | }
73 |
74 | remove() {
75 | if (!this.parent || !this.parent.layers) {
76 | return;
77 | }
78 |
79 | let index = this.parent.layers.indexOf(this);
80 | if (index >= 0) {
81 | this.parent.layers.splice(index, 1);
82 | }
83 |
84 | this.parent = null;
85 | }
86 |
87 | walk(fn, context) {
88 | let visit_ = (layer, context) => {
89 | let childContext = fn(layer, context);
90 | if (layer.layers) {
91 | walkLayerGroup_(layer, childContext);
92 | }
93 | };
94 |
95 | let walkLayerGroup_ = (layerGroup, context) => {
96 | layerGroup.layers.forEach(layer => visit_(layer, context));
97 | };
98 |
99 | visit_(this, context);
100 | }
101 |
102 | toJSON() {
103 | return {
104 | id: this.id,
105 | type: this.typeString,
106 | visible: this.visible,
107 | };
108 | }
109 |
110 | static load(obj = {}, opts) {
111 | if (obj instanceof BaseLayer) {
112 | return new obj.constructor(obj, opts);
113 | }
114 |
115 | return new BaseLayer.LAYER_CLASSES_BY_TYPE[obj.type || 'path'](obj, opts);
116 | }
117 | }
118 |
119 | // filled in by derived classes
120 | BaseLayer.LAYER_CLASSES_BY_TYPE = {};
121 |
--------------------------------------------------------------------------------
/app/scripts/model/LayerGroup.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, NumberProperty} from './properties';
18 | import {BaseLayer} from './BaseLayer';
19 |
20 | /**
21 | * A group ('folder') containing other layers.
22 | */
23 | @Property.register([
24 | new NumberProperty('rotation', {animatable: true}),
25 | new NumberProperty('scaleX', {animatable: true}),
26 | new NumberProperty('scaleY', {animatable: true}),
27 | new NumberProperty('pivotX', {animatable: true}),
28 | new NumberProperty('pivotY', {animatable: true}),
29 | new NumberProperty('translateX', {animatable: true}),
30 | new NumberProperty('translateY', {animatable: true}),
31 | ])
32 | export class LayerGroup extends BaseLayer {
33 | constructor(obj = {}, opts = {}) {
34 | super(obj, opts);
35 | this.layers = (obj.layers || []).map(obj => BaseLayer.load(obj, opts));
36 | this.rotation = obj.rotation || 0;
37 | this.scaleX = ('scaleX' in obj) ? obj.scaleX : 1;
38 | this.scaleY = ('scaleY' in obj) ? obj.scaleY : 1;
39 | this.pivotX = obj.pivotX || 0;
40 | this.pivotY = obj.pivotY || 0;
41 | this.translateX = obj.translateX || 0;
42 | this.translateY = obj.translateY || 0;
43 |
44 | // meta
45 | this.expanded = ('expanded' in obj) ? obj.expanded : true;
46 | }
47 |
48 | computeBounds() {
49 | let bounds = null;
50 | this.layers.forEach(child => {
51 | let childBounds = child.computeBounds();
52 | if (!childBounds) {
53 | return;
54 | }
55 |
56 | if (!bounds) {
57 | bounds = Object.assign({}, childBounds);
58 | } else {
59 | bounds.l = Math.min(childBounds.l, bounds.l);
60 | bounds.t = Math.min(childBounds.t, bounds.t);
61 | bounds.r = Math.max(childBounds.r, bounds.r);
62 | bounds.b = Math.max(childBounds.b, bounds.b);
63 | }
64 | });
65 | return bounds;
66 | }
67 |
68 | get layers() {
69 | return this.layers_ || [];
70 | }
71 |
72 | set layers(layers) {
73 | this.layers_ = layers;
74 | this.layers_.forEach(layer => layer.parent = this);
75 | }
76 |
77 | get typeString() {
78 | return 'group';
79 | }
80 |
81 | get typeIdPrefix() {
82 | return 'group';
83 | }
84 |
85 | get typeIcon() {
86 | return 'layer_group';
87 | }
88 |
89 | findLayerById(id) {
90 | for (let i = 0; i < this.layers.length; i++) {
91 | let layer = this.layers[i];
92 | if (layer.id === id) {
93 | return layer;
94 | } else if (layer.findLayerById) {
95 | layer = layer.findLayerById(id);
96 | if (layer) {
97 | return layer;
98 | }
99 | }
100 | }
101 |
102 | return null;
103 | }
104 |
105 | toJSON() {
106 | return Object.assign(super.toJSON(), {
107 | rotation: this.rotation,
108 | scaleX: this.scaleX,
109 | scaleY: this.scaleY,
110 | pivotX: this.pivotX,
111 | pivotY: this.pivotY,
112 | translateX: this.translateX,
113 | translateY: this.translateY,
114 | layers: this.layers.map(layer => layer.toJSON()),
115 | expanded: this.expanded,
116 | });
117 | }
118 | }
119 |
120 | BaseLayer.LAYER_CLASSES_BY_TYPE['group'] = LayerGroup;
121 |
--------------------------------------------------------------------------------
/app/scripts/model/MaskLayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, PathDataProperty} from './properties';
18 | import {BaseLayer} from './BaseLayer';
19 |
20 | /**
21 | * A mask layer (mask defined by a path) that clips/masks layers that follow it
22 | * within its layer group.
23 | */
24 | @Property.register([
25 | new PathDataProperty('pathData', {animatable: true}),
26 | ])
27 | export class MaskLayer extends BaseLayer {
28 | constructor(obj = {}, opts = {}) {
29 | super(obj, opts);
30 | this.pathData = obj.pathData || '';
31 | }
32 |
33 | computeBounds() {
34 | return Object.assign({}, (this.pathData && this.pathData.bounds) ? this.pathData.bounds : null);
35 | }
36 |
37 | get typeString() {
38 | return 'mask';
39 | }
40 |
41 | get typeIdPrefix() {
42 | return 'mask';
43 | }
44 |
45 | get typeIcon() {
46 | return 'mask_layer';
47 | }
48 |
49 | toJSON() {
50 | return Object.assign(super.toJSON(), {
51 | pathData: this.pathData.pathString
52 | });
53 | }
54 | }
55 |
56 | BaseLayer.LAYER_CLASSES_BY_TYPE['mask'] = MaskLayer;
57 |
--------------------------------------------------------------------------------
/app/scripts/model/PathLayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property, PathDataProperty, NumberProperty, ColorProperty,
18 | FractionProperty, EnumProperty} from './properties';
19 | import {BaseLayer} from './BaseLayer';
20 |
21 | export const DefaultValues = {
22 | LINECAP: 'butt',
23 | LINEJOIN: 'miter',
24 | MITER_LIMIT: 4,
25 | };
26 |
27 | const ENUM_LINECAP_OPTIONS = [
28 | {value: 'butt', label: 'Butt'},
29 | {value: 'square', label: 'Square'},
30 | {value: 'round', label: 'Round'},
31 | ];
32 |
33 | const ENUM_LINEJOIN_OPTIONS = [
34 | {value: 'miter', label: 'Miter'},
35 | {value: 'round', label: 'Round'},
36 | {value: 'bevel', label: 'Bevel'},
37 | ];
38 |
39 | /**
40 | * A path layer, which is the main building block for visible content in a vector
41 | * artwork.
42 | */
43 | @Property.register([
44 | new PathDataProperty('pathData', {animatable: true}),
45 | new ColorProperty('fillColor', {animatable: true}),
46 | new FractionProperty('fillAlpha', {animatable: true}),
47 | new ColorProperty('strokeColor', {animatable: true}),
48 | new FractionProperty('strokeAlpha', {animatable: true}),
49 | new NumberProperty('strokeWidth', {min:0, animatable: true}),
50 | new EnumProperty('strokeLinecap', ENUM_LINECAP_OPTIONS),
51 | new EnumProperty('strokeLinejoin', ENUM_LINEJOIN_OPTIONS),
52 | new NumberProperty('strokeMiterLimit', {min:1}),
53 | new FractionProperty('trimPathStart', {animatable: true}),
54 | new FractionProperty('trimPathEnd', {animatable: true}),
55 | new FractionProperty('trimPathOffset', {animatable: true}),
56 | ])
57 | export class PathLayer extends BaseLayer {
58 | constructor(obj = {}, opts = {}) {
59 | super(obj, opts);
60 | this.pathData = obj.pathData || '';
61 | this.fillColor = obj.fillColor || null;
62 | this.fillAlpha = ('fillAlpha' in obj) ? obj.fillAlpha : 1;
63 | this.strokeColor = obj.strokeColor || '';
64 | this.strokeAlpha = ('strokeAlpha' in obj) ? obj.strokeAlpha : 1;
65 | this.strokeWidth = obj.strokeWidth || 0;
66 | this.strokeLinecap = obj.strokeLinecap || DefaultValues.LINECAP;
67 | this.strokeLinejoin = obj.strokeLinejoin || DefaultValues.LINEJOIN;
68 | this.strokeMiterLimit = obj.strokeMiterLimit || DefaultValues.MITER_LIMIT;
69 | this.trimPathStart = obj.trimPathStart || 0;
70 | this.trimPathEnd = ('trimPathEnd' in obj && typeof obj.trimPathEnd == 'number')
71 | ? obj.trimPathEnd : 1;
72 | this.trimPathOffset = obj.trimPathOffset || 0;
73 | }
74 |
75 | computeBounds() {
76 | return Object.assign({}, (this.pathData && this.pathData.bounds) ? this.pathData.bounds : null);
77 | }
78 |
79 | get typeString() {
80 | return 'path';
81 | }
82 |
83 | get typeIdPrefix() {
84 | return 'path';
85 | }
86 |
87 | get typeIcon() {
88 | return 'path_layer';
89 | }
90 |
91 | toJSON() {
92 | return Object.assign(super.toJSON(), {
93 | pathData: this.pathData.pathString,
94 | fillColor: this.fillColor,
95 | fillAlpha: this.fillAlpha,
96 | strokeColor: this.strokeColor,
97 | strokeAlpha: this.strokeAlpha,
98 | strokeWidth: this.strokeWidth,
99 | strokeLinecap: this.strokeLinecap,
100 | strokeLinejoin: this.strokeLinejoin,
101 | strokeMiterLimit: this.strokeMiterLimit,
102 | trimPathStart: this.trimPathStart,
103 | trimPathEnd: this.trimPathEnd,
104 | trimPathOffset: this.trimPathOffset
105 | });
106 | }
107 | }
108 |
109 | BaseLayer.LAYER_CLASSES_BY_TYPE['path'] = PathLayer;
110 |
--------------------------------------------------------------------------------
/app/scripts/model/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export {Animation} from './Animation';
18 | export {AnimationBlock} from './AnimationBlock';
19 | export {Artwork} from './Artwork';
20 | export {BaseLayer} from './BaseLayer';
21 | export {LayerGroup} from './LayerGroup';
22 | export {MaskLayer} from './MaskLayer';
23 | export {PathLayer, DefaultValues} from './PathLayer';
24 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/ColorProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {ColorUtil} from 'ColorUtil';
18 | import {MathUtil} from 'MathUtil';
19 |
20 | import {Property} from './Property';
21 |
22 | export class ColorProperty extends Property {
23 | interpolateValue(start, end, f) {
24 | start = ColorUtil.parseAndroidColor(start);
25 | end = ColorUtil.parseAndroidColor(end);
26 | return ColorUtil.toAndroidString({
27 | r: MathUtil.constrain(Math.round(Property.simpleInterpolate(start.r, end.r, f)), 0, 255),
28 | g: MathUtil.constrain(Math.round(Property.simpleInterpolate(start.g, end.g, f)), 0, 255),
29 | b: MathUtil.constrain(Math.round(Property.simpleInterpolate(start.b, end.b, f)), 0, 255),
30 | a: MathUtil.constrain(Math.round(Property.simpleInterpolate(start.a, end.a, f)), 0, 255)
31 | });
32 | }
33 |
34 | trySetEditedValue(obj, propertyName, value) {
35 | if (!value) {
36 | obj[propertyName] = null;
37 | return;
38 | }
39 |
40 | let processedValue = ColorUtil.parseAndroidColor(value);
41 | if (!processedValue) {
42 | processedValue = ColorUtil.parseAndroidColor(ColorUtil.svgToAndroidColor(value));
43 | }
44 |
45 | obj[propertyName] = ColorUtil.toAndroidString(processedValue);
46 | }
47 |
48 | get animatorValueType() {
49 | return 'colorType';
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/EnumProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property} from './Property';
18 |
19 | export class EnumProperty extends Property {
20 | constructor(name, options, config = {}) {
21 | super(name, config);
22 | this.optionsByValue_ = {};
23 | this.options_ = (options || []).map(option => {
24 | let newOption = {};
25 | if (typeof option === 'string') {
26 | newOption = {
27 | value: option,
28 | label: option
29 | };
30 | option = newOption;
31 | }
32 |
33 | if (!('label' in option)) {
34 | option.label = option.value;
35 | }
36 |
37 | this.optionsByValue_[option.value] = option;
38 | return option;
39 | });
40 |
41 | config = config || {};
42 | if (config.storeEntireOption) {
43 | this.storeEntireOption = config.storeEntireOption;
44 | }
45 | }
46 |
47 | getter_(obj, propertyName, value) {
48 | let backingPropertyName = `${propertyName}_`;
49 | return obj[backingPropertyName];
50 | }
51 |
52 | setter_(obj, propertyName, value) {
53 | let backingPropertyName = `${propertyName}_`;
54 |
55 | obj[backingPropertyName] = this.storeEntireOption
56 | ? this.getOptionForValue_(value)
57 | : this.getOptionForValue_(value).value;
58 | }
59 |
60 | getOptionForValue_(value) {
61 | if (!value) {
62 | return null;
63 | }
64 |
65 | if (typeof value === 'string') {
66 | return this.optionsByValue_[value];
67 | } else if ('value' in value) {
68 | return value;
69 | }
70 |
71 | return null;
72 | }
73 |
74 | displayValueForValue(value) {
75 | if (!value) {
76 | return '';
77 | }
78 |
79 | return this.getOptionForValue_(value).label;
80 | }
81 |
82 | get options() {
83 | return this.options_;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/FractionProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {NumberProperty} from './NumberProperty';
18 |
19 | export class FractionProperty extends NumberProperty {
20 | constructor(name, config = {}) {
21 | config.min = 0;
22 | config.max = 1;
23 | super(name, config);
24 | }
25 |
26 | get animatorValueType() {
27 | return 'floatType';
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/IdProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property} from './Property';
18 |
19 | export class IdProperty extends Property {
20 | trySetEditedValue(obj, propertyName, value) {
21 | obj[propertyName] = IdProperty.sanitize(value);
22 | }
23 |
24 | static sanitize(value) {
25 | value = (value || '')
26 | .toLowerCase()
27 | .replace(/^\s+|\s+$/g, '')
28 | .replace(/[\s-]+/g, '_')
29 | .replace(/[^\w_]+/g, '');
30 | return value;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/NumberProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property} from './Property';
18 |
19 | export class NumberProperty extends Property {
20 | constructor(name, config = {}) {
21 | super(name, config);
22 | this.config = config;
23 | }
24 |
25 | trySetEditedValue(obj, propertyName, value) {
26 | value = parseFloat(value);
27 | if (!isNaN(value)) {
28 | if ('min' in this.config) {
29 | value = Math.max(this.config.min, value);
30 | }
31 | if ('max' in this.config) {
32 | value = Math.min(this.config.max, value);
33 | }
34 | if (this.config.integer) {
35 | value = Math.floor(value);
36 | }
37 | obj[propertyName] = value;
38 | }
39 | }
40 |
41 | displayValueForValue(value) {
42 | if (typeof value === 'number') {
43 | return (Number.isInteger(value)
44 | ? value.toString()
45 | : Number(value.toFixed(3)).toString())
46 | .replace(/-/g, '\u2212');
47 | }
48 | return value;
49 | }
50 |
51 | setter_(obj, propertyName, value) {
52 | if (typeof value === 'string') {
53 | value = Number(value);
54 | }
55 |
56 | if (typeof value === 'number') {
57 | if (!isNaN(value)) {
58 | if ('min' in this.config) {
59 | value = Math.max(this.config.min, value);
60 | }
61 | if ('max' in this.config) {
62 | value = Math.min(this.config.max, value);
63 | }
64 | if (this.config.integer) {
65 | value = Math.floor(value);
66 | }
67 | }
68 | }
69 |
70 | let backingPropertyName = `${propertyName}_`;
71 | obj[backingPropertyName] = value;
72 | }
73 |
74 | interpolateValue(start, end, f) {
75 | return Property.simpleInterpolate(start, end, f);
76 | }
77 |
78 | get animatorValueType() {
79 | return 'floatType';
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/PathDataProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {SvgPathData} from 'SvgPathData';
18 |
19 | import {Property} from './Property';
20 |
21 | export class PathDataProperty extends Property {
22 | interpolateValue(start, end, f) {
23 | return SvgPathData.interpolate(start, end, f);
24 | }
25 |
26 | displayValueForValue(val) {
27 | return val.pathString;
28 | }
29 |
30 | getEditableValue(obj, propertyName) {
31 | return obj[propertyName] ? obj[propertyName].pathString : '';
32 | }
33 |
34 | trySetEditedValue(obj, propertyName, stringValue) {
35 | obj[propertyName] = new SvgPathData(stringValue);
36 | }
37 |
38 | getter_(obj, propertyName) {
39 | let backingPropertyName = `${propertyName}_`;
40 | return obj[backingPropertyName];
41 | }
42 |
43 | setter_(obj, propertyName, value) {
44 | let backingPropertyName = `${propertyName}_`;
45 | let pathData;
46 | if (!value || value instanceof SvgPathData) {
47 | pathData = value;
48 | } else {
49 | pathData = new SvgPathData(value);
50 | }
51 |
52 | obj[backingPropertyName] = pathData;
53 | }
54 |
55 | cloneValue(val) {
56 | return JSON.parse(JSON.stringify(val));
57 | }
58 |
59 | get animatorValueType() {
60 | return 'pathType';
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/Property.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export class Property {
18 | constructor(name, config = {}) {
19 | this.name = name;
20 | this.config = config;
21 | this.animatable = config.animatable;
22 | this.inspectable = config.inspectable;
23 | }
24 |
25 | interpolateValue(start, end, f) {
26 | return start;
27 | }
28 |
29 | getEditableValue(obj, propertyName) {
30 | return obj[propertyName];
31 | }
32 |
33 | trySetEditedValue(obj, propertyName, value) {
34 | obj[propertyName] = value;
35 | }
36 |
37 | getter_(obj, propertyName, value) {
38 | let backingPropertyName = `${propertyName}_`;
39 | return obj[backingPropertyName];
40 | }
41 |
42 | setter_(obj, propertyName, value) {
43 | let backingPropertyName = `${propertyName}_`;
44 | obj[backingPropertyName] = value;
45 | }
46 |
47 | displayValueForValue(val) {
48 | return val;
49 | }
50 |
51 | cloneValue(val) {
52 | return val;
53 | }
54 |
55 | static simpleInterpolate(start, end, f) {
56 | return start + (end - start) * f;
57 | }
58 |
59 | static register(props, {reset = false} = {}) {
60 | return function(cls) {
61 | props.forEach(prop => {
62 | if (!(prop instanceof StubProperty)) {
63 | Object.defineProperty(cls.prototype, prop.name, {
64 | get() {
65 | return prop.getter_(this, prop.name);
66 | },
67 | set(value) {
68 | prop.setter_(this, prop.name, value);
69 | }
70 | });
71 | }
72 | });
73 |
74 | let animatableProperties = {};
75 | let inspectableProperties = {};
76 |
77 | if (!reset) {
78 | Object.assign(animatableProperties, cls.prototype.animatableProperties);
79 | Object.assign(inspectableProperties, cls.prototype.inspectableProperties);
80 | }
81 |
82 | props.forEach(prop => {
83 | if (prop.animatable) {
84 | animatableProperties[prop.name] = prop;
85 | }
86 |
87 | if (!prop.inspectable) {
88 | inspectableProperties[prop.name] = prop;
89 | }
90 | });
91 |
92 | Object.defineProperty(cls.prototype, 'animatableProperties', {
93 | get: () => Object.assign({}, animatableProperties)
94 | });
95 |
96 | Object.defineProperty(cls.prototype, 'inspectableProperties', {
97 | get: () => Object.assign({}, inspectableProperties)
98 | });
99 | };
100 | }
101 | }
102 |
103 | export class StubProperty extends Property {}
104 |
105 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/StringProperty.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Property} from './Property';
18 |
19 | export class StringProperty extends Property {
20 | }
21 |
--------------------------------------------------------------------------------
/app/scripts/model/properties/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export {ColorProperty} from './ColorProperty';
18 | export {EnumProperty} from './EnumProperty';
19 | export {FractionProperty} from './FractionProperty';
20 | export {IdProperty} from './IdProperty';
21 | export {NumberProperty} from './NumberProperty';
22 | export {PathDataProperty} from './PathDataProperty';
23 | export {Property, StubProperty} from './Property';
24 | export {StringProperty} from './StringProperty';
25 |
--------------------------------------------------------------------------------
/app/scripts/routes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | module.exports.routeConfig = function($locationProvider, $routeProvider) {
18 | $locationProvider.html5Mode(true);
19 |
20 | $routeProvider
21 | .otherwise({
22 | templateUrl: 'pages/studio/studio.html'
23 | });
24 | };
25 |
26 | Object.assign(module.exports, {
27 | studio: () => `/`
28 | });
29 |
--------------------------------------------------------------------------------
/app/scripts/xmlserializer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Based on https://github.com/cburgmer/xmlserializer/blob/master/lib/serializer.js
18 | // Other options for pretty-printing:
19 | // - https://github.com/travisleithead/xmlserialization-polyfill
20 | // - https://github.com/prettydiff/prettydiff/blob/master/lib/markuppretty.js
21 | // - https://github.com/vkiryukhin/vkBeautify
22 |
23 | var removeInvalidCharacters = function (content) {
24 | // See http://www.w3.org/TR/xml/#NT-Char for valid XML 1.0 characters
25 | return content.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
26 | };
27 |
28 | var serializeAttributeValue = function (value) {
29 | return value
30 | .replace(/&/g, '&')
31 | .replace(//g, '>')
33 | .replace(/"/g, '"')
34 | .replace(/'/g, ''');
35 | };
36 |
37 | var serializeTextContent = function (content) {
38 | return content
39 | .replace(/&/g, '&')
40 | .replace(//g, '>');
42 | };
43 |
44 | var serializeAttribute = function (attr) {
45 | var value = attr.value;
46 |
47 | return attr.name + '="' + serializeAttributeValue(value) + '"';
48 | };
49 |
50 | var getTagName = function (node) {
51 | var tagName = node.tagName;
52 |
53 | // Aid in serializing of original HTML documents
54 | if (node.namespaceURI === 'http://www.w3.org/1999/xhtml') {
55 | tagName = tagName.toLowerCase();
56 | }
57 | return tagName;
58 | };
59 |
60 | var serializeNamespace = function (node, options) {
61 | var nodeHasXmlnsAttr = Array.prototype.map.call(node.attributes || node.attrs, function (attr) {
62 | return attr.name;
63 | })
64 | .indexOf('xmlns') >= 0;
65 | // Serialize the namespace as an xmlns attribute whenever the element
66 | // doesn't already have one and the inherited namespace does not match
67 | // the element's namespace.
68 | if (!nodeHasXmlnsAttr && node.namespaceURI &&
69 | (options.isRootNode/* ||
70 | node.namespaceURI !== node.parentNode.namespaceURI*/)) {
71 | return ' xmlns="' + node.namespaceURI + '"';
72 | } else {
73 | return '';
74 | }
75 | };
76 |
77 | var serializeChildren = function (node, options) {
78 | return Array.prototype.map.call(node.childNodes, function (childNode) {
79 | return nodeTreeToXHTML(childNode, options);
80 | }).join('');
81 | };
82 |
83 | var serializeTag = function (node, options) {
84 | var output = '';
85 | if (options.indent && options._indentLevel) {
86 | output += Array(options._indentLevel * options.indent + 1).join(' ');
87 | }
88 | output += '<' + getTagName(node);
89 | output += serializeNamespace(node, options.isRootNode);
90 |
91 | var attributes = node.attributes || node.attrs;
92 | Array.prototype.forEach.call(attributes, function (attr) {
93 | if (options.multiAttributeIndent && attributes.length > 1) {
94 | output += '\n';
95 | output += Array((options._indentLevel || 0) * options.indent + options.multiAttributeIndent + 1).join(' ');
96 | } else {
97 | output += ' ';
98 | }
99 | output += serializeAttribute(attr);
100 | });
101 |
102 | if (node.childNodes.length > 0) {
103 | output += '>';
104 | if (options.indent) {
105 | output += '\n';
106 | }
107 | options.isRootNode = false;
108 | options._indentLevel = (options._indentLevel || 0) + 1;
109 | output += serializeChildren(node, options);
110 | --options._indentLevel;
111 | if (options.indent && options._indentLevel) {
112 | output += Array(options._indentLevel * options.indent + 1).join(' ');
113 | }
114 | output += '' + getTagName(node) + '>';
115 | } else {
116 | output += '/>';
117 | }
118 | if (options.indent) {
119 | output += '\n';
120 | }
121 | return output;
122 | };
123 |
124 | var serializeText = function (node) {
125 | var text = node.nodeValue || node.value || '';
126 | return serializeTextContent(text);
127 | };
128 |
129 | var serializeComment = function (node) {
130 | return '';
134 | };
135 |
136 | var serializeCDATA = function (node) {
137 | return '';
138 | };
139 |
140 | var nodeTreeToXHTML = function (node, options) {
141 | if (node.nodeName === '#document' ||
142 | node.nodeName === '#document-fragment') {
143 | return serializeChildren(node, options);
144 | } else {
145 | if (node.tagName) {
146 | return serializeTag(node, options);
147 | } else if (node.nodeName === '#text') {
148 | return serializeText(node);
149 | } else if (node.nodeName === '#comment') {
150 | return serializeComment(node);
151 | } else if (node.nodeName === '#cdata-section') {
152 | return serializeCDATA(node);
153 | }
154 | }
155 | };
156 |
157 | exports.serializeToString = function (node, options) {
158 | options = options || {};
159 | options.rootNode = true;
160 | return removeInvalidCharacters(nodeTreeToXHTML(node, options));
161 | };
162 |
--------------------------------------------------------------------------------
/app/styles/angular-material-overrides.scss:
--------------------------------------------------------------------------------
1 | .md-button:not(.md-icon-button):not(.md-fab) {
2 | margin: 0;
3 | padding: 0 12px;
4 | }
5 |
6 | md-menu-content {
7 | padding: 4px 0;
8 | }
9 |
10 | md-menu-item {
11 | min-height: 32px;
12 | height: 32px;
13 |
14 | .md-button {
15 | line-height: 32px;
16 | min-height: 32px;
17 | font-size: 14px;
18 | }
19 | }
20 |
21 | md-icon {
22 | min-width: 0;
23 | min-height: 0;
24 | }
25 |
--------------------------------------------------------------------------------
/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | // libraries and such
2 | @import 'material-colors';
3 | @import 'material-shadows';
4 | @import 'material-icons';
5 | @import 'angular-material-overrides';
6 |
7 | // global styles
8 | @import 'variables';
9 |
10 | // core page layouts and common stuff
11 | @import 'root';
12 |
13 | // components
14 | @import '../components/**/*';
15 |
16 | // pages
17 | @import '../pages/**/*';
18 |
--------------------------------------------------------------------------------
/app/styles/globals.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidIconAnimator/336e461c7d771ebf459c07620e0f5fe6f4e0f7f2/app/styles/globals.scss
--------------------------------------------------------------------------------
/app/styles/material-colors.scss:
--------------------------------------------------------------------------------
1 | $material-colors: (
2 | 'red': (
3 | '50': #ffebee,
4 | '100': #ffcdd2,
5 | '200': #ef9a9a,
6 | '300': #e57373,
7 | '400': #ef5350,
8 | '500': #f44336,
9 | '600': #e53935,
10 | '700': #d32f2f,
11 | '800': #c62828,
12 | '900': #b71c1c,
13 | 'a100': #ff8a80,
14 | 'a200': #ff5252,
15 | 'a400': #ff1744,
16 | 'a700': #d50000
17 | ),
18 |
19 | 'pink': (
20 | '50': #fce4ec,
21 | '100': #f8bbd0,
22 | '200': #f48fb1,
23 | '300': #f06292,
24 | '400': #ec407a,
25 | '500': #e91e63,
26 | '600': #d81b60,
27 | '700': #c2185b,
28 | '800': #ad1457,
29 | '900': #880e4f,
30 | 'a100': #ff80ab,
31 | 'a200': #ff4081,
32 | 'a400': #f50057,
33 | 'a700': #c51162
34 | ),
35 |
36 | 'purple': (
37 | '50': #f3e5f5,
38 | '100': #e1bee7,
39 | '200': #ce93d8,
40 | '300': #ba68c8,
41 | '400': #ab47bc,
42 | '500': #9c27b0,
43 | '600': #8e24aa,
44 | '700': #7b1fa2,
45 | '800': #6a1b9a,
46 | '900': #4a148c,
47 | 'a100': #ea80fc,
48 | 'a200': #e040fb,
49 | 'a400': #d500f9,
50 | 'a700': #aa00ff
51 | ),
52 |
53 | 'deep-purple': (
54 | '50': #ede7f6,
55 | '100': #d1c4e9,
56 | '200': #b39ddb,
57 | '300': #9575cd,
58 | '400': #7e57c2,
59 | '500': #673ab7,
60 | '600': #5e35b1,
61 | '700': #512da8,
62 | '800': #4527a0,
63 | '900': #311b92,
64 | 'a100': #b388ff,
65 | 'a200': #7c4dff,
66 | 'a400': #651fff,
67 | 'a700': #6200ea
68 | ),
69 |
70 | 'indigo': (
71 | '50': #e8eaf6,
72 | '100': #c5cae9,
73 | '200': #9fa8da,
74 | '300': #7986cb,
75 | '400': #5c6bc0,
76 | '500': #3f51b5,
77 | '600': #3949ab,
78 | '700': #303f9f,
79 | '800': #283593,
80 | '900': #1a237e,
81 | 'a100': #8c9eff,
82 | 'a200': #536dfe,
83 | 'a400': #3d5afe,
84 | 'a700': #304ffe
85 | ),
86 |
87 | 'blue': (
88 | '50': #e3f2fd,
89 | '100': #bbdefb,
90 | '200': #90caf9,
91 | '300': #64b5f6,
92 | '400': #42a5f5,
93 | '500': #2196f3,
94 | '600': #1e88e5,
95 | '700': #1976d2,
96 | '800': #1565c0,
97 | '900': #0d47a1,
98 | 'a100': #82b1ff,
99 | 'a200': #448aff,
100 | 'a400': #2979ff,
101 | 'a700': #2962ff
102 | ),
103 |
104 | 'light-blue': (
105 | '50': #e1f5fe,
106 | '100': #b3e5fc,
107 | '200': #81d4fa,
108 | '300': #4fc3f7,
109 | '400': #29b6f6,
110 | '500': #03a9f4,
111 | '600': #039be5,
112 | '700': #0288d1,
113 | '800': #0277bd,
114 | '900': #01579b,
115 | 'a100': #80d8ff,
116 | 'a200': #40c4ff,
117 | 'a400': #00b0ff,
118 | 'a700': #0091ea
119 | ),
120 |
121 | 'cyan': (
122 | '50': #e0f7fa,
123 | '100': #b2ebf2,
124 | '200': #80deea,
125 | '300': #4dd0e1,
126 | '400': #26c6da,
127 | '500': #00bcd4,
128 | '600': #00acc1,
129 | '700': #0097a7,
130 | '800': #00838f,
131 | '900': #006064,
132 | 'a100': #84ffff,
133 | 'a200': #18ffff,
134 | 'a400': #00e5ff,
135 | 'a700': #00b8d4
136 | ),
137 |
138 | 'teal': (
139 | '50': #e0f2f1,
140 | '100': #b2dfdb,
141 | '200': #80cbc4,
142 | '300': #4db6ac,
143 | '400': #26a69a,
144 | '500': #009688,
145 | '600': #00897b,
146 | '700': #00796b,
147 | '800': #00695c,
148 | '900': #004d40,
149 | 'a100': #a7ffeb,
150 | 'a200': #64ffda,
151 | 'a400': #1de9b6,
152 | 'a700': #00bfa5
153 | ),
154 |
155 | 'green': (
156 | '50': #e8f5e9,
157 | '100': #c8e6c9,
158 | '200': #a5d6a7,
159 | '300': #81c784,
160 | '400': #66bb6a,
161 | '500': #4caf50,
162 | '600': #43a047,
163 | '700': #388e3c,
164 | '800': #2e7d32,
165 | '900': #1b5e20,
166 | 'a100': #b9f6ca,
167 | 'a200': #69f0ae,
168 | 'a400': #00e676,
169 | 'a700': #00c853
170 | ),
171 |
172 | 'light-green': (
173 | '50': #f1f8e9,
174 | '100': #dcedc8,
175 | '200': #c5e1a5,
176 | '300': #aed581,
177 | '400': #9ccc65,
178 | '500': #8bc34a,
179 | '600': #7cb342,
180 | '700': #689f38,
181 | '800': #558b2f,
182 | '900': #33691e,
183 | 'a100': #ccff90,
184 | 'a200': #b2ff59,
185 | 'a400': #76ff03,
186 | 'a700': #64dd17
187 | ),
188 |
189 | 'lime': (
190 | '50': #f9fbe7,
191 | '100': #f0f4c3,
192 | '200': #e6ee9c,
193 | '300': #dce775,
194 | '400': #d4e157,
195 | '500': #cddc39,
196 | '600': #c0ca33,
197 | '700': #afb42b,
198 | '800': #9e9d24,
199 | '900': #827717,
200 | 'a100': #f4ff81,
201 | 'a200': #eeff41,
202 | 'a400': #c6ff00,
203 | 'a700': #aeea00
204 | ),
205 |
206 | 'yellow': (
207 | '50': #fffde7,
208 | '100': #fff9c4,
209 | '200': #fff59d,
210 | '300': #fff176,
211 | '400': #ffee58,
212 | '500': #ffeb3b,
213 | '600': #fdd835,
214 | '700': #fbc02d,
215 | '800': #f9a825,
216 | '900': #f57f17,
217 | 'a100': #ffff8d,
218 | 'a200': #ffff00,
219 | 'a400': #ffea00,
220 | 'a700': #ffd600
221 | ),
222 |
223 | 'amber': (
224 | '50': #fff8e1,
225 | '100': #ffecb3,
226 | '200': #ffe082,
227 | '300': #ffd54f,
228 | '400': #ffca28,
229 | '500': #ffc107,
230 | '600': #ffb300,
231 | '700': #ffa000,
232 | '800': #ff8f00,
233 | '900': #ff6f00,
234 | 'a100': #ffe57f,
235 | 'a200': #ffd740,
236 | 'a400': #ffc400,
237 | 'a700': #ffab00
238 | ),
239 |
240 | 'orange': (
241 | '50': #fff3e0,
242 | '100': #ffe0b2,
243 | '200': #ffcc80,
244 | '300': #ffb74d,
245 | '400': #ffa726,
246 | '500': #ff9800,
247 | '600': #fb8c00,
248 | '700': #f57c00,
249 | '800': #ef6c00,
250 | '900': #e65100,
251 | 'a100': #ffd180,
252 | 'a200': #ffab40,
253 | 'a400': #ff9100,
254 | 'a700': #ff6d00
255 | ),
256 |
257 | 'deep-orange': (
258 | '50': #fbe9e7,
259 | '100': #ffccbc,
260 | '200': #ffab91,
261 | '300': #ff8a65,
262 | '400': #ff7043,
263 | '500': #ff5722,
264 | '600': #f4511e,
265 | '700': #e64a19,
266 | '800': #d84315,
267 | '900': #bf360c,
268 | 'a100': #ff9e80,
269 | 'a200': #ff6e40,
270 | 'a400': #ff3d00,
271 | 'a700': #dd2c00
272 | ),
273 |
274 | 'brown': (
275 | '50': #efebe9,
276 | '100': #d7ccc8,
277 | '200': #bcaaa4,
278 | '300': #a1887f,
279 | '400': #8d6e63,
280 | '500': #795548,
281 | '600': #6d4c41,
282 | '700': #5d4037,
283 | '800': #4e342e,
284 | '900': #3e2723
285 | ),
286 |
287 | 'grey': (
288 | '50': #fafafa,
289 | '100': #f5f5f5,
290 | '200': #eeeeee,
291 | '300': #e0e0e0,
292 | '400': #bdbdbd,
293 | '500': #9e9e9e,
294 | '600': #757575,
295 | '700': #616161,
296 | '800': #424242,
297 | '900': #212121
298 | ),
299 |
300 | 'blue-grey': (
301 | '50': #eceff1,
302 | '100': #cfd8dc,
303 | '200': #b0bec5,
304 | '300': #90a4ae,
305 | '400': #78909c,
306 | '500': #607d8b,
307 | '600': #546e7a,
308 | '700': #455a64,
309 | '800': #37474f,
310 | '900': #263238,
311 | '1000': #11171a
312 | )
313 | );
314 |
315 | @function material-color($color-name, $color-variant: '500') {
316 | $color: map-get(map-get($material-colors, $color-name), $color-variant);
317 | @if $color {
318 | @return $color;
319 | } @else {
320 | // Libsass still doesn't seem to support @error
321 | @warn '=> ERROR: COLOR NOT FOUND! <= | Your $color-name, $color-variant combination did not match any of the values in the $material-colors map.';
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/app/styles/material-icons.scss:
--------------------------------------------------------------------------------
1 | @mixin use-icon-font {
2 | font-weight: normal;
3 | font-style: normal;
4 | font-size: 24px;
5 | // Preferred icon size
6 | display: inline-block;
7 | width: 1em;
8 | height: 1em;
9 | line-height: 1;
10 | text-transform: none;
11 | letter-spacing: normal;
12 | word-wrap: normal;
13 | // Support for all WebKit browsers.
14 | -webkit-font-smoothing: antialiased;
15 | // Support for Safari and Chrome.
16 | text-rendering: optimizeLegibility;
17 | // Support for Firefox.
18 | -moz-osx-font-smoothing: grayscale;
19 | // Support for IE.
20 | -webkit-font-feature-settings: 'liga';
21 | -moz-font-feature-settings: 'liga';
22 | font-feature-settings: 'liga';
23 | // Custom added for GMP
24 | user-select: none;
25 | }
26 |
27 | @mixin material-icons {
28 | @include use-icon-font;
29 | font-family: 'Material Icons';
30 | }
31 |
32 | .material-icons {
33 | @include material-icons;
34 | }
35 |
--------------------------------------------------------------------------------
/app/styles/material-shadows.scss:
--------------------------------------------------------------------------------
1 | // shadow values taken directly from angular material (as of v1.0.0-rc1)
2 |
3 | $shadow-key-umbra-opacity: .2;
4 | $shadow-key-penumbra-opacity: .14;
5 | $shadow-ambient-shadow-opacity: .12;
6 |
7 | $material-box-shadows: (
8 | 1: (0 1px 3px 0 rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 1px 1px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 2px 1px -1px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
9 | 2: (0 1px 5px 0 rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 2px 2px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 3px 1px -2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
10 | 3: (0 1px 8px 0 rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 3px 4px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 3px 3px -2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
11 | 4: (0 2px 4px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 4px 5px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 1px 10px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
12 | 5: (0 3px 5px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 5px 8px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 1px 14px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
13 | 6: (0 3px 5px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 6px 10px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 1px 18px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
14 | 7: (0 4px 5px -2px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 7px 10px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 2px 16px 1px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
15 | 8: (0 5px 5px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 8px 10px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 3px 14px 2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
16 | 9: (0 5px 6px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 9px 12px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 3px 16px 2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
17 | 10: (0 6px 6px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 10px 14px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 4px 18px 3px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
18 | 11: (0 6px 7px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 11px 15px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 4px 20px 3px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
19 | 12: (0 7px 8px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 12px 17px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 5px 22px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
20 | 13: (0 7px 8px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 13px 19px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 5px 24px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
21 | 14: (0 7px 9px -4px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 14px 21px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 5px 26px 4px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
22 | 15: (0 8px 9px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 15px 22px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 6px 28px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
23 | 16: (0 8px 10px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 16px 24px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 6px 30px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
24 | 17: (0 8px 11px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 17px 26px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 6px 32px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
25 | 18: (0 9px 11px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 18px 28px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 7px 34px 6px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
26 | 19: (0 9px 12px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 19px 29px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 7px 36px 6px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
27 | 20: (0 10px 13px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 20px 31px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 8px 38px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
28 | 21: (0 10px 13px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 21px 33px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 8px 40px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
29 | 22: (0 10px 14px -6px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 22px 35px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 8px 42px 7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
30 | 23: (0 11px 14px -7px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 23px 36px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 9px 44px 8px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
31 | 24: (0 11px 15px -7px rgba(0, 0, 0, $shadow-key-umbra-opacity), 0 24px 38px 3px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 0 9px 46px 8px rgba(0, 0, 0, $shadow-ambient-shadow-opacity)),
32 | );
33 |
34 | @function material-shadow($z) {
35 | $shadow: map-get($material-box-shadows, $z);
36 | @if $shadow {
37 | @return $shadow;
38 | } @else {
39 | // Libsass still doesn't seem to support @error
40 | @warn '=> ERROR: NO SHADOW SPECIFIED AT GIVEN DEPTH.';
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/styles/root.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | width: 100%;
6 | height: 100%;
7 | overflow: hidden;
8 | }
9 |
10 | code {
11 | font-family: 'Roboto Mono', monospace;
12 | }
13 |
14 | // prevent CSS transitions from running before content is ready
15 | body.no-transitions * {
16 | transition: none !important;
17 | }
18 |
19 | ng-view,
20 | main {
21 | position: absolute;
22 | left: 0;
23 | top: 0;
24 | right: 0;
25 | bottom: 0;
26 | color: $colorBlackTextPrimary;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/app/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // dimensions
2 | $mobileChromeBreakpoint: 600px;
3 |
4 | // borders and seams
5 | $thinBorderColor: rgba(#000, .12);
6 | $thinBorderColorLighter: rgba(#000, .06);
7 | $thinBorderColorDarker: rgba(#000, .2);
8 | $thinBorderColorWhite: rgba(#fff, .2);
9 |
10 | // colors
11 | $colorBlackTextPrimary: rgba(#000, .87);
12 | $colorBlackTextSecondary: rgba(#000, .54);
13 | $colorBlackTextDisabled: rgba(#000, .38);
14 | $colorBlackTextVeryLight: rgba(#000, .15);
15 | $colorWhiteTextPrimary: rgba(#fff, 1.0);
16 | $colorWhiteTextSecondary: rgba(#fff, .70);
17 | $colorWhiteTextDisabled: rgba(#fff, .5);
18 |
19 | $colorPrimary400: material-color('blue', '400');
20 | $colorPrimary500: material-color('blue', '500');
21 | $colorPrimary600: material-color('blue', '600');
22 | $colorPrimary700: material-color('blue', '700');
23 |
24 | $colorFocusBorder: material-color('blue', '400');
25 | $colorSelection: material-color('blue', '700');
26 | $colorSelectedAnimationBlock: material-color('blue', '500');
27 |
28 | $colorError: material-color('red', 'a200');
29 | $colorSuccess: material-color('teal', 'a700');
30 |
31 | // animation timings
32 | $animTimeMedium: .3s;
33 | $animTimeFast: $animTimeMedium / 2;
34 | $animTimeVeryFast: $animTimeMedium / 3;
35 | $animTimeSlow: $animTimeMedium * 1.5;
36 |
--------------------------------------------------------------------------------
/art/screencap.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romannurik/AndroidIconAnimator/336e461c7d771ebf459c07620e0f5fe6f4e0f7f2/art/screencap.gif
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "android-icon-animator",
3 | "main": "android-icon-animator",
4 | "version": "0.0.1",
5 | "authors": [],
6 | "ignore": [
7 | "**/.*",
8 | "node_modules",
9 | "bower_components",
10 | "test",
11 | "tests"
12 | ],
13 | "dependencies": {
14 | "angular": "^1.5.8",
15 | "angular-material": "~1.1",
16 | "angular-route": "^1.5.8",
17 | "jquery": "~2.1.4"
18 | },
19 | "resolutions": {
20 | "angular": "1.5.8"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/back_simple.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "vector",
4 | "width": 24,
5 | "height": 24,
6 | "layers": [
7 | {
8 | "id": "page_1",
9 | "type": "group",
10 | "rotation": 0,
11 | "scaleX": 1,
12 | "scaleY": 1,
13 | "pivotX": 0,
14 | "pivotY": 0,
15 | "translateX": 0,
16 | "translateY": 0,
17 | "layers": [
18 | {
19 | "id": "artboard",
20 | "type": "group",
21 | "rotation": 0,
22 | "scaleX": 1,
23 | "scaleY": 1,
24 | "pivotX": 0,
25 | "pivotY": 0,
26 | "translateX": 0,
27 | "translateY": 0,
28 | "layers": [
29 | {
30 | "id": "top_arrow",
31 | "type": "path",
32 | "pathData": "M4,12 L12,4",
33 | "fillColor": null,
34 | "fillAlpha": 1,
35 | "strokeColor": "#ff979797",
36 | "strokeAlpha": 1,
37 | "strokeWidth": 2,
38 | "strokeLinecap": "square",
39 | "strokeLinejoin": "miter",
40 | "strokeMiterLimit": 4,
41 | "trimPathStart": 0,
42 | "trimPathEnd": 0,
43 | "trimPathOffset": 0
44 | },
45 | {
46 | "id": "bottom_arrow",
47 | "type": "path",
48 | "pathData": "M 4 12 L 12 20",
49 | "fillColor": null,
50 | "fillAlpha": 1,
51 | "strokeColor": "#ff979797",
52 | "strokeAlpha": 1,
53 | "strokeWidth": 2,
54 | "strokeLinecap": "square",
55 | "strokeLinejoin": "miter",
56 | "strokeMiterLimit": 4,
57 | "trimPathStart": 0,
58 | "trimPathEnd": 0,
59 | "trimPathOffset": 0
60 | },
61 | {
62 | "id": "middle",
63 | "type": "path",
64 | "pathData": "M4.5,12 L20,12",
65 | "fillColor": null,
66 | "fillAlpha": 1,
67 | "strokeColor": "#ff979797",
68 | "strokeAlpha": 1,
69 | "strokeWidth": 2,
70 | "strokeLinecap": "butt",
71 | "strokeLinejoin": "miter",
72 | "strokeMiterLimit": 4,
73 | "trimPathStart": 0,
74 | "trimPathEnd": 1,
75 | "trimPathOffset": 0
76 | }
77 | ]
78 | }
79 | ]
80 | }
81 | ]
82 | },
83 | "animations": [
84 | {
85 | "id": "anim",
86 | "duration": 2000,
87 | "blocks": [
88 | {
89 | "layerId": "top_arrow",
90 | "propertyName": "trimPathEnd",
91 | "fromValue": 0,
92 | "toValue": 1,
93 | "startTime": 525,
94 | "endTime": 1256,
95 | "interpolator": "FAST_OUT_SLOW_IN"
96 | },
97 | {
98 | "layerId": "bottom_arrow",
99 | "propertyName": "trimPathEnd",
100 | "fromValue": 0,
101 | "toValue": 1,
102 | "startTime": 525,
103 | "endTime": 1256,
104 | "interpolator": "FAST_OUT_SLOW_IN"
105 | },
106 | {
107 | "layerId": "middle",
108 | "propertyName": "trimPathStart",
109 | "fromValue": 1,
110 | "toValue": 0,
111 | "startTime": 0,
112 | "endTime": 550,
113 | "interpolator": "FAST_OUT_SLOW_IN"
114 | }
115 | ]
116 | }
117 | ]
118 | }
--------------------------------------------------------------------------------
/examples/barchart_in.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "vector",
4 | "canvasColor": null,
5 | "width": 24,
6 | "height": 24,
7 | "layers": [
8 | {
9 | "id": "middle",
10 | "type": "path",
11 | "pathData": "M 10,20 14,20 14,4 10,4 Z",
12 | "fillColor": "#ff000000",
13 | "fillAlpha": 1,
14 | "strokeColor": "",
15 | "strokeAlpha": 1,
16 | "strokeWidth": 1,
17 | "strokeLinecap": "butt",
18 | "strokeLinejoin": "miter",
19 | "strokeMiterLimit": 4,
20 | "trimPathStart": 0,
21 | "trimPathEnd": 1,
22 | "trimPathOffset": 0
23 | },
24 | {
25 | "id": "left",
26 | "type": "path",
27 | "pathData": "M 4,20 8,20 8,20 4,20 Z",
28 | "fillColor": "#ff000000",
29 | "fillAlpha": 1,
30 | "strokeColor": "",
31 | "strokeAlpha": 1,
32 | "strokeWidth": 1,
33 | "strokeLinecap": "butt",
34 | "strokeLinejoin": "miter",
35 | "strokeMiterLimit": 4,
36 | "trimPathStart": 0,
37 | "trimPathEnd": 1,
38 | "trimPathOffset": 0
39 | },
40 | {
41 | "id": "right",
42 | "type": "path",
43 | "pathData": "M 16,20 16,20 20,20 20,20 Z",
44 | "fillColor": "#ff000000",
45 | "fillAlpha": 1,
46 | "strokeColor": "",
47 | "strokeAlpha": 1,
48 | "strokeWidth": 1,
49 | "strokeLinecap": "butt",
50 | "strokeLinejoin": "miter",
51 | "strokeMiterLimit": 4,
52 | "trimPathStart": 0,
53 | "trimPathEnd": 1,
54 | "trimPathOffset": 0
55 | }
56 | ]
57 | },
58 | "animations": [
59 | {
60 | "id": "anim",
61 | "duration": 1000,
62 | "blocks": [
63 | {
64 | "layerId": "middle",
65 | "propertyName": "pathData",
66 | "fromValue": "M 10,20 14,20 14,20 10,20 Z",
67 | "toValue": "M 10,20 14,20 14,4 10,4 Z",
68 | "startTime": 0,
69 | "endTime": 664,
70 | "interpolator": "FAST_OUT_SLOW_IN"
71 | },
72 | {
73 | "layerId": "left",
74 | "propertyName": "pathData",
75 | "fromValue": "M 4,20 8,20 8,20 4,20 Z",
76 | "toValue": "M 4,20 8,20 8,12 4,12 Z",
77 | "startTime": 159,
78 | "endTime": 865,
79 | "interpolator": "FAST_OUT_SLOW_IN"
80 | },
81 | {
82 | "layerId": "right",
83 | "propertyName": "pathData",
84 | "fromValue": "M 16,20 16,20 20,20 20,20 Z",
85 | "toValue": "M 16,9 16,20 20,20 20,9 Z",
86 | "startTime": 208,
87 | "endTime": 907,
88 | "interpolator": "FAST_OUT_SLOW_IN"
89 | }
90 | ]
91 | }
92 | ]
93 | }
--------------------------------------------------------------------------------
/examples/menu_to_back.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "menu_back",
4 | "canvasColor": null,
5 | "width": 24,
6 | "height": 24,
7 | "alpha": 1,
8 | "layers": [
9 | {
10 | "id": "menu",
11 | "type": "group",
12 | "rotation": 0,
13 | "scaleX": 1,
14 | "scaleY": 1,
15 | "pivotX": 12,
16 | "pivotY": 12,
17 | "translateX": 0,
18 | "translateY": 0,
19 | "layers": [
20 | {
21 | "id": "bottom_container",
22 | "type": "group",
23 | "rotation": 0,
24 | "scaleX": 1,
25 | "scaleY": 1,
26 | "pivotX": 20,
27 | "pivotY": 17,
28 | "translateX": 0,
29 | "translateY": 0,
30 | "layers": [
31 | {
32 | "id": "bottom",
33 | "type": "path",
34 | "pathData": "M4,17 L20,17",
35 | "fillColor": null,
36 | "fillAlpha": 1,
37 | "strokeColor": "#000",
38 | "strokeAlpha": 1,
39 | "strokeWidth": 2,
40 | "strokeLinecap": "square",
41 | "strokeLinejoin": "miter",
42 | "strokeMiterLimit": 4,
43 | "trimPathStart": 0,
44 | "trimPathEnd": 1,
45 | "trimPathOffset": 0
46 | }
47 | ]
48 | },
49 | {
50 | "id": "stem_container",
51 | "type": "group",
52 | "rotation": 0,
53 | "scaleX": 1,
54 | "scaleY": 1,
55 | "pivotX": 12,
56 | "pivotY": 12,
57 | "translateX": 0,
58 | "translateY": 0,
59 | "layers": [
60 | {
61 | "id": "stem",
62 | "type": "path",
63 | "pathData": "M4,12 L20,12",
64 | "fillColor": null,
65 | "fillAlpha": 1,
66 | "strokeColor": "#000",
67 | "strokeAlpha": 1,
68 | "strokeWidth": 2,
69 | "strokeLinecap": "square",
70 | "strokeLinejoin": "miter",
71 | "strokeMiterLimit": 4,
72 | "trimPathStart": 0,
73 | "trimPathEnd": 1,
74 | "trimPathOffset": 0
75 | }
76 | ]
77 | },
78 | {
79 | "id": "top_container",
80 | "type": "group",
81 | "rotation": 0,
82 | "scaleX": 1,
83 | "scaleY": 1,
84 | "pivotX": 20,
85 | "pivotY": 7,
86 | "translateX": 0,
87 | "translateY": 0,
88 | "layers": [
89 | {
90 | "id": "top",
91 | "type": "path",
92 | "pathData": "M4,7 L20,7",
93 | "fillColor": null,
94 | "fillAlpha": 1,
95 | "strokeColor": "#000",
96 | "strokeAlpha": 1,
97 | "strokeWidth": 2,
98 | "strokeLinecap": "square",
99 | "strokeLinejoin": "miter",
100 | "strokeMiterLimit": 4,
101 | "trimPathStart": 0,
102 | "trimPathEnd": 1,
103 | "trimPathOffset": 0
104 | }
105 | ]
106 | }
107 | ]
108 | }
109 | ]
110 | },
111 | "animations": [
112 | {
113 | "id": "menu_to_back",
114 | "duration": 1600,
115 | "blocks": [
116 | {
117 | "layerId": "top_container",
118 | "propertyName": "translateX",
119 | "fromValue": 0,
120 | "toValue": -1.41421356,
121 | "startTime": 500,
122 | "endTime": 1200,
123 | "interpolator": "ACCELERATE_DECELERATE"
124 | },
125 | {
126 | "layerId": "top_container",
127 | "propertyName": "translateY",
128 | "fromValue": 0,
129 | "toValue": 5,
130 | "startTime": 500,
131 | "endTime": 1200,
132 | "interpolator": "ACCELERATE_DECELERATE"
133 | },
134 | {
135 | "layerId": "top_container",
136 | "propertyName": "rotation",
137 | "fromValue": 0,
138 | "toValue": 45,
139 | "startTime": 500,
140 | "endTime": 1200,
141 | "interpolator": "ACCELERATE_DECELERATE"
142 | },
143 | {
144 | "layerId": "bottom_container",
145 | "propertyName": "translateX",
146 | "fromValue": 0,
147 | "toValue": -1.41421356,
148 | "startTime": 500,
149 | "endTime": 1200,
150 | "interpolator": "ACCELERATE_DECELERATE"
151 | },
152 | {
153 | "layerId": "bottom_container",
154 | "propertyName": "translateY",
155 | "fromValue": 0,
156 | "toValue": -5,
157 | "startTime": 500,
158 | "endTime": 1200,
159 | "interpolator": "ACCELERATE_DECELERATE"
160 | },
161 | {
162 | "layerId": "bottom_container",
163 | "propertyName": "rotation",
164 | "fromValue": 0,
165 | "toValue": -45,
166 | "startTime": 500,
167 | "endTime": 1200,
168 | "interpolator": "ACCELERATE_DECELERATE"
169 | },
170 | {
171 | "layerId": "menu",
172 | "propertyName": "rotation",
173 | "fromValue": 0,
174 | "toValue": 180,
175 | "startTime": 500,
176 | "endTime": 1200,
177 | "interpolator": "ACCELERATE_DECELERATE"
178 | },
179 | {
180 | "layerId": "bottom",
181 | "propertyName": "trimPathStart",
182 | "fromValue": 0,
183 | "toValue": 0.5,
184 | "startTime": 500,
185 | "endTime": 1200,
186 | "interpolator": "ACCELERATE_DECELERATE"
187 | },
188 | {
189 | "layerId": "top",
190 | "propertyName": "trimPathStart",
191 | "fromValue": 0,
192 | "toValue": 0.5,
193 | "startTime": 500,
194 | "endTime": 1200,
195 | "interpolator": "ACCELERATE_DECELERATE"
196 | },
197 | {
198 | "layerId": "stem",
199 | "propertyName": "trimPathEnd",
200 | "fromValue": 1,
201 | "toValue": 0.8,
202 | "startTime": 500,
203 | "endTime": 1200,
204 | "interpolator": "ACCELERATE_DECELERATE"
205 | },
206 | {
207 | "layerId": "stem_container",
208 | "propertyName": "translateX",
209 | "fromValue": 0,
210 | "toValue": 1,
211 | "startTime": 500,
212 | "endTime": 1200,
213 | "interpolator": "ACCELERATE_DECELERATE"
214 | }
215 | ]
216 | }
217 | ]
218 | }
--------------------------------------------------------------------------------
/examples/search_to_back.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "search_back",
4 | "width": 48,
5 | "height": 24,
6 | "layers": [
7 | {
8 | "type": "group",
9 | "id": "menu",
10 | "pivotX": 12,
11 | "pivotY": 12,
12 | "layers": [
13 | {
14 | "id": "circle",
15 | "strokeWidth": 2,
16 | "strokeColor": "#000",
17 | "pathData": "M25.39,13.39 C24.0002369,14.7797632 21.9746129,15.3225275 20.0761611,14.8138389 C18.1777093,14.3051503 16.6948497,12.8222907 16.1861611,10.9238389 C15.6774725,9.02538707 16.2202368,6.99976313 17.61,5.61 C19.7583877,3.46161232 23.2416123,3.46161232 25.39,5.61 C27.5383877,7.75838768 27.5383877,11.2416123 25.39,13.39"
18 | },
19 | {
20 | "id": "stem",
21 | "strokeWidth": 2,
22 | "strokeColor": "#000",
23 | "pathData": "M24.7000008,12.6999998 C24.7000008,12.6999998 31.8173374,19.9066081 31.8173371,19.9066082 C32.7867437,20.7006357 34.4599991,23 37.5,23 C40.5400009,23 43,20.54 43,17.5 C43,14.46 40.5400009,12 37.5,12 L31.8173371,12 C31.8173374,12 18.8477173,12 17,12",
24 | "trimPathStart": 0,
25 | "trimPathEnd": 0.185
26 | },
27 | {
28 | "id": "top_arrowhead",
29 | "strokeWidth": 2,
30 | "strokeColor": "#000",
31 | "pathData": "M16.7107986,11.2764828 L24.7221527,19.2878361",
32 | "trimPathStart": 0,
33 | "trimPathEnd": 0
34 | },
35 | {
36 | "id": "bottom_arrowhead",
37 | "strokeWidth": 2,
38 | "strokeColor": "#000",
39 | "pathData": "M16.7017297,12.6957157 L24.7043962,4.69304955",
40 | "trimPathStart": 0,
41 | "trimPathEnd": 0
42 | }
43 | ]
44 | }
45 | ]
46 | },
47 | "animations": [
48 | {
49 | "id": "search_to_back",
50 | "duration": 2500,
51 | "blocks": [
52 | {
53 | "layerId": "circle",
54 | "propertyName": "trimPathEnd",
55 | "fromValue": 1,
56 | "toValue": 0,
57 | "startTime": 0,
58 | "endTime": 1000
59 | },
60 | {
61 | "layerId": "stem",
62 | "propertyName": "trimPathEnd",
63 | "fromValue": 0.185,
64 | "toValue": 1,
65 | "startTime": 0,
66 | "endTime": 1500
67 | },
68 | {
69 | "layerId": "stem",
70 | "propertyName": "trimPathStart",
71 | "fromValue": 0,
72 | "toValue": 0.75,
73 | "startTime": 1000,
74 | "endTime": 1700
75 | },
76 | {
77 | "layerId": "top_arrowhead",
78 | "propertyName": "trimPathEnd",
79 | "fromValue": 0.2,
80 | "toValue": 1,
81 | "startTime": 1400,
82 | "endTime": 1900
83 | },
84 | {
85 | "layerId": "bottom_arrowhead",
86 | "propertyName": "trimPathEnd",
87 | "fromValue": 0.2,
88 | "toValue": 1,
89 | "startTime": 1400,
90 | "endTime": 1900
91 | }
92 | ]
93 | }
94 | ]
95 | }
96 |
--------------------------------------------------------------------------------
/examples/search_to_close.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "vector",
4 | "canvasColor": null,
5 | "width": 24,
6 | "height": 24,
7 | "alpha": 1,
8 | "layers": [
9 | {
10 | "id": "oval_container",
11 | "type": "group",
12 | "rotation": 0,
13 | "scaleX": 1,
14 | "scaleY": 1,
15 | "pivotX": 0,
16 | "pivotY": 0,
17 | "translateX": 0,
18 | "translateY": 0,
19 | "layers": [
20 | {
21 | "id": "oval",
22 | "type": "path",
23 | "pathData": "M 13.389 13.389 C 15.537 11.241 15.537 7.759 13.389 5.611 C 11.241 3.463 7.759 3.463 5.611 5.611 C 3.463 7.759 3.463 11.241 5.611 13.389 C 7.759 15.537 11.241 15.537 13.389 13.389 Z",
24 | "fillColor": null,
25 | "fillAlpha": 1,
26 | "strokeColor": "#000000",
27 | "strokeAlpha": 1,
28 | "strokeWidth": 1.8,
29 | "strokeLinecap": "butt",
30 | "strokeLinejoin": "miter",
31 | "strokeMiterLimit": 4,
32 | "trimPathStart": 0,
33 | "trimPathEnd": 1,
34 | "trimPathOffset": 0
35 | }
36 | ]
37 | },
38 | {
39 | "id": "ne_stem",
40 | "type": "path",
41 | "pathData": "M 18 6 L 6 18",
42 | "fillColor": null,
43 | "fillAlpha": 1,
44 | "strokeColor": "#000000",
45 | "strokeAlpha": 1,
46 | "strokeWidth": 1.8,
47 | "strokeLinecap": "butt",
48 | "strokeLinejoin": "miter",
49 | "strokeMiterLimit": 4,
50 | "trimPathStart": 1,
51 | "trimPathEnd": 1,
52 | "trimPathOffset": 0
53 | },
54 | {
55 | "id": "nw_stem",
56 | "type": "path",
57 | "pathData": "M 6 6 L 20 20",
58 | "fillColor": null,
59 | "fillAlpha": 1,
60 | "strokeColor": "#000000",
61 | "strokeAlpha": 1,
62 | "strokeWidth": 1.8,
63 | "strokeLinecap": "butt",
64 | "strokeLinejoin": "miter",
65 | "strokeMiterLimit": 4,
66 | "trimPathStart": 0.48,
67 | "trimPathEnd": 1,
68 | "trimPathOffset": 0
69 | }
70 | ]
71 | },
72 | "animations": [
73 | {
74 | "id": "anim",
75 | "duration": 1000,
76 | "blocks": [
77 | {
78 | "layerId": "nw_stem",
79 | "propertyName": "trimPathStart",
80 | "fromValue": 0.48,
81 | "toValue": 0,
82 | "startTime": 300,
83 | "endTime": 800,
84 | "interpolator": "FAST_OUT_SLOW_IN"
85 | },
86 | {
87 | "layerId": "nw_stem",
88 | "propertyName": "trimPathEnd",
89 | "fromValue": 1,
90 | "toValue": 0.86,
91 | "startTime": 300,
92 | "endTime": 800,
93 | "interpolator": "FAST_OUT_SLOW_IN"
94 | },
95 | {
96 | "layerId": "ne_stem",
97 | "propertyName": "trimPathStart",
98 | "fromValue": 1,
99 | "toValue": 0,
100 | "startTime": 522,
101 | "endTime": 836,
102 | "interpolator": "FAST_OUT_SLOW_IN"
103 | },
104 | {
105 | "layerId": "oval",
106 | "propertyName": "trimPathStart",
107 | "fromValue": 0,
108 | "toValue": 1,
109 | "startTime": 134,
110 | "endTime": 550,
111 | "interpolator": "ACCELERATE_DECELERATE"
112 | },
113 | {
114 | "layerId": "oval_container",
115 | "propertyName": "translateX",
116 | "fromValue": 0,
117 | "toValue": -6.7,
118 | "startTime": 300,
119 | "endTime": 800,
120 | "interpolator": "FAST_OUT_SLOW_IN"
121 | },
122 | {
123 | "layerId": "oval_container",
124 | "propertyName": "translateY",
125 | "fromValue": 0,
126 | "toValue": -6.7,
127 | "startTime": 300,
128 | "endTime": 800,
129 | "interpolator": "FAST_OUT_SLOW_IN"
130 | }
131 | ]
132 | }
133 | ]
134 | }
--------------------------------------------------------------------------------
/examples/simple_path_morph.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "pathmorphtest",
4 | "width": 24,
5 | "height": 24,
6 | "layers": [
7 | {
8 | "id": "foo",
9 | "strokeColor": "#f00",
10 | "strokeWidth": 2,
11 | "strokeLinecap": "round",
12 | "pathData": "M16,3 C7,16 30,20 8,22"
13 | }
14 | ]
15 | },
16 | "animations": [
17 | {
18 | "id": "morph_test",
19 | "duration": 1000,
20 | "blocks": [
21 | {
22 | "layerId": "foo",
23 | "propertyName": "pathData",
24 | "startTime": 0,
25 | "endTime": 1000,
26 | "fromValue": "M16,3 C7,16 30,20 8,22",
27 | "toValue": "M3,5 C12.3026934,-3 30,8 17,21"
28 | }
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/examples/visibility_strike.iconanim:
--------------------------------------------------------------------------------
1 | {
2 | "artwork": {
3 | "id": "vector",
4 | "canvasColor": null,
5 | "width": 24,
6 | "height": 24,
7 | "alpha": 1,
8 | "layers": [
9 | {
10 | "id": "page_1",
11 | "type": "group",
12 | "rotation": 0,
13 | "scaleX": 1,
14 | "scaleY": 1,
15 | "pivotX": 0,
16 | "pivotY": 0,
17 | "translateX": 0,
18 | "translateY": 0,
19 | "layers": [
20 | {
21 | "id": "artboard",
22 | "type": "group",
23 | "rotation": 0,
24 | "scaleX": 1,
25 | "scaleY": 1,
26 | "pivotX": 0,
27 | "pivotY": 0,
28 | "translateX": 0,
29 | "translateY": 0,
30 | "layers": [
31 | {
32 | "id": "strike",
33 | "type": "path",
34 | "pathData": "M 2,4.27 3.27,3 3.27,3 2,4.27 Z",
35 | "fillColor": "#ff000000",
36 | "fillAlpha": 1,
37 | "strokeColor": "",
38 | "strokeAlpha": 1,
39 | "strokeWidth": 1,
40 | "strokeLinecap": "butt",
41 | "strokeLinejoin": "miter",
42 | "strokeMiterLimit": 4,
43 | "trimPathStart": 0,
44 | "trimPathEnd": 1,
45 | "trimPathOffset": 0
46 | },
47 | {
48 | "id": "strike_negative",
49 | "type": "mask",
50 | "pathData": "M0 0 L24,0 L24,24 L0,24 L0,0 Z M4.54,1.73 L3.27,3 L3.27,3 L4.54,1.73 Z"
51 | },
52 | {
53 | "id": "shape",
54 | "type": "path",
55 | "pathData": "M12,4.5 C7,4.5 2.73,7.61 1,12 C2.73,16.39 7,19.5 12,19.5 C17,19.5 21.27,16.39 23,12 C21.27,7.61 17,4.5 12,4.5 L12,4.5 Z M12,17 C9.24,17 7,14.76 7,12 C7,9.24 9.24,7 12,7 C14.76,7 17,9.24 17,12 C17,14.76 14.76,17 12,17 L12,17 Z M12,9 C10.34,9 9,10.34 9,12 C9,13.66 10.34,15 12,15 C13.66,15 15,13.66 15,12 C15,10.34 13.66,9 12,9 L12,9 Z",
56 | "fillColor": "#ff000000",
57 | "fillAlpha": 1,
58 | "strokeColor": "",
59 | "strokeAlpha": 1,
60 | "strokeWidth": 1,
61 | "strokeLinecap": "butt",
62 | "strokeLinejoin": "miter",
63 | "strokeMiterLimit": 4,
64 | "trimPathStart": 0,
65 | "trimPathEnd": 1,
66 | "trimPathOffset": 0
67 | }
68 | ]
69 | }
70 | ]
71 | }
72 | ]
73 | },
74 | "animations": [
75 | {
76 | "id": "anim",
77 | "duration": 2000,
78 | "blocks": [
79 | {
80 | "layerId": "strike",
81 | "propertyName": "pathData",
82 | "fromValue": "M 2,4.27 3.27,3 3.27,3 2,4.27 Z",
83 | "toValue": "M 19.73,22 21,20.73 3.27,3 2,4.27 Z",
84 | "startTime": 268,
85 | "endTime": 1539,
86 | "interpolator": "ACCELERATE_DECELERATE"
87 | },
88 | {
89 | "layerId": "strike_negative",
90 | "propertyName": "pathData",
91 | "fromValue": "M0 0 L24,0 L24,24 L0,24 L0,0 Z M4.54,1.73 L3.27,3 L3.27,3 L4.54,1.73 Z",
92 | "toValue": "M0 0 L24,0 L24,24 L0,24 L0,0 Z M4.54,1.73 L3.27,3 L21,20.73 L22.27,19.46 Z",
93 | "startTime": 268,
94 | "endTime": 1539,
95 | "interpolator": "ACCELERATE_DECELERATE"
96 | },
97 | {
98 | "layerId": "vector",
99 | "propertyName": "alpha",
100 | "fromValue": 1,
101 | "toValue": 0.333,
102 | "startTime": 268,
103 | "endTime": 1539,
104 | "interpolator": "ACCELERATE_DECELERATE"
105 | }
106 | ]
107 | }
108 | ]
109 | }
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | 'use strict';
18 |
19 | // Include Gulp & Tools We'll Use
20 | var gulp = require('gulp');
21 | var $ = require('gulp-load-plugins')();
22 | var del = require('del');
23 | var fs = require('fs');
24 | var source = require('vinyl-source-stream');
25 | var buffer = require('vinyl-buffer');
26 | var runSequence = require('run-sequence');
27 | var browserSync = require('browser-sync');
28 | var browserify = require('browserify');
29 | var exclude = require('gulp-ignore').exclude;
30 | var reload = browserSync.reload;
31 | var history = require('connect-history-api-fallback');
32 | var merge = require('merge-stream');
33 |
34 |
35 | var AUTOPREFIXER_BROWSERS = [
36 | 'ie >= 10',
37 | 'ie_mob >= 10',
38 | 'ff >= 30',
39 | 'chrome >= 34',
40 | 'safari >= 7',
41 | 'ios >= 7',
42 | 'android >= 4.4'
43 | ];
44 |
45 |
46 | var DEV_MODE = false;
47 | var BASE_HREF = '/AndroidIconAnimator/';
48 |
49 |
50 | function errorHandler(error) {
51 | console.error(error.stack);
52 | this.emit('end'); // http://stackoverflow.com/questions/23971388
53 | }
54 |
55 | // Lint JavaScript
56 | gulp.task('scripts', function () {
57 | return browserify('./app/scripts/app.js', {
58 | debug: true, // debug generates sourcemap
59 | basedir: '.',
60 | paths: [
61 | './app/scripts/',
62 | './node_modules/'
63 | ]
64 | })
65 | .transform('babelify', {
66 | presets: ['es2015'],
67 | plugins: ['transform-decorators-legacy']
68 | })
69 | .transform('require-globify')
70 | .bundle()
71 | .on('error', errorHandler)
72 | .pipe(source('app.js'))
73 | .pipe(buffer())
74 | .pipe(gulp.dest('.tmp/scripts'))
75 | .pipe($.if(!DEV_MODE, $.uglify({
76 | mangle:false
77 | })))
78 | .pipe(gulp.dest('dist/scripts'));
79 | });
80 |
81 | // Bower
82 | gulp.task('bower', function(cb) {
83 | return $.bower('.tmp/lib')
84 | .pipe(exclude('!**/*.{js,css,map}'))
85 | .pipe(exclude('**/test/**'))
86 | .pipe(exclude('**/tests/**'))
87 | .pipe(exclude('**/modules/**'))
88 | .pipe(exclude('**/demos/**'))
89 | .pipe(exclude('**/src/**'))
90 | .pipe(gulp.dest('dist/lib'));
91 | });
92 |
93 | // Optimize Images
94 | gulp.task('images', function () {
95 | return gulp.src('app/images/**/*')
96 | .pipe($.cache($.imagemin({
97 | progressive: true,
98 | interlaced: true
99 | })))
100 | .pipe(gulp.dest('dist/images'))
101 | .pipe($.size({title: 'images'}));
102 | });
103 |
104 | // Generate icon set
105 | gulp.task('icons', function () {
106 | return gulp.src('app/icons/**/*.svg')
107 | .pipe($.cache($.imagemin({
108 | progressive: true,
109 | interlaced: true
110 | })))
111 | .pipe($.svgNgmaterial({filename: 'icons.svg'}))
112 | .pipe(gulp.dest('dist/images'))
113 | .pipe(gulp.dest('.tmp/images'))
114 | .pipe($.size({title: 'icons'}));
115 | });
116 |
117 | // Copy All Files At The Root Level (app) and lib
118 | gulp.task('copy', function () {
119 | var s1 = gulp.src([
120 | 'app/*',
121 | '!app/icons',
122 | '!app/*.html'
123 | ], {
124 | dot: true
125 | }).pipe(gulp.dest('dist'))
126 | .pipe($.size({title: 'copy'}));
127 |
128 | var s2 = gulp.src('app/assets/**/*')
129 | .pipe(gulp.dest('dist/assets'))
130 | .pipe($.size({title: 'assets'}));
131 |
132 | return merge(s1, s2);
133 | });
134 |
135 | // Libs
136 | gulp.task('lib', function () {
137 | return gulp.src(['app/lib/**/*'], {dot: true})
138 | .pipe(gulp.dest('dist/lib'))
139 | .pipe($.size({title: 'lib'}));
140 | });
141 |
142 | // Compile and Automatically Prefix Stylesheets
143 | gulp.task('styles', function () {
144 | // For best performance, don't add Sass partials to `gulp.src`
145 | return gulp.src('app/styles/app.scss')
146 | .pipe($.changed('styles', {extension: '.scss'}))
147 | .pipe($.sassGlob())
148 | .pipe($.sass({
149 | style: 'expanded',
150 | precision: 10,
151 | quiet: true
152 | }).on('error', errorHandler))
153 | .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
154 | .pipe(gulp.dest('.tmp/styles'))
155 | // Concatenate And Minify Styles
156 | .pipe($.if(!DEV_MODE, $.if('*.css', $.csso())))
157 | .pipe(gulp.dest('dist/styles'))
158 | .pipe($.size({title: 'styles'}));
159 | });
160 |
161 |
162 | function currentVersionInfo() {
163 | return new Promise((resolve, reject) => {
164 | if (DEV_MODE) {
165 | resolve({version: 'DEV_BUILD'});
166 | } else {
167 | $.git.revParse({args: '--short HEAD'}, (err, hash) => {
168 | $.git.exec({args: 'describe --tags'}, (err, tag) => {
169 | tag = tag.replace(/\s/g, '');
170 | resolve({version: `${tag} (build ${hash})`});
171 | });
172 | });
173 | }
174 | });
175 | }
176 |
177 |
178 | gulp.task('html', function() {
179 | return currentVersionInfo().then((versionInfo) =>
180 | gulp.src('app/**/*.html')
181 | .pipe($.replace(/%%BASE_HREF%%/g, BASE_HREF))
182 | .pipe($.replace(/%%VERSION%%/g, versionInfo.version))
183 | .pipe(gulp.dest('.tmp'))
184 | .pipe($.if('*.html', $.minifyHtml({empty:true})))
185 | .pipe(gulp.dest('dist'))
186 | .pipe($.size({title: 'html'})));
187 | });
188 |
189 | // Clean Output Directory
190 | gulp.task('clean', function(cb) {
191 | del.sync(['.tmp', 'dist']);
192 | $.cache.clearAll();
193 | cb();
194 | });
195 |
196 | // Watch Files For Changes & Reload
197 | gulp.task('serve', function (cb) {
198 | DEV_MODE = true;
199 | BASE_HREF = '/';
200 | runSequence('__serve__', cb);
201 | });
202 |
203 | gulp.task('__serve__', ['styles', 'scripts', 'icons', 'bower', 'html'], function () {
204 | browserSync({
205 | notify: false,
206 | // Run as an https by uncommenting 'https: true'
207 | // Note: this uses an unsigned certificate which on first access
208 | // will present a certificate warning in the browser.
209 | // https: true,
210 | server: {
211 | baseDir: ['.tmp', 'app'],
212 | routes: {
213 | '/_sandbox': '_sandbox'
214 | },
215 | middleware: [history()]
216 | }
217 | });
218 |
219 | gulp.watch(['app/**/*.html'], ['html', reload]);
220 | gulp.watch(['app/**/*.{scss,css}'], ['styles', reload]);
221 | gulp.watch(['app/**/*.js'], ['scripts', reload]);
222 | gulp.watch(['app/images/**/*'], reload);
223 | gulp.watch(['app/icons/**/*'], ['icons', reload]);
224 | gulp.watch(['app/assets/**/*'], reload);
225 | });
226 |
227 | // Build and serve the output from the dist build
228 | gulp.task('serve:dist', ['default'], function () {
229 | browserSync({
230 | notify: false,
231 | // Run as an https by uncommenting 'https: true'
232 | // Note: this uses an unsigned certificate which on first access
233 | // will present a certificate warning in the browser.
234 | // https: true,
235 | server: 'dist'
236 | });
237 | });
238 |
239 | // Build Production Files, the Default Task
240 | gulp.task('default', ['clean', 'test'], function (cb) {
241 | runSequence('styles',
242 | ['scripts', 'bower', 'html', 'images', 'icons', 'lib', 'copy'],
243 | cb);
244 | });
245 |
246 | // Tests
247 | gulp.task('test', function (cb) {
248 | return gulp.src(['test/**/*.js'], {read: false})
249 | .pipe($.mocha({
250 | reporter: 'nyan',
251 | require: ['babel-register'],
252 | }));
253 | });
254 |
255 | // Deploy to GitHub pages
256 | gulp.task('deploy', function() {
257 | return gulp.src('dist/**/*', {dot: true})
258 | .pipe($.ghPages());
259 | });
260 |
261 | // Load custom tasks from the `tasks` directory
262 | try { require('require-dir')('tasks'); } catch (err) {}
263 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
4 | "babel-preset-es2015": "^6.3.13",
5 | "babel-register": "^6.18.0",
6 | "babelify": "^7.2.0",
7 | "browser-sync": "^2.12.8",
8 | "browserify": "^14.4.0",
9 | "connect-history-api-fallback": "^1.1.0",
10 | "del": "^3.0.0",
11 | "globby": "^6.1.0",
12 | "gulp": "^3.9.1",
13 | "gulp-autoprefixer": "^4.0.0",
14 | "gulp-bower": "0.0.13",
15 | "gulp-cache": "^0.4.5",
16 | "gulp-changed": "^3.1.0",
17 | "gulp-csso": "^3.0.0",
18 | "gulp-gh-pages": "^0.5.4",
19 | "gulp-git": "^2.4.0",
20 | "gulp-if": "^2.0.1",
21 | "gulp-ignore": "^2.0.1",
22 | "gulp-imagemin": "^3.0.1",
23 | "gulp-load-plugins": "^1.2.3",
24 | "gulp-minify-html": "^1.0.6",
25 | "gulp-mocha": "^4.3.1",
26 | "gulp-replace": "^0.5.4",
27 | "gulp-sass": "^3.0.0",
28 | "gulp-sass-glob": "^1.0.6",
29 | "gulp-size": "^2.1.0",
30 | "gulp-svg-ngmaterial": "^2.0.2",
31 | "gulp-uglify": "^3.0.0",
32 | "jquery": "^3.1.1",
33 | "merge-stream": "^1.0.0",
34 | "mocha": "^3.2.0",
35 | "opn": "^5.1.0",
36 | "require-dir": "^0.3.0",
37 | "require-globify": "^1.3.0",
38 | "run-sequence": "^1.2.0",
39 | "through2": "^2.0.1",
40 | "vinyl-buffer": "^1.0.0",
41 | "vinyl-source-stream": "^1.1.0"
42 | },
43 | "engines": {
44 | "node": ">=0.10.0"
45 | },
46 | "private": true,
47 | "scripts": {
48 | "test": "gulp test",
49 | "postinstall": "bower install"
50 | },
51 | "dependencies": {
52 | "bezier-easing": "^2.0.3",
53 | "bezier-js": "^2.0.1",
54 | "tinycolor2": "^1.4.1",
55 | "zipjs-browserify": "^1.0.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/test-colorutil.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import assert from 'assert';
18 | import {ColorUtil} from '../app/scripts/ColorUtil';
19 |
20 | describe('ColorUtil', () => {
21 | let TESTS_ANDROID_RAW = [
22 | ['#f000', {r:0,g:0,b:0,a:255}, '#000000'],
23 | ['f00', {r:255,g:0,b:0,a:255}, '#ff0000'],
24 | ['#7f00ff00', {r:0,g:255,b:0,a:127}, '#7f00ff00'],
25 | ['an invalid color', null],
26 | ];
27 |
28 | let TESTS_ANDROID_CSS = [
29 | ['#f000', 'rgba(0,0,0,1.00)', '#000000'],
30 | ['f00', 'rgba(255,0,0,1.00)', '#ff0000'],
31 | ['#7f00ff00', 'rgba(0,255,0,0.50)', '#8000ff00'],
32 | ['', 'transparent', '#00000000'],
33 | ];
34 |
35 | describe('#parseAndroidColor', () => {
36 | TESTS_ANDROID_RAW.forEach(a => {
37 | it(`parsing '${a[0]}' yields ${JSON.stringify(a[1])}`, () =>
38 | assert.deepEqual(a[1], ColorUtil.parseAndroidColor(a[0])));
39 | });
40 | });
41 |
42 | describe('#toAndroidString', () => {
43 | TESTS_ANDROID_RAW.forEach(a => {
44 | if (a[1]) {
45 | it(`converting ${JSON.stringify(a[1])} to string yields '${a[2]}'`, () => {
46 | assert.deepEqual(a[2], ColorUtil.toAndroidString(a[1]));
47 | });
48 | }
49 | });
50 | });
51 |
52 | describe('#androidToCssColor', () => {
53 | TESTS_ANDROID_CSS.forEach(a => {
54 | it(`converting '${a[0]}' to CSS color yields '${a[1]}'`, () =>
55 | assert.equal(a[1], ColorUtil.androidToCssColor(a[0])));
56 | });
57 | });
58 |
59 | describe('#svgToAndroidColor', () => {
60 | TESTS_ANDROID_CSS.forEach(a => {
61 | it(`converting '${a[1]}' to Android color yields '${a[2]}'`, () =>
62 | assert.equal(a[2], ColorUtil.svgToAndroidColor(a[1])));
63 | });
64 | });
65 | });
66 |
--------------------------------------------------------------------------------