├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── images
├── original.png
└── preview.png
├── index.html
├── package.json
├── public
├── dist.main.js
├── index.css
├── index.html
└── target
│ ├── document.json
│ ├── images
│ ├── 00f051d3a27fec14ba725121eb2bffb3ac1c3e02.png
│ ├── 0c2421c7228cf5e9d8133b6f598461a7cf439fc5.png
│ ├── 29c43ee866210c6174a4f800798b066f50e3b178.png
│ ├── 54099fda567d9ed2a6548505a921cb89177357b3.png
│ ├── 65d57b712c0dfc95448315b82d457f0a29dd4c8c.png
│ ├── 82fec9dc20c85608ebb2cc01f678364b4fd53107.png
│ ├── 841a9c5ab29068b5f372d9e171dad1b3e4a62950.png
│ ├── a1948e015ba95991c3a04a47e19f0497913a5997.png
│ ├── a683c9b5c00b8e1d441297f1df1c4fe2d4426c56.png
│ ├── b0ec6a85ff35a5223bb0c0dfefd749413050e777.png
│ ├── b4913550b705b27fb487bb41aac8e4150a0c7f53.png
│ ├── b771bedd82473fdbc03dad41679d4b710eaa9615.png
│ ├── c55edac9c53afcc5a37bc81c37a9d8329846041e.png
│ ├── cd36c97a217daeae79b3c00b3fa6678f2ac98ac9.png
│ ├── dbb5e35ccbb8f4dea5f95b79f1b44dcb442ffbe2.png
│ └── e607a3e5caf014ff210d1af02a2a2e9aae283ba7.png
│ ├── meta.json
│ ├── pages
│ ├── 1C01EF08-06C2-469A-8EB7-0BF12367FF6E.json
│ └── 9B2D6A75-D2C0-40F3-88AB-AE4BD625E520.json
│ ├── previews
│ └── preview.png
│ └── user.json
├── samples
├── Fitness App.sketch
├── ios11-app-store-design-ui.sketch
└── stickersheet_general.sketch
├── scripts
├── build.js
├── start.js
├── webpack.config.start.js
└── webpackDevServer.config.js
├── src
├── Document.js
├── Layer.js
├── Page.js
├── PageViewer.js
├── data
│ ├── Color.js
│ ├── Style.js
│ ├── SvgStyle.js
│ └── TextStyle.js
├── globals
│ └── SymbolStore.js
├── index.js
├── layers
│ ├── Artboard.js
│ ├── Bitmap.js
│ ├── Ellipse.js
│ ├── LayerGroup.js
│ ├── MaskGroup.js
│ ├── Path.js
│ ├── Rectangle.js
│ ├── ShapeGroup.js
│ ├── SymbolInstance.js
│ └── Text.js
└── utils
│ ├── axiosUtils.js
│ ├── decodeUtils.js
│ └── layerUtils.js
└── static
├── index.css
└── index.html
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "stage-0"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 양현석
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-sketch-viewer
2 | Sketch file viewer for web
3 |
4 | ## run samples
5 | clone this project and run
6 | `npm install`
7 | `npm start ./samples/Fitness\ App.sketch`
8 |
9 | ## What this project for
10 | for rendering sketch in web, static hosting unzipped sketch file, without any server-side image extracting.
11 |
12 | ## Requirements
13 | `node 7+`
14 |
15 | ## Preview
16 | original sketch file
17 | 
18 |
19 | rendered preview
20 | 
21 |
22 | ## Sample sources
23 | - [Fitness App](https://www.dropbox.com/s/w1udlebtbjidg1o/Fitness%20App.sketch?dl=0)
24 | - [iOS AppStore UI](https://www.sketchappsources.com/free-source/2689-ios-11-app-store-design-uu-sketch-freebie-resource.html)
--------------------------------------------------------------------------------
/images/original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/images/original.png
--------------------------------------------------------------------------------
/images/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/images/preview.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sketch Web Viewer
6 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-sketch-viewer",
3 | "version": "0.0.1",
4 | "description": "Sketch file viewer for web",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "node scripts/start.js",
8 | "build": "node scripts/build.js"
9 | },
10 | "keywords": [
11 | "sketch",
12 | "react"
13 | ],
14 | "author": "Fourwingsy@gmail.com",
15 | "license": "MIT",
16 | "dependencies": {
17 | "axios": "^0.16.2",
18 | "buffer": "^5.0.6",
19 | "classnames": "^2.2.5",
20 | "express": "^4.15.3",
21 | "prop-types": "^15.5.10",
22 | "react": "^15.5.4",
23 | "react-dom": "^15.5.4"
24 | },
25 | "devDependencies": {
26 | "babel-cli": "^6.24.1",
27 | "babel-loader": "^7.0.0",
28 | "babel-preset-react": "^6.24.1",
29 | "babel-preset-stage-0": "^6.24.1",
30 | "css-loader": "^0.28.4",
31 | "file-loader": "^0.11.2",
32 | "fs-extra": "^3.0.1",
33 | "fstream": "^1.0.11",
34 | "html-webpack-plugin": "^2.28.0",
35 | "json-loader": "^0.5.4",
36 | "unzip": "^0.1.11",
37 | "webpack": "^2.6.1",
38 | "webpack-dev-server": "^2.4.5"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
1 | html, body, #app, #document {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | font-family: 'Helvetica Neue', sans-serif;
7 | tab-size: 7;
8 | -webkit-font-smoothing: antialiased;
9 | }
10 |
11 | nav {
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | width: 245px;
16 | height: 100%;
17 | background-color: #ECECEC;
18 | z-index: 10;
19 | }
20 |
21 | nav .page-nav {
22 | height: 28px;
23 | line-height: 28px;
24 | padding-left: 25px;
25 | }
26 | nav .page-nav:nth-child(even) {
27 | background-color: #F0F0F0;
28 | }
29 | nav .page-nav.active {
30 | background-color: #6D9EE1;
31 | color: white;
32 | }
33 |
34 | .page-viewer {
35 | position: absolute;
36 | top: 0;
37 | left: 245px;
38 | width: 100%;
39 | height: 100%;
40 | }
41 |
42 | .artboard {
43 | background: white;
44 | box-shadow: 0 0 5px 0 rgba(0,0,0,0.2);
45 | }
46 | .artboard .artboard-name {
47 | position: absolute;
48 | top: -25px;
49 | font-size: 13px;
50 | }
51 | .artboard .artboard-contents {
52 | position: relative;
53 | width: 100%;
54 | height: 100%;
55 | overflow: hidden;
56 | }
57 |
58 | p {
59 | margin: 0;
60 | }
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sketch Web Viewer
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/target/document.json:
--------------------------------------------------------------------------------
1 | {"_class":"document","do_objectID":"D71ED31A-157A-4533-9B4C-3351905FC972","assets":{"_class":"assetCollection","colors":[],"gradients":[],"imageCollection":{"_class":"imageCollection","images":{}},"images":[]},"currentPageIndex":1,"enableLayerInteraction":true,"enableSliceInteraction":true,"foreignSymbols":[],"layerStyles":{"_class":"sharedStyleContainer","objects":[{"_class":"sharedStyle","do_objectID":"FFE1F7A6-0F19-4AA4-B326-E98F72E88F89","name":"Avatar Styling","value":{"_class":"style","borders":[{"_class":"border","isEnabled":false,"color":{"_class":"color","alpha":0.1,"blue":1,"green":1,"red":1},"fillType":0,"position":1,"thickness":1}],"endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0.67135115527602085}","gradientType":1,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":0,"blue":1,"green":1,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":0,"blue":1,"green":1,"red":1},"position":0.6000000000000001},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"position":1}],"to":"{0.5, 1.5159143518518519}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":1,"color":{"_class":"color","alpha":0.2,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0},{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.1023833786231884,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":-1,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":5,"color":{"_class":"color","alpha":0.5,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"FFE1F7A6-0F19-4AA4-B326-E98F72E88F89","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"B96F8A35-2C39-4280-9499-579762F0E4E7","name":"Activity \/ Check In","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.2901960784313726,"green":0.5647058823529412,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.06666666666666668,"green":0.4,"red":0.9294117647058824},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.3,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"B96F8A35-2C39-4280-9499-579762F0E4E7","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"E154F4C7-711B-49D0-A77E-6C02D09AD8DF","name":"Activity \/ Meal","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.9764705882352941,"green":0.8078431372549019,"red":0.211764705882353},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.9568627450980393,"green":0.6156862745098044,"red":0.1529411764705882},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.3,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"E154F4C7-711B-49D0-A77E-6C02D09AD8DF","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"E927DCEB-46A4-4ABE-9980-688DE16F0133","name":"Activity \/ Run","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.374436616897583,"green":0.317264050245285,"red":0.9606321454048157},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.1070586666464806,"green":0.01541697140783072,"red":0.623913049697876},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.2995640851449275,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"E927DCEB-46A4-4ABE-9980-688DE16F0133","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"D83E1BB5-93A5-475A-BD99-CC9658F49932","name":"Activity \/ Walk","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.4313725490196079,"green":0.9529411764705882,"red":0.4509803921568628},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.3137254901960784,"green":0.9019607843137255,"red":0.3176470588235293},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1},{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":0.5999999642372131},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.5058823529411764,"green":1,"red":0.403921568627451},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.1215686274509805,"green":0.7058823529411765,"red":0.003921568627450966},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.3,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"D83E1BB5-93A5-475A-BD99-CC9658F49932","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"59D33591-9241-422C-9478-58AF9AC868BB","name":"Activity \/ Photo","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.9450980392156862,"green":0.6784313725490198,"red":0.7215686274509805},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.8941176470588236,"green":0.4588235294117647,"red":0.4823529411764707},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.3,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"59D33591-9241-422C-9478-58AF9AC868BB","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"DAD83B83-57CB-4EF4-94B8-067F12FD172A","name":"Activty \/ Sleep","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.3725490196078432,"green":0.9058823529411765,"red":0.9921568627450981},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.2549019607843137,"green":0.803921568627451,"red":0.9921568627450981},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":2,"color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":-1,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":1,"color":{"_class":"color","alpha":0.1,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"DAD83B83-57CB-4EF4-94B8-067F12FD172A","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"587D3A9E-A88A-4FA5-A8D5-AE12C48517CB","name":"Activity \/ Burn","value":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":1,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.4862745098039215,"green":0.407843137254902,"red":0.9882352941176471},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":1,"blue":0.3333333333333336,"green":0.2745098039215687,"red":0.984313725490196},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"innerShadows":[{"_class":"innerShadow","isEnabled":true,"blurRadius":0,"color":{"_class":"color","alpha":0.4,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"offsetX":0,"offsetY":0.5,"spread":0}],"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":true,"blurRadius":3,"color":{"_class":"color","alpha":0.3,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"587D3A9E-A88A-4FA5-A8D5-AE12C48517CB","startDecorationType":0}},{"_class":"sharedStyle","do_objectID":"56FFB74F-9970-4865-9112-D3D3A60D6FCA","name":"Background","value":{"_class":"style","blur":{"_class":"blur","isEnabled":true,"center":"{0.5, 0.5}","motionAngle":0,"radius":10,"type":3},"endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":0.5,"blue":0.09188988095238096,"green":0.09188988095238096,"red":0.09188988095238096},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1},{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":7,"opacity":1},"fillType":0,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":0,"green":0,"red":0},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1},{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.2389987244897959,"green":0.2389987244897959,"red":0.2389987244897959},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"fillType":0,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":0,"green":0,"red":0},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"sharedObjectID":"56FFB74F-9970-4865-9112-D3D3A60D6FCA","startDecorationType":0}}]},"layerSymbols":{"_class":"symbolContainer","objects":[]},"layerTextStyles":{"_class":"sharedTextStyleContainer","objects":[{"_class":"sharedStyle","do_objectID":"5200D98F-163B-4F2F-92FC-F38125368831","name":"Title — Menubar","value":{"_class":"style","blur":{"_class":"blur","isEnabled":false,"center":"{0.5, 0.5}","motionAngle":0,"radius":5,"type":0},"endDecorationType":0,"miterLimit":10,"shadows":[{"_class":"shadow","isEnabled":false,"blurRadius":1,"color":{"_class":"color","alpha":0.1494282155797101,"blue":0,"green":0,"red":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":1},"offsetX":0,"offsetY":1,"spread":0}],"sharedObjectID":"5200D98F-163B-4F2F-92FC-F38125368831","startDecorationType":0,"textStyle":{"_class":"textStyle","encodedAttributes":{"MSAttributedStringFontAttribute":{"_archive":"YnBsaXN0MDDUAQIDBAUGJidYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKkHCA0XGBkaGyJVJG51bGzSCQoLDFYkY2xhc3NfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4AIgALTDg8JEBMWV05TLmtleXNaTlMub2JqZWN0c6IREoADgASiFBWABYAGgAdfEBNOU0ZvbnRTaXplQXR0cmlidXRlXxATTlNGb250TmFtZUF0dHJpYnV0ZSNAMQAAAAAAAF8QEkF2ZW5pck5leHQtUmVndWxhctIcHR4fWiRjbGFzc25hbWVYJGNsYXNzZXNfEBNOU011dGFibGVEaWN0aW9uYXJ5ox4gIVxOU0RpY3Rpb25hcnlYTlNPYmplY3TSHB0jJF8QEE5TRm9udERlc2NyaXB0b3KiJSFfEBBOU0ZvbnREZXNjcmlwdG9yXxAPTlNLZXllZEFyY2hpdmVy0SgpVHJvb3SAAQAIABEAGgAjAC0AMgA3AEEARwBMAFMAcAByAHQAewCDAI4AkQCTAJUAmACaAJwAngC0AMoA0wDoAO0A+AEBARcBGwEoATEBNgFJAUwBXwFxAXQBeQAAAAAAAAIBAAAAAAAAACoAAAAAAAAAAAAAAAAAAAF7"},"NSColor":{"_archive":"YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMSAxIDEAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGarK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY="},"NSKern":0,"NSParagraphStyle":{"_archive":"YnBsaXN0MDDUAQIDBAUGFBVYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA1VJG51bGzSCQoLDFYkY2xhc3NaTlNUYWJTdG9wc4ACgADSDg8QEVokY2xhc3NuYW1lWCRjbGFzc2VzXxAXTlNNdXRhYmxlUGFyYWdyYXBoU3R5bGWjEBITXxAQTlNQYXJhZ3JhcGhTdHlsZVhOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEWF1Ryb290gAEIERojLTI3O0FGTVhaXGFsdY+Tpq\/BxMkAAAAAAAABAQAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAyw=="}}}}},{"_class":"sharedStyle","do_objectID":"18DAA8D8-2FAE-4911-924E-1F68B1100E4E","name":"Signature","value":{"_class":"style","blur":{"_class":"blur","isEnabled":false,"center":"{0.5, 0.5}","motionAngle":0,"radius":5,"type":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":0.800000011920929},"endDecorationType":0,"miterLimit":10,"sharedObjectID":"18DAA8D8-2FAE-4911-924E-1F68B1100E4E","startDecorationType":0,"textStyle":{"_class":"textStyle","encodedAttributes":{"MSAttributedStringFontAttribute":{"_archive":"YnBsaXN0MDDUAQIDBAUGJidYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKkHCA0XGBkaGyJVJG51bGzSCQoLDFYkY2xhc3NfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4AIgALTDg8JEBMWV05TLmtleXNaTlMub2JqZWN0c6IREoADgASiFBWABYAGgAdfEBNOU0ZvbnRTaXplQXR0cmlidXRlXxATTlNGb250TmFtZUF0dHJpYnV0ZSNAKAAAAAAAAF8QEkF2ZW5pck5leHQtUmVndWxhctIcHR4fWiRjbGFzc25hbWVYJGNsYXNzZXNfEBNOU011dGFibGVEaWN0aW9uYXJ5ox4gIVxOU0RpY3Rpb25hcnlYTlNPYmplY3TSHB0jJF8QEE5TRm9udERlc2NyaXB0b3KiJSFfEBBOU0ZvbnREZXNjcmlwdG9yXxAPTlNLZXllZEFyY2hpdmVy0SgpVHJvb3SAAQAIABEAGgAjAC0AMgA3AEEARwBMAFMAcAByAHQAewCDAI4AkQCTAJUAmACaAJwAngC0AMoA0wDoAO0A+AEBARcBGwEoATEBNgFJAUwBXwFxAXQBeQAAAAAAAAIBAAAAAAAAACoAAAAAAAAAAAAAAAAAAAF7"},"NSKern":0,"NSColor":{"_archive":"YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMSAxIDEAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGarK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY="},"NSParagraphStyle":{"_archive":"YnBsaXN0MDDUAQIDBAUGGRpYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCBJVJG51bGzVCQoLDA0ODxAREFtOU0FsaWdubWVudFpOU1RhYlN0b3BzXxAPTlNNaW5MaW5lSGVpZ2h0ViRjbGFzc18QD05TTWF4TGluZUhlaWdodBAEgAAjQC4AAAAAAACAAtITFBUWWiRjbGFzc25hbWVYJGNsYXNzZXNfEBdOU011dGFibGVQYXJhZ3JhcGhTdHlsZaMVFxhfEBBOU1BhcmFncmFwaFN0eWxlWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RscVHJvb3SAAQAIABEAGgAjAC0AMgA3ADsAQQBMAFgAYwB1AHwAjgCQAJIAmwCdAKIArQC2ANAA1ADnAPABAgEFAQoAAAAAAAACAQAAAAAAAAAdAAAAAAAAAAAAAAAAAAABDA=="}}}}},{"_class":"sharedStyle","do_objectID":"9B41A88C-50F4-417B-A6BD-EC96D3B93C86","name":"Sub Text","value":{"_class":"style","blur":{"_class":"blur","isEnabled":false,"center":"{0.5, 0.5}","motionAngle":0,"radius":5,"type":0},"endDecorationType":0,"miterLimit":10,"sharedObjectID":"9B41A88C-50F4-417B-A6BD-EC96D3B93C86","startDecorationType":0,"textStyle":{"_class":"textStyle","encodedAttributes":{"MSAttributedStringFontAttribute":{"_archive":"YnBsaXN0MDDUAQIDBAUGJidYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKkHCA0XGBkaGyJVJG51bGzSCQoLDFYkY2xhc3NfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4AIgALTDg8JEBMWV05TLmtleXNaTlMub2JqZWN0c6IREoADgASiFBWABYAGgAdfEBNOU0ZvbnRTaXplQXR0cmlidXRlXxATTlNGb250TmFtZUF0dHJpYnV0ZSNAKAAAAAAAAF8QEkF2ZW5pck5leHQtUmVndWxhctIcHR4fWiRjbGFzc25hbWVYJGNsYXNzZXNfEBNOU011dGFibGVEaWN0aW9uYXJ5ox4gIVxOU0RpY3Rpb25hcnlYTlNPYmplY3TSHB0jJF8QEE5TRm9udERlc2NyaXB0b3KiJSFfEBBOU0ZvbnREZXNjcmlwdG9yXxAPTlNLZXllZEFyY2hpdmVy0SgpVHJvb3SAAQAIABEAGgAjAC0AMgA3AEEARwBMAFMAcAByAHQAewCDAI4AkQCTAJUAmACaAJwAngC0AMoA0wDoAO0A+AEBARcBGwEoATEBNgFJAUwBXwFxAXQBeQAAAAAAAAIBAAAAAAAAACoAAAAAAAAAAAAAAAAAAAF7"},"NSParagraphStyle":{"_archive":"YnBsaXN0MDDUAQIDBAUGGRpYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCBJVJG51bGzVCQoLDA0ODxAREFtOU0FsaWdubWVudFpOU1RhYlN0b3BzXxAPTlNNaW5MaW5lSGVpZ2h0ViRjbGFzc18QD05TTWF4TGluZUhlaWdodBAEgAAjQC4AAAAAAACAAtITFBUWWiRjbGFzc25hbWVYJGNsYXNzZXNfEBdOU011dGFibGVQYXJhZ3JhcGhTdHlsZaMVFxhfEBBOU1BhcmFncmFwaFN0eWxlWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RscVHJvb3SAAQAIABEAGgAjAC0AMgA3ADsAQQBMAFgAYwB1AHwAjgCQAJIAmwCdAKIArQC2ANAA1ADnAPABAgEFAQoAAAAAAAACAQAAAAAAAAAdAAAAAAAAAAAAAAAAAAABDA=="},"NSKern":0,"NSColor":{"_archive":"YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NKMSAxIDEgMC42ABABgALSEBESE1okY2xhc3NuYW1lWCRjbGFzc2VzV05TQ29sb3KiEhRYTlNPYmplY3RfEA9OU0tleWVkQXJjaGl2ZXLRFxhUcm9vdIABCBEaIy0yNztBSE5bYm1vcXaBipKVnrCzuAAAAAAAAAEBAAAAAAAAABkAAAAAAAAAAAAAAAAAAAC6"}}}}}]},"pages":[{"_class":"MSJSONFileReference","_ref_class":"MSImmutablePage","_ref":"pages\/9B2D6A75-D2C0-40F3-88AB-AE4BD625E520"},{"_class":"MSJSONFileReference","_ref_class":"MSImmutablePage","_ref":"pages\/1C01EF08-06C2-469A-8EB7-0BF12367FF6E"}]}
--------------------------------------------------------------------------------
/public/target/images/00f051d3a27fec14ba725121eb2bffb3ac1c3e02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/00f051d3a27fec14ba725121eb2bffb3ac1c3e02.png
--------------------------------------------------------------------------------
/public/target/images/0c2421c7228cf5e9d8133b6f598461a7cf439fc5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/0c2421c7228cf5e9d8133b6f598461a7cf439fc5.png
--------------------------------------------------------------------------------
/public/target/images/29c43ee866210c6174a4f800798b066f50e3b178.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/29c43ee866210c6174a4f800798b066f50e3b178.png
--------------------------------------------------------------------------------
/public/target/images/54099fda567d9ed2a6548505a921cb89177357b3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/54099fda567d9ed2a6548505a921cb89177357b3.png
--------------------------------------------------------------------------------
/public/target/images/65d57b712c0dfc95448315b82d457f0a29dd4c8c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/65d57b712c0dfc95448315b82d457f0a29dd4c8c.png
--------------------------------------------------------------------------------
/public/target/images/82fec9dc20c85608ebb2cc01f678364b4fd53107.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/82fec9dc20c85608ebb2cc01f678364b4fd53107.png
--------------------------------------------------------------------------------
/public/target/images/841a9c5ab29068b5f372d9e171dad1b3e4a62950.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/841a9c5ab29068b5f372d9e171dad1b3e4a62950.png
--------------------------------------------------------------------------------
/public/target/images/a1948e015ba95991c3a04a47e19f0497913a5997.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/a1948e015ba95991c3a04a47e19f0497913a5997.png
--------------------------------------------------------------------------------
/public/target/images/a683c9b5c00b8e1d441297f1df1c4fe2d4426c56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/a683c9b5c00b8e1d441297f1df1c4fe2d4426c56.png
--------------------------------------------------------------------------------
/public/target/images/b0ec6a85ff35a5223bb0c0dfefd749413050e777.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/b0ec6a85ff35a5223bb0c0dfefd749413050e777.png
--------------------------------------------------------------------------------
/public/target/images/b4913550b705b27fb487bb41aac8e4150a0c7f53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/b4913550b705b27fb487bb41aac8e4150a0c7f53.png
--------------------------------------------------------------------------------
/public/target/images/b771bedd82473fdbc03dad41679d4b710eaa9615.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/b771bedd82473fdbc03dad41679d4b710eaa9615.png
--------------------------------------------------------------------------------
/public/target/images/c55edac9c53afcc5a37bc81c37a9d8329846041e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/c55edac9c53afcc5a37bc81c37a9d8329846041e.png
--------------------------------------------------------------------------------
/public/target/images/cd36c97a217daeae79b3c00b3fa6678f2ac98ac9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/cd36c97a217daeae79b3c00b3fa6678f2ac98ac9.png
--------------------------------------------------------------------------------
/public/target/images/dbb5e35ccbb8f4dea5f95b79f1b44dcb442ffbe2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/dbb5e35ccbb8f4dea5f95b79f1b44dcb442ffbe2.png
--------------------------------------------------------------------------------
/public/target/images/e607a3e5caf014ff210d1af02a2a2e9aae283ba7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/images/e607a3e5caf014ff210d1af02a2a2e9aae283ba7.png
--------------------------------------------------------------------------------
/public/target/meta.json:
--------------------------------------------------------------------------------
1 | {"commit":"10b2b021ddaac63eb3f52ce0b42edfb625ca194b","appVersion":"44.1","build":41455,"app":"com.bohemiancoding.sketch3","pagesAndArtboards":{"9B2D6A75-D2C0-40F3-88AB-AE4BD625E520":{"name":"Page 1","artboards":{"B8ECDD04-C5B6-496B-B640-7743425317F7":{"name":"Detail View"},"98555297-4575-40EC-AB67-B1C3918F163C":{"name":"History"},"0D7CFF39-0795-427A-A195-749BCFB97FA4":{"name":"Timeline"},"DDD74CEB-8B48-487D-92D8-4FD3BDB04411":{"name":"Profile"},"10E1A27E-C298-40A5-8547-A62A031514C6":{"name":"Friends"},"EE2328CD-533A-494C-B438-085002962A3B":{"name":"Menu"}}},"1C01EF08-06C2-469A-8EB7-0BF12367FF6E":{"name":"Symbols","artboards":{"32FD6548-E53C-4615-88E6-117FBDACCF42":{"name":"Background"},"72C43561-2764-4D7C-8C6A-5A4B3FED4727":{"name":"Time"},"3C69524A-C8CD-4847-B6C1-62807F339A48":{"name":"Status Bar"}}}},"fonts":["STHeitiSC-Light","AvenirNext-Medium","HelveticaNeue","AvenirNext-Regular"],"created":{"app":"com.bohemiancoding.sketch3","commit":"10b2b021ddaac63eb3f52ce0b42edfb625ca194b","build":41455,"appVersion":"44.1","variant":"NONAPPSTORE","version":91},"version":91,"saveHistory":["NONAPPSTORE.41455"],"autosaved":1,"variant":"NONAPPSTORE"}
--------------------------------------------------------------------------------
/public/target/pages/1C01EF08-06C2-469A-8EB7-0BF12367FF6E.json:
--------------------------------------------------------------------------------
1 | {"_class":"page","do_objectID":"1C01EF08-06C2-469A-8EB7-0BF12367FF6E","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":300,"width":300,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Symbols","nameIsFixed":false,"resizingConstraint":0,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"hasClickThrough":true,"layers":[{"_class":"symbolMaster","do_objectID":"72C43561-2764-4D7C-8C6A-5A4B3FED4727","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":15,"width":95,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Time","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"hasClickThrough":true,"layers":[{"_class":"text","do_objectID":"B7822ED4-7966-45E9-9783-D993FBE9E971","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":15,"width":75.5,"x":19.5,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"18:34","nameIsFixed":false,"originalObjectID":"B7822ED4-7966-45E9-9783-D993FBE9E971","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","do_objectID":"2E8E469D-ED5D-4FDD-B2AE-B55ED6A0C913","blur":{"_class":"blur","isEnabled":false,"center":"{0.5, 0.5}","motionAngle":0,"radius":5,"type":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":0.800000011920929},"endDecorationType":0,"miterLimit":10,"sharedObjectID":"18DAA8D8-2FAE-4911-924E-1F68B1100E4E","startDecorationType":0,"textStyle":{"_class":"textStyle","encodedAttributes":{"MSAttributedStringFontAttribute":{"_archive":"YnBsaXN0MDDUAQIDBAUGJidYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKkHCA0XGBkaGyJVJG51bGzSCQoLDFYkY2xhc3NfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4AIgALTDg8JEBMWV05TLmtleXNaTlMub2JqZWN0c6IREoADgASiFBWABYAGgAdfEBNOU0ZvbnRTaXplQXR0cmlidXRlXxATTlNGb250TmFtZUF0dHJpYnV0ZSNAKAAAAAAAAF8QEkF2ZW5pck5leHQtUmVndWxhctIcHR4fWiRjbGFzc25hbWVYJGNsYXNzZXNfEBNOU011dGFibGVEaWN0aW9uYXJ5ox4gIVxOU0RpY3Rpb25hcnlYTlNPYmplY3TSHB0jJF8QEE5TRm9udERlc2NyaXB0b3KiJSFfEBBOU0ZvbnREZXNjcmlwdG9yXxAPTlNLZXllZEFyY2hpdmVy0SgpVHJvb3SAAQAIABEAGgAjAC0AMgA3AEEARwBMAFMAcAByAHQAewCDAI4AkQCTAJUAmACaAJwAngC0AMoA0wDoAO0A+AEBARcBGwEoATEBNgFJAUwBXwFxAXQBeQAAAAAAAAIBAAAAAAAAACoAAAAAAAAAAAAAAAAAAAF7"},"NSParagraphStyle":{"_archive":"YnBsaXN0MDDUAQIDBAUGGRpYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCBJVJG51bGzVCQoLDA0ODxAREFtOU0FsaWdubWVudFpOU1RhYlN0b3BzXxAPTlNNaW5MaW5lSGVpZ2h0ViRjbGFzc18QD05TTWF4TGluZUhlaWdodBAEgAAjQC4AAAAAAACAAtITFBUWWiRjbGFzc25hbWVYJGNsYXNzZXNfEBdOU011dGFibGVQYXJhZ3JhcGhTdHlsZaMVFxhfEBBOU1BhcmFncmFwaFN0eWxlWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RscVHJvb3SAAQAIABEAGgAjAC0AMgA3ADsAQQBMAFgAYwB1AHwAjgCQAJIAmwCdAKIArQC2ANAA1ADnAPABAgEFAQoAAAAAAAACAQAAAAAAAAAdAAAAAAAAAAAAAAAAAAABDA=="},"NSKern":0,"NSColor":{"_archive":"YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMSAxIDEAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGarK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY="}}}},"attributedString":{"_class":"MSAttributedString","archivedAttributedString":{"_archive":"YnBsaXN0MDDUAQIDBAUGWltYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QFwcIDxAeHyAhIisyNj4\/QEFCRkpLUVRWVSRudWxs0wkKCwwNDlhOU1N0cmluZ1YkY2xhc3NcTlNBdHRyaWJ1dGVzgAKAFoADVTE4OjM00xESChMYHVdOUy5rZXlzWk5TLm9iamVjdHOkFBUWF4AEgAWABoAHpBkaGxyACIAKgBKAE4AVXxAQTlNQYXJhZ3JhcGhTdHlsZV8QH01TQXR0cmlidXRlZFN0cmluZ0ZvbnRBdHRyaWJ1dGVWTlNLZXJuV05TQ29sb3LVIyQlCiYnKCkqKVtOU0FsaWdubWVudFpOU1RhYlN0b3BzXxAPTlNNaW5MaW5lSGVpZ2h0XxAPTlNNYXhMaW5lSGVpZ2h0EASAACNALgAAAAAAAIAJ0iwtLi9aJGNsYXNzbmFtZVgkY2xhc3Nlc18QF05TTXV0YWJsZVBhcmFncmFwaFN0eWxloy4wMV8QEE5TUGFyYWdyYXBoU3R5bGVYTlNPYmplY3TSCjM0NV8QGk5TRm9udERlc2NyaXB0b3JBdHRyaWJ1dGVzgBGAC9MREgo3Oj2iODmADIANojs8gA6AD4AQXxATTlNGb250U2l6ZUF0dHJpYnV0ZV8QE05TRm9udE5hbWVBdHRyaWJ1dGUjQCgAAAAAAABfEBJBdmVuaXJOZXh0LVJlZ3VsYXLSLC1DRF8QE05TTXV0YWJsZURpY3Rpb25hcnmjQ0UxXE5TRGljdGlvbmFyedIsLUdIXxAQTlNGb250RGVzY3JpcHRvcqJJMV8QEE5TRm9udERlc2NyaXB0b3IjAAAAAAAAAADTTE0KTk9QVU5TUkdCXE5TQ29sb3JTcGFjZUYxIDEgMQAQAYAU0iwtUlNXTlNDb2xvcqJSMdIsLUVVokUx0iwtV1hfEBJOU0F0dHJpYnV0ZWRTdHJpbmeiWTFfEBJOU0F0dHJpYnV0ZWRTdHJpbmdfEA9OU0tleWVkQXJjaGl2ZXLRXF1Ucm9vdIABAAgAEQAaACMALQAyADcAUQBXAF4AZwBuAHsAfQB\/AIEAhwCOAJYAoQCmAKgAqgCsAK4AswC1ALcAuQC7AL0A0ADyAPkBAQEMARgBIwE1AUcBSQFLAVQBVgFbAWYBbwGJAY0BoAGpAa4BywHNAc8B1gHZAdsB3QHgAeIB5AHmAfwCEgIbAjACNQJLAk8CXAJhAnQCdwKKApMCmgKgAq0CtAK2ArgCvQLFAsgCzQLQAtUC6gLtAwIDFAMXAxwAAAAAAAACAQAAAAAAAABeAAAAAAAAAAAAAAAAAAADHg=="}},"automaticallyDrawOnUnderlyingPath":false,"dontSynchroniseWithSymbol":true,"glyphBounds":"{{0, 0}, {75.5, 15}}","heightIsClipped":false,"lineSpacingBehaviour":0,"textBehaviour":1},{"_class":"shapeGroup","do_objectID":"44DF4B03-FB71-4A7D-B63A-068A57DC7A33","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":11,"width":11,"x":0,"y":1.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Fill 2","nameIsFixed":false,"originalObjectID":"44DF4B03-FB71-4A7D-B63A-068A57DC7A33","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","blur":{"_class":"blur","isEnabled":false,"center":"{0.5, 0.5}","motionAngle":0,"radius":5,"type":0},"contextSettings":{"_class":"graphicsContextSettings","blendMode":0,"opacity":0.800000011920929},"endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"shapePath","do_objectID":"54CECF01-12FB-45E0-896C-BC56D3DDA068","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":11.00150000000002,"width":10.99900000000002,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77611601054641066, 0}","curveMode":4,"curveTo":"{0.49995454132193945, 0}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.49995454132193945, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.77616688633367914}","curveMode":2,"curveTo":"{1, 0.22387856201427236}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.49997727582602169}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22383853077552357, 1}","curveMode":2,"curveTo":"{0.77611601054641066, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49995454132193945, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.22387856201427236}","curveMode":2,"curveTo":"{0, 0.77616688633367914}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.49997727582602169}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.49995454132193945, 0}","curveMode":4,"curveTo":"{0.22383853077552357, 0}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.49995454132193945, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.49995454132193945, 0}","curveMode":1,"curveTo":"{0.49995454132193945, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0.49995454132193945, 0}"}]}},{"_class":"shapePath","do_objectID":"28C6696F-A087-48E8-A769-D84927B366B7","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":8.148500000000013,"width":8.146999999999991,"x":1.425999999999988,"y":1.427000000000021},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22378351843897912}","curveMode":4,"curveTo":"{1, 0.49990795851997516}","hasCurveFrom":true,"hasCurveTo":false,"point":"{1, 0.49990795851997516}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.2238860930403847, 0}","curveMode":2,"curveTo":"{0.77611390695962224, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.4999386277157285, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77609375958765447}","curveMode":2,"curveTo":"{0, 0.22378351843897912}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.49990795851997516}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77611390695962224, 1}","curveMode":2,"curveTo":"{0.2238860930403847, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.4999386277157285, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.49990795851997516}","curveMode":4,"curveTo":"{1, 0.77609375958765447}","hasCurveFrom":false,"hasCurveTo":true,"point":"{1, 0.49990795851997516}"}]}},{"_class":"shapePath","do_objectID":"2DF3BB46-0B8C-41F2-B065-073567879780","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":3.119000000000028,"width":3.760499999999979,"x":5,"y":2.880999999999972},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.93010580314202762}","curveMode":4,"curveTo":"{1, 0.85379929464571558}","hasCurveFrom":true,"hasCurveTo":false,"point":"{1, 0.85379929464571558}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.89017417896555961, 1}","curveMode":4,"curveTo":"{0.95200106368834037, 0.99182430266110222}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.89017417896555961, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.028852546203960345, 0.99679384418081196}","curveMode":4,"curveTo":"{0.062624650977264495, 1}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.062624650977264495, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.92225072138505781}","curveMode":4,"curveTo":"{0.0021273766786282142, 0.96344982366141974}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0, 0.92225072138505781}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.92016672010259015}","curveMode":1,"curveTo":"{0, 0.92016672010259015}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0.92016672010259015}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.0023932987634510726, 0.063802500801542317}","curveMode":4,"curveTo":"{0, 0.14395639628086188}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0, 0.14395639628086188}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.19186278420422706, 0}","curveMode":2,"curveTo":"{0.056508443026187592, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.12365376944554649, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.24717457784868155, 0.14892593780057153}","curveMode":4,"curveTo":"{0.24717457784868155, 0.066688041038784257}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.24717457784868155, 0.14892593780057153}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.24717457784868155, 0.62103238217377177}","curveMode":4,"curveTo":"{0.24717457784868155, 0.61974991984609662}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.24717457784868155, 0.61974991984609662}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.2469086557638587, 0.62536069252964388}","curveMode":2,"curveTo":"{0.2469086557638587, 0.62247515229240191}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.2469086557638587, 0.62391792241103194}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.24717457784868155, 0.6277653093940212}","curveMode":4,"curveTo":"{0.24717457784868155, 0.62680346264827391}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.24717457784868155, 0.6277653093940212}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.24890307140007548, 0.66928502725231109}","curveMode":4,"curveTo":"{0.24717457784868155, 0.62888746393074169}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.24717457784868155, 0.62888746393074169}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.30807073527456724, 0.70487335684514407}","curveMode":4,"curveTo":"{0.27509639675574676, 0.70086566207116352}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.30807073527456724, 0.70487335684514407}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.9493418428400513, 0.71080474511060099}","curveMode":4,"curveTo":"{0.88525462039623093, 0.70551458800896338}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.88525462039623093, 0.70551458800896338}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.85379929464571558}","curveMode":4,"curveTo":"{1, 0.77508816928502611}","hasCurveFrom":false,"hasCurveTo":true,"point":"{1, 0.85379929464571558}"}]}}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1}],"backgroundColor":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"hasBackgroundColor":false,"horizontalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInExport":true,"includeInCloudUpload":true,"resizesContent":false,"verticalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInInstance":false,"symbolID":"2B6C176D-024E-45BE-8154-2C32C4C73591","changeIdentifier":0},{"_class":"symbolMaster","do_objectID":"3C69524A-C8CD-4847-B6C1-62807F339A48","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":20,"width":320,"x":370,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Status Bar","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"hasClickThrough":true,"layers":[{"_class":"shapeGroup","do_objectID":"D82A79A8-9AB0-4FCD-B85E-5044E5F56294","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":20,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":false,"layerListExpandedType":0,"name":"Bar BG","nameIsFixed":true,"originalObjectID":"D82A79A8-9AB0-4FCD-B85E-5044E5F56294","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.8470588235294118,"green":0.8470588235294118,"red":0.8470588235294118},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"rectangle","do_objectID":"8FAE3DF4-30EE-43A4-8968-3BD4D5284460","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":20,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":0,"hasConvertedToNewRoundCorners":true}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1},{"_class":"shapeGroup","do_objectID":"DBDAF966-4DE7-4280-B616-786FB42C73E1","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":true,"height":5.5,"width":33.5,"x":6.5,"y":7.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Signal","nameIsFixed":true,"originalObjectID":"DBDAF966-4DE7-4280-B616-786FB42C73E1","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"oval","do_objectID":"BAC0EA9E-305D-43D2-9C78-DAC3961C5E18","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":5.5,"width":5.5,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}},{"_class":"oval","do_objectID":"5F00CFBB-6BF6-4E7E-B29D-FC29E1C84EDA","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":5.5,"width":5.5,"x":7,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Oval 2","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}},{"_class":"oval","do_objectID":"85D4AB03-E24B-459E-B56B-0855E820249D","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":5.5,"width":5.5,"x":14,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Oval 3","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}},{"_class":"oval","do_objectID":"554B9656-29AB-411F-8442-EE56BFB44188","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":5.5,"width":5.5,"x":21,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Oval 4","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}},{"_class":"oval","do_objectID":"B02BF479-A0C2-4ECD-8B0A-01F67F39265A","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":5.5,"width":5.5,"x":28,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}},{"_class":"oval","do_objectID":"A7617A07-C81A-41F4-BF9B-AD8D9F06AA51","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":3.5,"width":3.5,"x":29,"y":1},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Oval 6","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.77614237490000004, 1}","curveMode":2,"curveTo":"{0.22385762510000001, 1}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.22385762510000001}","curveMode":2,"curveTo":"{1, 0.77614237490000004}","hasCurveFrom":true,"hasCurveTo":true,"point":"{1, 0.5}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22385762510000001, 0}","curveMode":2,"curveTo":"{0.77614237490000004, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.5, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.77614237490000004}","curveMode":2,"curveTo":"{0, 0.22385762510000001}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0, 0.5}"}]}}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1},{"_class":"shapeGroup","do_objectID":"19130C23-1692-4A3A-80A9-C34F587ED62A","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":true,"height":9,"width":12.82579776775265,"x":44.5,"y":5.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Wifi","nameIsFixed":true,"originalObjectID":"19130C23-1692-4A3A-80A9-C34F587ED62A","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"shapePath","do_objectID":"A4D0DD07-8A71-49FA-A5D6-82857436E6C7","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":3.714744149075145,"width":12.82579776775265,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.86975371293899018, 0.27410397928373892}","curveMode":4,"curveTo":"{0.99999999999999778, 0.71814200325240374}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.99999999999999778, 0.71814200325240374}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.30248724341834243, 0}","curveMode":2,"curveTo":"{0.69079434564498121, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49329270402552861, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.67356751483908628}","curveMode":4,"curveTo":"{0.12898804111163287, 0.25583238514402074}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0, 0.67356751483908628}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.1895073019564161, 0.61602065440235398}","curveMode":4,"curveTo":"{0.081604772375830872, 0.9629371194066173}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.081604772375830872, 0.9629371194066173}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.65795261826933005, 0.40379631538647237}","curveMode":2,"curveTo":"{0.33422184760108858, 0.40379631538647237}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49329270402552861, 0.40379631538647237}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.91621700311673504, 1}","curveMode":4,"curveTo":"{0.80722966206833502, 0.63119592816964709}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.91621700311673504, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0.71814200325240762}","curveMode":1,"curveTo":"{1, 0.71814200325240762}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0.71814200325240762}"}]}},{"_class":"shapePath","do_objectID":"03786B48-5EE5-49B7-A2B1-4A0711A0BFC9","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":2.914926729197475,"width":8.583116912662362,"x":2.093398610018511,"y":3},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.86891842214025106, 0.2302962824523212}","curveMode":4,"curveTo":"{1, 0.60443586147635719}","hasCurveFrom":true,"hasCurveTo":false,"point":"{1, 0.60443586147635719}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.30295354709991712, 0}","curveMode":2,"curveTo":"{0.69020917130406401, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49323269101406392, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.56677511616858245}","curveMode":4,"curveTo":"{0.12972055357861176, 0.21490205044615746}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0, 0.56677511616858245}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.22378888551236922, 0.68978075530978522}","curveMode":4,"curveTo":"{0.13353397595252781, 0.97059810460050377}","hasCurveFrom":true,"hasCurveTo":false,"point":"{0.13353397595252781, 0.97059810460050377}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.6397841432403153, 0.51459269455221379}","curveMode":2,"curveTo":"{0.35156278096290389, 0.51459269455221379}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49323269101406392, 0.51459269455221379}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.86212658372532425, 1}","curveMode":4,"curveTo":"{0.77146544790646576, 0.70206171996009181}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0.86212658372532425, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.99999999999994038, 0.60443586147657657}","curveMode":1,"curveTo":"{0.99999999999994038, 0.60443586147657657}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0.99999999999994038, 0.60443586147657657}"}]}},{"_class":"shapePath","do_objectID":"5F7C21BD-FD22-4E29-A422-BB66E33708D3","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":3,"width":4.130000886273919,"x":4.287318010799225,"y":6},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.87787464143979821, 0.12454554913771194}","curveMode":4,"curveTo":"{1, 0.32105158523517235}","hasCurveFrom":true,"hasCurveTo":false,"point":"{1, 0.32105158523517235}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.29796221239297044, 0}","curveMode":2,"curveTo":"{0.69642176500582831, 0}","hasCurveFrom":true,"hasCurveTo":true,"point":"{0.49383874422849089, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0.3017741517935093}","curveMode":4,"curveTo":"{0.12183995986546665, 0.1164359090148821}","hasCurveFrom":false,"hasCurveTo":true,"point":"{0, 0.3017741517935093}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.49383874422849089, 1}","curveMode":1,"curveTo":"{0.49383874422849089, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0.49383874422849089, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0.99999999999999656, 0.32105158523516764}","curveMode":1,"curveTo":"{0.99999999999999656, 0.32105158523516764}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0.99999999999999656, 0.32105158523516764}"}]}}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1},{"_class":"text","do_objectID":"B59F0615-F883-4A8E-992A-7F7518F78B84","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":14,"width":33,"x":143.5,"y":3.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Time","nameIsFixed":true,"originalObjectID":"B59F0615-F883-4A8E-992A-7F7518F78B84","resizingConstraint":47,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"userInfo":{"com.animaapp.stc-sketch-plugin":{"kModelPropertiesKey":{"isAutoWidth":1}}},"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0,"textStyle":{"_class":"textStyle","encodedAttributes":{"MSAttributedStringFontAttribute":{"_archive":"YnBsaXN0MDDUAQIDBAUGJidYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKkHCA0XGBkaGyJVJG51bGzSCQoLDFYkY2xhc3NfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4AIgALTDg8JEBMWV05TLmtleXNaTlMub2JqZWN0c6IREoADgASiFBWABYAGgAdfEBNOU0ZvbnRTaXplQXR0cmlidXRlXxATTlNGb250TmFtZUF0dHJpYnV0ZSNAKAAAAAAAAF1IZWx2ZXRpY2FOZXVl0hwdHh9aJGNsYXNzbmFtZVgkY2xhc3Nlc18QE05TTXV0YWJsZURpY3Rpb25hcnmjHiAhXE5TRGljdGlvbmFyeVhOU09iamVjdNIcHSMkXxAQTlNGb250RGVzY3JpcHRvcqIlIV8QEE5TRm9udERlc2NyaXB0b3JfEA9OU0tleWVkQXJjaGl2ZXLRKClUcm9vdIABAAgAEQAaACMALQAyADcAQQBHAEwAUwBwAHIAdAB7AIMAjgCRAJMAlQCYAJoAnACeALQAygDTAOEA5gDxAPoBEAEUASEBKgEvAUIBRQFYAWoBbQFyAAAAAAAAAgEAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAXQ="},"NSKern":0.4000000059604645,"NSColor":{"_archive":"YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMCAwIDAAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGarK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY="},"NSParagraphStyle":{"_archive":"YnBsaXN0MDDUAQIDBAUGFhdYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OWk5TVGFiU3RvcHNWJGNsYXNzW05TQWxpZ25tZW50gACAAhAC0hAREhNaJGNsYXNzbmFtZVgkY2xhc3Nlc18QF05TTXV0YWJsZVBhcmFncmFwaFN0eWxloxIUFV8QEE5TUGFyYWdyYXBoU3R5bGVYTlNPYmplY3RfEA9OU0tleWVkQXJjaGl2ZXLRGBlUcm9vdIABCBEaIy0yNztBSFNaZmhqbHF8hZ+jtr\/R1NkAAAAAAAABAQAAAAAAAAAaAAAAAAAAAAAAAAAAAAAA2w=="}}}},"attributedString":{"_class":"MSAttributedString","archivedAttributedString":{"_archive":"YnBsaXN0MDDUAQIDBAUGV1hYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QFwcIDxAeHyAhIiguMjo7PD0+QkZMUFFTVSRudWxs0wkKCwwNDlhOU1N0cmluZ1YkY2xhc3NcTlNBdHRyaWJ1dGVzgAKAFoADVTA5OjQy0xESChMYHVdOUy5rZXlzWk5TLm9iamVjdHOkFBUWF4AEgAWABoAHpBkaGxyACIAKgBKAFIAVV05TQ29sb3JfEB9NU0F0dHJpYnV0ZWRTdHJpbmdGb250QXR0cmlidXRlXxAQTlNQYXJhZ3JhcGhTdHlsZVZOU0tlcm7TIyQKJSYnVU5TUkdCXE5TQ29sb3JTcGFjZUYwIDAgMAAQAYAJ0ikqKyxaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9yoistWE5TT2JqZWN00govMDFfEBpOU0ZvbnREZXNjcmlwdG9yQXR0cmlidXRlc4ARgAvTERIKMzY5ojQ1gAyADaI3OIAOgA+AEF8QE05TRm9udFNpemVBdHRyaWJ1dGVfEBNOU0ZvbnROYW1lQXR0cmlidXRlI0AoAAAAAAAAXUhlbHZldGljYU5ldWXSKSo\/QF8QE05TTXV0YWJsZURpY3Rpb25hcnmjP0EtXE5TRGljdGlvbmFyedIpKkNEXxAQTlNGb250RGVzY3JpcHRvcqJFLV8QEE5TRm9udERlc2NyaXB0b3LTRwpISUpLWk5TVGFiU3RvcHNbTlNBbGlnbm1lbnSAAIATEALSKSpNTl8QF05TTXV0YWJsZVBhcmFncmFwaFN0eWxlo01PLV8QEE5TUGFyYWdyYXBoU3R5bGUjP9mZmaAAAADSKSpBUqJBLdIpKlRVXxASTlNBdHRyaWJ1dGVkU3RyaW5nolYtXxASTlNBdHRyaWJ1dGVkU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0VlaVHJvb3SAAQAIABEAGgAjAC0AMgA3AFEAVwBeAGcAbgB7AH0AfwCBAIcAjgCWAKEApgCoAKoArACuALMAtQC3ALkAuwC9AMUA5wD6AQEBCAEOARsBIgEkASYBKwE2AT8BRwFKAVMBWAF1AXcBeQGAAYMBhQGHAYoBjAGOAZABpgG8AcUB0wHYAe4B8gH\/AgQCFwIaAi0CNAI\/AksCTQJPAlECVgJwAnQChwKQApUCmAKdArICtQLKAtwC3wLkAAAAAAAAAgEAAAAAAAAAWwAAAAAAAAAAAAAAAAAAAuY="}},"automaticallyDrawOnUnderlyingPath":false,"dontSynchroniseWithSymbol":true,"glyphBounds":"{{0, 0}, {33, 14}}","heightIsClipped":false,"lineSpacingBehaviour":0,"textBehaviour":0},{"_class":"shapeGroup","do_objectID":"EB58FED1-6DF5-4113-B03A-2BB57983FC54","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":9.5,"width":24.5,"x":289.5,"y":5.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Battery","nameIsFixed":true,"originalObjectID":"EB58FED1-6DF5-4113-B03A-2BB57983FC54","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"rectangle","do_objectID":"494C5B49-634F-4862-9D6B-B37DDE167559","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":9.5,"width":22.5,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":1.5,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":1.5,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":1.5,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":1.5,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":1.5,"hasConvertedToNewRoundCorners":true},{"_class":"rectangle","do_objectID":"920894E6-74EB-45B8-941C-01E23659C73A","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":8.5,"width":21.5,"x":0.5,"y":0.5},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Rectangle 3","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":1,"hasConvertedToNewRoundCorners":true},{"_class":"rectangle","do_objectID":"81FF0102-3E40-49B0-A30B-BAB67281E4FF","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":3.5,"width":1.5,"x":23,"y":3},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Rectangle 4","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":true,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":1,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":0,"hasConvertedToNewRoundCorners":true},{"_class":"rectangle","do_objectID":"D29713ED-A8AD-4779-9E83-50AB8B44ACB2","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":7.5,"width":20.5,"x":1,"y":1},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Rectangle 5","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":0,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0.5,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":0.5,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":0.5,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":0.5,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":0.5,"hasConvertedToNewRoundCorners":true}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1}],"backgroundColor":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"hasBackgroundColor":false,"horizontalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInExport":true,"includeInCloudUpload":true,"resizesContent":false,"verticalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInInstance":false,"symbolID":"C145EF56-A58F-48C2-B117-F8A1573E5FC1","changeIdentifier":1},{"_class":"symbolMaster","do_objectID":"32FD6548-E53C-4615-88E6-117FBDACCF42","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":618},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Background","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"hasClickThrough":true,"layers":[{"_class":"group","do_objectID":"197E7749-006E-4ADC-8DEA-9699C78E4410","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Colour 2 + Bitmap","nameIsFixed":false,"originalObjectID":"197E7749-006E-4ADC-8DEA-9699C78E4410","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"shapeGroup","do_objectID":"F738FC46-5B38-42D3-A3AD-504954132C9C","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Colour 2","nameIsFixed":true,"originalObjectID":"F738FC46-5B38-42D3-A3AD-504954132C9C","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.847,"green":0.847,"red":0.847},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"rectangle","do_objectID":"3DBBB69D-27FE-42E2-8015-1EDF8CDB54C2","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":0,"hasConvertedToNewRoundCorners":true}],"clippingMaskMode":0,"hasClippingMask":true,"windingRule":1},{"_class":"bitmap","do_objectID":"FA3BBFFB-5998-4E2D-97D2-2EFAC99D67BB","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":true,"height":698,"width":1241,"x":-731,"y":-65},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":false,"layerListExpandedType":0,"name":"Bitmap","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"clippingMask":"{{0, 0}, {1, 1}}","fillReplacesImage":false,"image":{"_class":"MSJSONFileReference","_ref_class":"MSImageData","_ref":"images\/a683c9b5c00b8e1d441297f1df1c4fe2d4426c56"},"nineSliceCenterRect":"{{0, 0}, {0, 0}}","nineSliceScale":"{1, 1}"},{"_class":"bitmap","do_objectID":"0028627D-56E6-4892-889D-64814A850786","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":true,"height":584,"width":879,"x":-164,"y":-16},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":false,"layerListExpandedType":0,"name":"Bitmap","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","endDecorationType":0,"miterLimit":10,"startDecorationType":0},"clippingMask":"{{0, 0}, {1, 1}}","fillReplacesImage":false,"image":{"_class":"MSJSONFileReference","_ref_class":"MSImageData","_ref":"images\/e607a3e5caf014ff210d1af02a2a2e9aae283ba7"},"nineSliceCenterRect":"{{0, 0}, {0, 0}}","nineSliceScale":"{1, 1}"},{"_class":"bitmap","do_objectID":"2082C4EF-19D9-4A16-AC20-012E9B30C6F4","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":true,"height":654,"width":981,"x":-381,"y":-43},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Bitmap","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","colorControls":{"_class":"colorControls","isEnabled":true,"brightness":0,"contrast":1,"hue":0,"saturation":3},"endDecorationType":0,"miterLimit":10,"startDecorationType":0},"clippingMask":"{{0, 0}, {1, 1}}","fillReplacesImage":false,"image":{"_class":"MSJSONFileReference","_ref_class":"MSImageData","_ref":"images\/00f051d3a27fec14ba725121eb2bffb3ac1c3e02"},"nineSliceCenterRect":"{{0, 0}, {0, 0}}","nineSliceScale":"{1, 1}"}]},{"_class":"shapeGroup","do_objectID":"A853DAD7-96B9-4728-B1D3-4EA49A3FC5F6","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Colour","nameIsFixed":true,"originalObjectID":"A853DAD7-96B9-4728-B1D3-4EA49A3FC5F6","resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"style":{"_class":"style","do_objectID":"7E453BA0-F533-4FD4-A57B-5B223C3682A1","blur":{"_class":"blur","isEnabled":true,"center":"{0.5, 0.5}","motionAngle":0,"radius":10,"type":3},"endDecorationType":0,"fills":[{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":0.5,"blue":0.09188988095238096,"green":0.09188988095238096,"red":0.09188988095238096},"fillType":0,"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1},{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"contextSettings":{"_class":"graphicsContextSettings","blendMode":7,"opacity":1},"fillType":0,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":0,"green":0,"red":0},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1},{"_class":"fill","isEnabled":true,"color":{"_class":"color","alpha":1,"blue":0.2389987244897959,"green":0.2389987244897959,"red":0.2389987244897959},"contextSettings":{"_class":"graphicsContextSettings","blendMode":8,"opacity":1},"fillType":0,"gradient":{"_class":"gradient","elipseLength":1,"from":"{0.5, 0}","gradientType":0,"shouldSmoothenOpacity":false,"stops":[{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":1,"green":1,"red":1},"position":0},{"_class":"gradientStop","color":{"_class":"color","alpha":0.5,"blue":0,"green":0,"red":0},"position":1}],"to":"{0.5, 1}"},"noiseIndex":0,"noiseIntensity":0,"patternFillType":0,"patternTileScale":1}],"miterLimit":10,"sharedObjectID":"56FFB74F-9970-4865-9112-D3D3A60D6FCA","startDecorationType":0},"hasClickThrough":false,"layers":[{"_class":"rectangle","do_objectID":"DF86FB38-8CE2-48FA-9C72-B588D0618A54","exportOptions":{"_class":"exportOptions","exportFormats":[],"includedLayerIds":[],"layerOptions":0,"shouldTrim":false},"frame":{"_class":"rect","constrainProportions":false,"height":568,"width":320,"x":0,"y":0},"isFlippedHorizontal":false,"isFlippedVertical":false,"isLocked":false,"isVisible":true,"layerListExpandedType":0,"name":"Path","nameIsFixed":false,"resizingConstraint":63,"resizingType":0,"rotation":0,"shouldBreakMaskChain":false,"booleanOperation":-1,"edited":false,"path":{"_class":"path","isClosed":true,"pointRadiusBehaviour":0,"points":[{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 0}","curveMode":1,"curveTo":"{0, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 0}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{0, 1}","curveMode":1,"curveTo":"{0, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{0, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 1}","curveMode":1,"curveTo":"{1, 1}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 1}"},{"_class":"curvePoint","cornerRadius":0,"curveFrom":"{1, 0}","curveMode":1,"curveTo":"{1, 0}","hasCurveFrom":false,"hasCurveTo":false,"point":"{1, 0}"}]},"fixedRadius":0,"hasConvertedToNewRoundCorners":true}],"clippingMaskMode":0,"hasClippingMask":false,"windingRule":1}],"backgroundColor":{"_class":"color","alpha":1,"blue":1,"green":1,"red":1},"hasBackgroundColor":false,"horizontalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInExport":true,"includeInCloudUpload":true,"resizesContent":false,"verticalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeBackgroundColorInInstance":false,"symbolID":"26FA3C2E-D56B-4197-956C-1CB8CFCEE8E3","changeIdentifier":0}],"horizontalRulerData":{"_class":"rulerData","base":0,"guides":[]},"includeInCloudUpload":true,"verticalRulerData":{"_class":"rulerData","base":0,"guides":[]}}
--------------------------------------------------------------------------------
/public/target/previews/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/public/target/previews/preview.png
--------------------------------------------------------------------------------
/public/target/user.json:
--------------------------------------------------------------------------------
1 | {"9B2D6A75-D2C0-40F3-88AB-AE4BD625E520":{"scrollOrigin":"{14, 142}","zoomValue":0.6360955290019409},"D71ED31A-157A-4533-9B4C-3351905FC972":{"pageListHeight":110,"cloudShare":null},"1C01EF08-06C2-469A-8EB7-0BF12367FF6E":{"scrollOrigin":"{48, 130}","zoomValue":1}}
--------------------------------------------------------------------------------
/samples/Fitness App.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/samples/Fitness App.sketch
--------------------------------------------------------------------------------
/samples/ios11-app-store-design-ui.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/samples/ios11-app-store-design-ui.sketch
--------------------------------------------------------------------------------
/samples/stickersheet_general.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FourwingsY/react-sketch-viewer/63213d4b52bc26b537816539b1ae031c93ca6cbc/samples/stickersheet_general.sketch
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs-extra')
3 | const webpack = require('webpack')
4 | const fstream = require('fstream')
5 | const unzip = require('unzip')
6 |
7 | const config = require('./webpack.config.start')
8 |
9 | const [node, filename, ...args] = process.argv
10 | const projectRoot = fs.realpathSync(process.cwd())
11 | const resolve = relativePath => path.resolve(projectRoot, relativePath)
12 |
13 | // copy ./static to ./public
14 | fs.copySync(resolve('static'), resolve('public'))
15 |
16 | // copy sketch file into public directory and unzip
17 | prepareTarget()
18 |
19 | // run webpack
20 | const compiler = webpack(config)
21 | compiler.run((err, stats) => {
22 | if (err) {
23 | return reject(err)
24 | }
25 | })
26 |
27 |
28 | function prepareTarget() {
29 | // initial code assumes args[0] is relative path
30 | const sketchFile = resolve(args[0])
31 | const outputPath = resolve('public/target')
32 | const targetZipFile = path.resolve(outputPath, 'target.zip')
33 |
34 | // copy .sketch file to target.zip
35 | fs.emptyDirSync(outputPath)
36 | fs.copySync(sketchFile, targetZipFile)
37 |
38 | // unzip target.zip
39 | const readStream = fs.createReadStream(targetZipFile)
40 | const writeStream = fstream.Writer(outputPath)
41 | readStream
42 | .pipe(unzip.Parse())
43 | .pipe(writeStream)
44 |
45 | // remove target.zip
46 | fs.removeSync(targetZipFile)
47 | }
--------------------------------------------------------------------------------
/scripts/start.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs-extra')
3 | const webpack = require('webpack')
4 | const WebpackDevServer = require('webpack-dev-server')
5 | const fstream = require('fstream')
6 | const unzip = require('unzip')
7 |
8 | const config = require('./webpack.config.start')
9 | const devServerConfig = require('./webpackDevServer.config')
10 |
11 | const [node, filename, ...args] = process.argv
12 | const projectRoot = fs.realpathSync(process.cwd())
13 | const resolve = relativePath => path.resolve(projectRoot, relativePath)
14 |
15 | // copy ./static to ./public
16 | fs.copySync(resolve('static'), resolve('public'))
17 |
18 | // copy sketch file into public directory and unzip
19 | prepareTarget()
20 |
21 | // do webpack things
22 | const compiler = webpack(config)
23 | const devServer = new WebpackDevServer(compiler, devServerConfig)
24 |
25 | const PORT = 8000
26 | const HOST = '0.0.0.0'
27 |
28 | // Launch WebpackDevServer.
29 | devServer.listen(PORT, HOST, err => {
30 | if (err) {
31 | return console.log(err)
32 | }
33 | console.log('\nStarting the development server...')
34 | console.log(`on ${HOST}:${PORT}\n`)
35 | })
36 |
37 |
38 | function prepareTarget() {
39 | // initial code assumes args[0] is relative path
40 | const sketchFile = resolve(args[0])
41 | const outputPath = resolve('public/target')
42 | const targetZipFile = path.resolve(outputPath, 'target.zip')
43 |
44 | // copy .sketch file to target.zip
45 | fs.emptyDirSync(outputPath)
46 | fs.copySync(sketchFile, targetZipFile)
47 |
48 | // unzip target.zip
49 | const readStream = fs.createReadStream(targetZipFile)
50 | const writeStream = fstream.Writer(outputPath)
51 | readStream
52 | .pipe(unzip.Parse())
53 | .pipe(writeStream)
54 |
55 | // remove target.zip
56 | fs.removeSync(targetZipFile)
57 | }
--------------------------------------------------------------------------------
/scripts/webpack.config.start.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const fs = require('fs')
3 | const path = require('path')
4 |
5 | const webpack = require('webpack')
6 | const HtmlWebpackPlugin = require('html-webpack-plugin')
7 |
8 | const projectRoot = fs.realpathSync(process.cwd())
9 | const resolve = relativePath => path.resolve(projectRoot, relativePath)
10 |
11 | module.exports = {
12 | devtool: 'cheap-module-source-map',
13 | entry: resolve('src/index.js'),
14 | cache: true,
15 | output: {
16 | path: resolve('public'),
17 | pathinfo: true,
18 | filename: 'dist.[name].js',
19 | publicPath: './',
20 | },
21 | resolve: {
22 | modules: ['node_modules'],
23 | extensions: ['.js', '.json'],
24 | },
25 | module: {
26 | rules: [
27 | {
28 | exclude: [
29 | /\.html$/,
30 | /\.js$/,
31 | /\.css$/,
32 | ],
33 | loader: require.resolve('file-loader'),
34 | },
35 | {
36 | test: /\.js$/,
37 | include: resolve('src'),
38 | loader: require.resolve('babel-loader'),
39 | options: {
40 | cacheDirectory: true,
41 | },
42 | },
43 | {
44 | test: /\.css$/,
45 | loader: require.resolve('css-loader'),
46 | },
47 | ],
48 | },
49 | plugins: [
50 | new HtmlWebpackPlugin({
51 | inject: true,
52 | template: resolve('static/index.html'),
53 | }),
54 | new webpack.HotModuleReplacementPlugin(),
55 | ],
56 | }
57 |
--------------------------------------------------------------------------------
/scripts/webpackDevServer.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | compress: true,
3 | clientLogLevel: 'none',
4 | contentBase: 'public',
5 | watchContentBase: true,
6 | hot: true,
7 | publicPath: '/',
8 | quiet: true,
9 | watchOptions: {
10 | ignored: /node_modules/,
11 | },
12 | host: '0.0.0.0',
13 | disableHostCheck: true,
14 | }
15 |
--------------------------------------------------------------------------------
/src/Document.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import cx from 'classnames'
4 |
5 | import PageViewer from './PageViewer'
6 | import Page from './Page'
7 | import Layer from './Layer'
8 | import SymbolStore from './globals/SymbolStore'
9 |
10 | import {axiosJSON} from './utils/axiosUtils'
11 | import {update, traverse} from './utils/layerUtils'
12 |
13 | class Document extends React.Component {
14 |
15 | static childContextTypes = {
16 | renderLayer: PropTypes.func
17 | }
18 |
19 | getChildContext() {
20 | return {
21 | renderLayer: (layer, index) =>
22 | }
23 | }
24 |
25 | state = {
26 | document: null,
27 | currentPage: null,
28 | }
29 |
30 | async componentDidMount() {
31 | // load document and all pages
32 | const document = await axiosJSON.get('document.json')
33 | let pages = await Promise.all(document.pages.map(page => axiosJSON.get(page._ref)))
34 |
35 | // update mask layer
36 | pages = pages.map(page =>
37 | update(page, hasMaskingChild, treatClippingMask)
38 | )
39 |
40 | // extract symbols
41 | const symbols = []
42 | pages.forEach(page =>
43 | traverse(page, isSymbolMaster, symbol => symbols.push(symbol))
44 | )
45 | // and store them into SymbolStore
46 | SymbolStore.setMasters(symbols)
47 |
48 | document.pages = pages
49 | this.setState({document, currentPage: document.pages[0]})
50 | }
51 |
52 | setPage = page => e => {
53 | this.setState({currentPage: page})
54 | }
55 |
56 | render() {
57 | const {document, currentPage} = this.state
58 |
59 | // Do not render while loading pages
60 | if (!document) {
61 | return null
62 | }
63 |
64 | return (
65 |
66 |
69 |
70 |
71 |
72 |
73 | )
74 | }
75 |
76 | renderPageNav = page => {
77 | return (
78 |
83 | {page.name}
84 |
85 | )
86 | }
87 |
88 | }
89 |
90 | function hasMaskingChild(layer) {
91 | if (!layer.layers) {
92 | return false
93 | }
94 | return !layer.layers.every(layer => !layer.hasClippingMask)
95 | }
96 |
97 | function treatClippingMask(layer) {
98 | const childLayers = layer.layers
99 | let layers = []
100 | let maskGroup = null
101 | for (let childLayer of childLayers) {
102 | if (childLayer.hasClippingMask) {
103 | maskGroup = Object.assign({}, childLayer)
104 | maskGroup.mask = maskGroup.layers[0]
105 | maskGroup._class = 'maskGroup'
106 | maskGroup.layers = []
107 | maskGroup.isVisible = true
108 | }
109 | else if (!maskGroup) {
110 | layers.push(childLayer)
111 | }
112 | else if (maskGroup && layer.shouldBreakMaskChain) {
113 | layers.push(maskGroup)
114 | maskGroup = null
115 | layers.push(childLayer)
116 | }
117 | else if (maskGroup && !layer.shouldBreakMaskChain) {
118 | childLayer.frame.x = childLayer.frame.x - maskGroup.frame.x
119 | childLayer.frame.y = childLayer.frame.y - maskGroup.frame.y
120 | maskGroup.layers.push(childLayer)
121 | }
122 | }
123 | // at last
124 | if (maskGroup) {
125 | layers.push(maskGroup)
126 | }
127 | layer.layers = layers
128 | return layer
129 | }
130 |
131 | function isSymbolMaster(layer) {
132 | return layer._class === 'symbolMaster'
133 | }
134 |
135 | export default Document
--------------------------------------------------------------------------------
/src/Layer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Artboard from './layers/Artboard'
4 | import LayerGroup from './layers/LayerGroup'
5 | import MaskGroup from './layers/MaskGroup'
6 | import ShapeGroup from './layers/ShapeGroup'
7 | import SymbolInstance from './layers/SymbolInstance'
8 | import Bitmap from './layers/Bitmap'
9 | import Ellipse from './layers/Ellipse'
10 | import Path from './layers/Path'
11 | import Rectangle from './layers/Rectangle'
12 | import Text from './layers/Text'
13 |
14 | function Layer(props) {
15 | const {layer} = props
16 | if (layer.isVisible === false) {
17 | return null
18 | }
19 | switch(layer._class) {
20 | case 'artboard':
21 | case 'symbolMaster':
22 | return
23 | case 'group':
24 | return
25 | case 'maskGroup':
26 | return
27 | case 'shapeGroup':
28 | return
29 | case 'symbolInstance':
30 | return
31 | case 'text':
32 | return
33 | case 'bitmap':
34 | return
35 | case 'mask':
36 | return
37 | case 'rectangle':
38 | return
39 | case 'oval':
40 | return
41 | case 'shapePath':
42 | return
43 | default: {
44 | console.log(`${layer._class} is not supported`)
45 | return null
46 | }
47 | }
48 | }
49 |
50 | export default Layer
--------------------------------------------------------------------------------
/src/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Page extends React.Component {
5 |
6 | static propTypes = {
7 | page: PropTypes.object,
8 | }
9 |
10 | static contextTypes = {
11 | renderLayer: PropTypes.func,
12 | }
13 |
14 | render() {
15 | const page = this.props.page
16 | console.log(page)
17 | const {name, layers: childLayers} = page
18 | const style = {
19 | background: "#F2F2F2",
20 | boxSizing: "border-box",
21 | }
22 | return (
23 |
24 | {childLayers.map(this.context.renderLayer)}
25 |
26 | )
27 | }
28 | }
29 |
30 | export default Page
--------------------------------------------------------------------------------
/src/PageViewer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class PageViewer extends React.Component {
5 |
6 | static propTypes = {
7 | children: PropTypes.node.isRequired,
8 | }
9 |
10 | state = {
11 | dragging: false,
12 | zooming: false,
13 | startPoint: null,
14 | panStart: {x: 0, y: 0},
15 | panEnd: {x: 0, y: 0},
16 | zoom: 1,
17 | }
18 |
19 | startDrag = e => {
20 | e.preventDefault()
21 | const {clientX: x, clientY: y} = e
22 | this.setState({dragging: true, startPoint: {x, y}})
23 | }
24 |
25 | onDrag = e => {
26 | e.preventDefault()
27 | if (!this.state.dragging) return
28 |
29 | const {x: x0, y: y0} = this.state.panStart
30 | const {x: x1, y: y1} = this.state.startPoint
31 | const {clientX: x2, clientY: y2} = e
32 | const dx = x2 - x1, dy = y2 - y1
33 | this.setState({panEnd: {x: x0 + dx, y: y0 + dy}})
34 | }
35 |
36 | endDrag = e => {
37 | this.setState({dragging: false, startPoint: null, panStart: this.state.panEnd})
38 | }
39 |
40 | onWheel = e => {
41 | e.stopPropagation()
42 | e.preventDefault()
43 |
44 | // scroll too narrow
45 | if (Math.abs(Math.atan(e.deltaY / e.deltaX)) < 1) {
46 | return
47 | }
48 |
49 | if (e.deltaY > 0) {
50 | this.setState(prevState => ({zooming: true, zoom: prevState.zoom * 0.99}))
51 | } else if (e.deltaY < 0) {
52 | this.setState(prevState => ({zooming: true, zoom: prevState.zoom * 1.01}))
53 | }
54 | }
55 |
56 | render() {
57 | const {x, y} = this.state.panEnd
58 | const {zoom} = this.state
59 |
60 | return (
61 |
68 |
69 | {this.props.children}
70 |
71 |
72 | )
73 | }
74 |
75 | }
76 |
77 | export default PageViewer
--------------------------------------------------------------------------------
/src/data/Color.js:
--------------------------------------------------------------------------------
1 | class Color {
2 | constructor(color) {
3 | // default color: transparent
4 | if (!color) {
5 | this.red = 255
6 | this.green = 255
7 | this.blue = 255
8 | this.alpha = 0
9 | return
10 | }
11 | const {red, green, blue, alpha = 1} = color
12 | this.red = Math.round(red * 255)
13 | this.green = Math.round(green * 255)
14 | this.blue = Math.round(blue * 255)
15 | this.alpha = parseFloat(alpha)
16 | }
17 | getRgba() {
18 | if (this.alpha === 1) {
19 | return `rgb(${this.red}, ${this.green}, ${this.blue})`
20 | }
21 | return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`
22 | }
23 | }
24 |
25 | export default Color
--------------------------------------------------------------------------------
/src/data/Style.js:
--------------------------------------------------------------------------------
1 | import Color from './Color'
2 |
3 | class Style {
4 |
5 | constructor(layer) {
6 | this.layer = layer
7 | this.style = layer.style
8 | }
9 |
10 | getStyle() {
11 | return {
12 | opacity: this.getOpacity(),
13 | ...this.getBackground(),
14 | border: this.getBorder(),
15 | boxShadow: this.getShadow(),
16 | ...this.getBlur(),
17 | }
18 | }
19 |
20 | getOpacity() {
21 | const context = this.style.contextSettings
22 | if (!context) {
23 | return null
24 | }
25 | return context.opacity
26 | }
27 |
28 | getBackground() {
29 | if (!this.style.fills) {
30 | return 'transparent'
31 | }
32 |
33 | const {width, height} = this.layer.frame
34 |
35 | const colorToGradient = color => `linear-gradient(to top, ${color}, ${color})`
36 |
37 | const enabledFills = this.style.fills.filter(fill => fill.isEnabled)
38 |
39 | let backgrounds = []
40 | let blendModes = []
41 | let backgroundColor = null
42 |
43 | enabledFills.forEach((fill, index) => {
44 | const {
45 | color, fillType,
46 | gradient,
47 | noiseIndex, noiseIntensity,
48 | patternFillType, patternTileScale,
49 | contextSettings,
50 | } = fill
51 |
52 | // background color
53 | if (index === 0 && fillType === 0) {
54 | backgroundColor = (new Color(color)).getRgba()
55 | return
56 | }
57 |
58 | // extract blend mode
59 | if (!contextSettings) {
60 | blendModes.push('normal')
61 | } else {
62 | switch (contextSettings.blendMode) {
63 | case 7:
64 | blendModes.push('overlay')
65 | break
66 | case 8:
67 | blendModes.push('soft-light')
68 | break
69 | default:
70 | blendModes.push('normal')
71 | }
72 | }
73 |
74 | // multiple backgrounds can have only one last background-color
75 | // if this is not the last one, describe color as a gradient
76 | if (fillType === 0) {
77 | const bgColor = new Color(color)
78 | backgrounds.push(`linear-gradient(to top, ${bgColor.getRgba()}, ${bgColor.getRgba()})`)
79 | return
80 | }
81 |
82 | // if (fillType === 1) : gradient
83 | const {
84 | elipseLength, // sketch has wrong typo
85 | from, to, stops,
86 | gradientType,
87 | } = gradient
88 |
89 | // linear gradient
90 | if (gradientType === 0) {
91 | const pointRegex = /{(.+), (.+)}/
92 | const [_, startX, startY] = pointRegex.exec(from)
93 | const [__, endX, endY] = pointRegex.exec(to)
94 |
95 | const angle = Math.atan2(startX - endX, startY - endY)
96 | const degree = angle * 180 / Math.PI
97 |
98 | const colorStops = stops.map(stop => {
99 | const color = new Color(stop.color).getRgba()
100 | const stopAt = `${stop.position * 100}%`
101 | return `${color} ${stopAt}`
102 | })
103 |
104 | backgrounds.push(`linear-gradient(${degree}deg, ${colorStops.join(', ')})`)
105 | }
106 |
107 | // radial gradient
108 | if (gradientType === 1) {
109 | let gradientShape = 'circle'
110 | if (elipseLength !== 1) {
111 | gradientShape = 'ellipse'
112 | }
113 |
114 | const pointRegex = /{(.+), (.+)}/
115 | const [_, startX, startY] = pointRegex.exec(from)
116 | const [__, endX, endY] = pointRegex.exec(to)
117 |
118 | const gradientSize = Math.max(endY - startY) * height + 'px'
119 | const gradientPosition = `${startX * 100}% ${startY * 100}%`
120 | const colorStops = stops.map(stop => {
121 | const color = new Color(stop.color).getRgba()
122 | const stopAt = `${stop.position * 100}%`
123 | return `${color} ${stopAt}`
124 | })
125 |
126 | backgrounds.push(`radial-gradient(${gradientShape} ${gradientSize} at ${gradientPosition}, ${colorStops.join(', ')})`)
127 | }
128 | })
129 |
130 | // describe top-layer color first
131 | const bgStyle = {
132 | backgroundImage: backgrounds.reverse().join(', '),
133 | backgroundBlendMode: blendModes.reverse().join(', '),
134 | backgroundColor: backgroundColor,
135 | mixBlendMode: backgrounds.length > 1 ? 'overlay' : '',
136 | }
137 | return bgStyle
138 | }
139 |
140 | getBorder() {
141 | if (!this.style.borders) {
142 | return null
143 | }
144 | const border = this.style.borders[0]
145 | if (!border.isEnabled) {
146 | return null
147 | }
148 |
149 | return `${parseInt(border.thickness)}px solid ${new Color(border.color).getRgba()}`
150 | }
151 |
152 | getShadow() {
153 | if (!this.style.shadows || !this.style.shadows[0].isEnabled) {
154 | return null
155 | }
156 | const {offsetX, offsetY, blurRadius, spread, color} = this.style.shadows[0]
157 | return `${offsetX}px ${offsetY}px ${blurRadius}px ${spread}px ${new Color(color).getRgba()}`
158 | }
159 |
160 | getBlur() {
161 | if (!this.style.blur || !this.style.blur.isEnabled) {
162 | return {}
163 | }
164 | const {center, motionAngle, radius, type} = this.style.blur
165 |
166 | // background blur
167 | if (type === 3) {
168 | return {
169 | WebkitBackdropFilter: `blur(${radius}px)`
170 | }
171 | }
172 | }
173 |
174 | }
175 |
176 | export default Style
--------------------------------------------------------------------------------
/src/data/SvgStyle.js:
--------------------------------------------------------------------------------
1 | import Color from './Color'
2 |
3 | class SvgStyle {
4 |
5 | constructor(layer) {
6 | this.layer = layer
7 | this.style = layer.style
8 | }
9 |
10 | getStyle() {
11 | return {
12 | opacity: this.getOpacity(),
13 | fill: this.getBackground(),
14 | transform: this.getTransform(),
15 | ...this.getBorder(),
16 | }
17 | }
18 |
19 | getOpacity() {
20 | const context = this.style.contextSettings
21 | if (!context) {
22 | return null
23 | }
24 | return context.opacity
25 | }
26 |
27 | getBackground() {
28 | if (!this.style.fills) {
29 | return 'transparent'
30 | }
31 |
32 | const backgrounds = this.style.fills.map(fill => {
33 | const {
34 | isEnabled,
35 | color, fillType,
36 | gradient,
37 | noiseIndex, noiseIntensity,
38 | patternFillType, patternTileScale
39 | } = fill
40 |
41 | if (!isEnabled) {
42 | return null
43 | }
44 | if (!gradient) {
45 | return (new Color(color)).getRgba()
46 | }
47 |
48 | // gradient
49 | const {
50 | from, to, stops,
51 | gradientType,
52 | } = gradient
53 |
54 | if (gradientType === 0) {
55 | const pointRegex = /{(.+), (.+)}/
56 | const [_, startX, startY] = pointRegex.exec(from)
57 | const [__, endX, endY] = pointRegex.exec(to)
58 |
59 | const angle = Math.atan2(startX - endX, startY - endY)
60 | const degree = angle * 180 / Math.PI
61 |
62 | const colorStops = stops.map(stop => {
63 | const color = new Color(stop.color).getRgba()
64 | const stopAt = `${stop.position * 100}%`
65 | return `${color} ${stopAt}`
66 | })
67 |
68 | return `linear-gradient(${degree}deg, ${colorStops.join(',')})`
69 | }
70 | })
71 | return backgrounds.join(', ')
72 | }
73 |
74 | getBorder() {
75 | if (!this.style.borders) {
76 | return {strokeWidth: 0, stroke: new Color().getRgba()}
77 | }
78 |
79 | const border = this.style.borders[0]
80 | if (!border.isEnabled) {
81 | return {strokeWidth: 0, stroke: new Color().getRgba()}
82 | }
83 |
84 | return {
85 | stroke: new Color(border.color).getRgba(),
86 | strokeWidth: border.thickness,
87 | }
88 | }
89 |
90 | getTransform() {
91 | const {rotation, isFlippedHorizontal, isFlippedVertical} = this.layer
92 | let transform = []
93 |
94 | if (rotation) {
95 | transform.push(`rotate(${-rotation}deg)`)
96 | }
97 | if (isFlippedHorizontal) {
98 | transform.push('scaleX(-1)')
99 | }
100 | if (isFlippedVertical) {
101 | transform.push('scaleY(-1)')
102 | }
103 |
104 | if (!transform) {
105 | return null
106 | }
107 |
108 | return transform.join(',')
109 | }
110 |
111 | }
112 |
113 | export default SvgStyle
--------------------------------------------------------------------------------
/src/data/TextStyle.js:
--------------------------------------------------------------------------------
1 | import {Buffer} from 'buffer'
2 | import Color from './Color'
3 | import {
4 | parseBuffer,
5 | unarchivePlist,
6 | simplifyPlist,
7 | } from '../utils/decodeUtils'
8 |
9 | class TextStyle {
10 |
11 | constructor(layer) {
12 | this.layer = layer
13 | this.textStyle = layer.style.textStyle
14 | }
15 |
16 | _getStyle(attributes) {
17 | const {
18 | MSAttributedStringFontAttribute,
19 | NSColor,
20 | NSKern,
21 | NSParagraphStyle,
22 | NSLigature,
23 | } = attributes
24 |
25 | const fontSize = MSAttributedStringFontAttribute.NSFontDescriptorAttributes.NSFontSizeAttribute
26 | const fontFamily = MSAttributedStringFontAttribute.NSFontDescriptorAttributes.NSFontNameAttribute
27 | let textAlign
28 | switch(NSParagraphStyle.NSAlignment) {
29 | case 1: {
30 | textAlign = 'left'
31 | break
32 | }
33 | case 2: {
34 | textAlign = 'center'
35 | break
36 | }
37 | case 3: {
38 | textAlign = 'right'
39 | break
40 | }
41 | }
42 | let lineHeight = NSParagraphStyle.NSMaxLineHeight
43 |
44 | const style = {
45 | color: this.decodeColor(NSColor),
46 | fontSize,
47 | fontFamily,
48 | textAlign,
49 | lineHeight: lineHeight ? `${lineHeight}px` : undefined,
50 | letterSpacing: `${NSKern}px`,
51 | whiteSpace: 'pre-wrap',
52 | }
53 |
54 | if (this.layer.style.fills) {
55 | let fillColor = new Color(this.layer.style.fills[0].color)
56 | style.color = fillColor.getRgba()
57 | }
58 |
59 | return style
60 | }
61 |
62 | // get style from textStyle.encodedAttributes
63 | getParagraphStyle() {
64 | const {
65 | MSAttributedStringFontAttribute,
66 | NSColor,
67 | NSKern,
68 | NSParagraphStyle,
69 | } = this.textStyle.encodedAttributes
70 |
71 | const fontAttribute = decodeText(MSAttributedStringFontAttribute)
72 | const paragraphStyle = decodeText(NSParagraphStyle)
73 |
74 | return this._getStyle({
75 | MSAttributedStringFontAttribute: fontAttribute,
76 | NSColor,
77 | NSKern,
78 | NSParagraphStyle: paragraphStyle,
79 | })
80 | }
81 |
82 | getTextStyle(styleIndex = null) {
83 | const decodedTextAttributes = decodeText(this.layer.attributedString.archivedAttributedString)
84 | const {
85 | NSAttributes,
86 | } = decodedTextAttributes
87 |
88 | // has single subText: NSAttributes is an object
89 | if (styleIndex === null) {
90 | return this._getStyle(NSAttributes)
91 | }
92 |
93 | // has many subText: NSAttributes["NS.objects"] is an array of attribute
94 | const textAttribute = NSAttributes["NS.objects"][styleIndex]
95 | return this._getStyle(textAttribute)
96 | }
97 |
98 | decodeColor(NSColor) {
99 | if (!NSColor) {
100 | return null
101 | }
102 | const decoder = new TextDecoder('utf8');
103 | const [red, green, blue, alpha] = decoder.decode(NSColor.NSRGB).split(' ');
104 | const textColor = new Color({
105 | red: parseFloat(red),
106 | green: parseFloat(green),
107 | blue: parseFloat(blue),
108 | alpha
109 | })
110 | return textColor.getRgba()
111 | }
112 |
113 | }
114 |
115 | function decodeText(archived) {
116 | const buffer = new Buffer(archived._archive, 'base64')
117 | const plist = parseBuffer(buffer)[0]
118 | const unarchived = unarchivePlist(plist)
119 | const simplified = simplifyPlist(unarchived)
120 | return simplified
121 | }
122 |
123 | export default TextStyle
--------------------------------------------------------------------------------
/src/globals/SymbolStore.js:
--------------------------------------------------------------------------------
1 | class SymbolStore {
2 | symbolMap = {}
3 |
4 | setMasters(symbolMasters = []) {
5 | symbolMasters.forEach(symbol => {
6 | this.symbolMap[symbol.symbolID] = symbol
7 | })
8 | }
9 |
10 | getSymbol(symbolID) {
11 | return this.symbolMap[symbolID]
12 | }
13 |
14 | }
15 |
16 | const instance = new SymbolStore()
17 |
18 | export default instance
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import Document from './Document'
5 |
6 | ReactDOM.render(, document.querySelector("#app"))
--------------------------------------------------------------------------------
/src/layers/Artboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import {getPositionStyle} from '../utils/layerUtils'
4 |
5 | class Artboard extends React.Component {
6 |
7 | static propTypes = {
8 | layer: PropTypes.object,
9 | }
10 |
11 | static contextTypes = {
12 | renderLayer: PropTypes.func,
13 | }
14 |
15 | render() {
16 | const layer = this.props.layer
17 | const {name} = layer
18 | let {layers: childLayers} = layer
19 |
20 | const style = {
21 | ...getPositionStyle(layer),
22 | }
23 |
24 | return (
25 |
26 |
{name}
27 |
28 | {childLayers.map(this.context.renderLayer)}
29 |
30 |
31 | )
32 | }
33 |
34 | }
35 |
36 | export default Artboard
--------------------------------------------------------------------------------
/src/layers/Bitmap.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Style from '../data/Style'
4 | import {getPositionStyle} from '../utils/layerUtils'
5 |
6 | class Bitmap extends React.Component {
7 |
8 | static propTypes = {
9 | layer: PropTypes.object,
10 | }
11 |
12 | render() {
13 | const layer = this.props.layer
14 | const {name, image} = layer
15 | if (!image || !image._ref) {
16 | return null
17 | }
18 |
19 | const style = {
20 | ...getPositionStyle(layer),
21 | ...new Style(layer).getStyle(),
22 | }
23 |
24 | // TODO: support other image formats
25 | const src = `./target/${image._ref}.png`
26 |
27 | return (
28 |
29 | )
30 | }
31 |
32 | }
33 |
34 | export default Bitmap
--------------------------------------------------------------------------------
/src/layers/Ellipse.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Ellipse extends React.Component {
5 |
6 | static propTypes = {
7 | layer: PropTypes.object,
8 | }
9 |
10 | render() {
11 | console.log("renderEllipse")
12 | const {frame} = this.props.layer
13 | const {width, height, x, y} = frame
14 | const {cx, cy, rx, ry} = {
15 | cx: x + width / 2,
16 | cy: y + height / 2,
17 | rx: width / 2,
18 | ry: height / 2,
19 | }
20 |
21 | return (
22 |
23 | )
24 | }
25 |
26 | }
27 |
28 | export default Ellipse
--------------------------------------------------------------------------------
/src/layers/LayerGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Color from '../data/Color'
4 | import {getPositionStyle} from '../utils/layerUtils'
5 |
6 | class LayerGroup extends React.Component {
7 |
8 | static propTypes = {
9 | layer: PropTypes.object,
10 | }
11 |
12 | static contextTypes = {
13 | renderLayer: PropTypes.func,
14 | }
15 |
16 | render() {
17 | const layer = this.props.layer
18 | const {name, background, layers: childLayers} = layer
19 |
20 | const style = {
21 | ...getPositionStyle(layer),
22 | background: (new Color(background)).getRgba()
23 | }
24 |
25 | return (
26 |
27 | {childLayers.map(this.context.renderLayer)}
28 |
29 | )
30 | }
31 |
32 | }
33 |
34 | export default LayerGroup
--------------------------------------------------------------------------------
/src/layers/MaskGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Style from '../data/Style'
4 | import {getPositionStyle} from '../utils/layerUtils'
5 |
6 | class MaskGroup extends React.Component {
7 |
8 | static propTypes = {
9 | layer: PropTypes.object,
10 | }
11 |
12 | static contextTypes = {
13 | renderLayer: PropTypes.func,
14 | }
15 |
16 | render() {
17 | const layer = this.props.layer
18 | const {name, mask, layers: childLayers} = layer
19 |
20 | const style = {
21 | ...getPositionStyle(layer),
22 | }
23 |
24 | const maskStyle = {
25 | position: 'absolute',
26 | width: '100%',
27 | height: '100%',
28 | overflow: 'hidden',
29 | borderRadius: mask._class === 'oval' ? '50%' : mask.fixedRadius + 'px',
30 | ...(new Style(layer).getStyle())
31 | }
32 |
33 | return (
34 |
35 |
36 | {childLayers.map(this.context.renderLayer)}
37 |
38 |
39 | )
40 | }
41 |
42 | }
43 |
44 | export default MaskGroup
--------------------------------------------------------------------------------
/src/layers/Path.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Path extends React.Component {
5 |
6 | static propTypes = {
7 | layer: PropTypes.object,
8 | }
9 |
10 | static getPath(layer) {
11 | const {frame, path, booleanOperation, rotation} = layer
12 |
13 | const p = calculateAbsolutePoint(frame, rotation)
14 |
15 | const {points, isClosed} = path
16 | let endPoints = points.slice().reverse()
17 | const startPoints = [null, ...endPoints]
18 | if (isClosed) {
19 | endPoints.push(endPoints[0])
20 | }
21 |
22 | const coordinates = startPoints.map((startPoint, i) => {
23 | const endPoint = endPoints[i]
24 | const pointRegex = /{(.+), (.+)}/
25 |
26 | // on first path
27 | if (startPoint === null) {
28 | const [_0, u, v] = pointRegex.exec(endPoint.point)
29 | const {x, y} = p(u, v)
30 | return `M ${x} ${y} `
31 | }
32 | // on last path with opened path
33 | if (!endPoint) {
34 | return ''
35 | }
36 |
37 | // curve
38 | if (startPoint.hasCurveTo && endPoint.hasCurveFrom) {
39 | const [_0, u0, v0] = pointRegex.exec(startPoint.curveTo)
40 | const {x: x0, y: y0} = p(u0, v0)
41 | const [_1, u1, v1] = pointRegex.exec(endPoint.curveFrom)
42 | const {x: x1, y: y1} = p(u1, v1)
43 | const [_2, u2, v2] = pointRegex.exec(endPoint.point)
44 | const {x: x2, y: y2} = p(u2, v2)
45 | return `C ${x0} ${y0} ${x1} ${y1} ${x2} ${y2} `
46 | } else {
47 | const [_2, u, v] = pointRegex.exec(endPoint.point)
48 | const {x, y} = p(u, v)
49 | return `L ${x} ${y} `
50 | }
51 | })
52 |
53 | const pathData = coordinates.join('') + (isClosed ? 'Z' : '')
54 | return pathData
55 | }
56 |
57 | render() {
58 | const pathData = Path.getPath(this.props.layer)
59 | return (
60 |
61 | )
62 | }
63 |
64 | }
65 |
66 | function calculateAbsolutePoint(frame, rotation) {
67 | return (u, v) => {
68 | const {x, y, width, height} = frame
69 | const point = {
70 | x: (x + u * width).toFixed(3),
71 | y: (y + v * height).toFixed(3),
72 | }
73 | // return point
74 | if (rotation === 0) {
75 | return point
76 | } else {
77 | const cx = x + width / 2
78 | const cy = y + height / 2
79 | return {
80 | x: cx + (point.x - cx) * Math.cos(deg2Rad(-rotation)) - (point.y - cy) * Math.sin(deg2Rad(-rotation)),
81 | y: cy + (point.x - cx) * Math.sin(deg2Rad(-rotation)) + (point.y - cy) * Math.cos(deg2Rad(-rotation)),
82 | }
83 | // other form
84 | // return {
85 | // x: cx + ((u - 0.5) * Math.cos(deg2Rad(-rotation)) * width - (v - 0.5) * Math.sin(deg2Rad(-rotation)) * height),
86 | // y: cy + ((u - 0.5) * Math.sin(deg2Rad(-rotation)) * width + (v - 0.5) * Math.cos(deg2Rad(-rotation)) * height),
87 | // }
88 | }
89 | }
90 | }
91 |
92 | function deg2Rad(degree) {
93 | return degree / 180 * Math.PI
94 | }
95 |
96 | export default Path
--------------------------------------------------------------------------------
/src/layers/Rectangle.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | class Rectangle extends React.Component {
5 |
6 | static propTypes = {
7 | layer: PropTypes.object,
8 | }
9 |
10 | render() {
11 | const {frame, path, fixedRadius, rotation} = this.props.layer
12 | const {width, height, x, y} = frame
13 |
14 | return (
15 |
16 | )
17 | }
18 |
19 | }
20 |
21 | export default Rectangle
--------------------------------------------------------------------------------
/src/layers/ShapeGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Style from '../data/Style'
4 | import SvgStyle from '../data/SvgStyle'
5 | import Path from './Path'
6 | import {getPositionStyle} from '../utils/layerUtils'
7 |
8 | class ShapeGroup extends React.Component {
9 |
10 | static propTypes = {
11 | layer: PropTypes.object,
12 | }
13 |
14 | static contextTypes = {
15 | renderLayer: PropTypes.func,
16 | svgContext: PropTypes.object,
17 | }
18 |
19 | static childContextTypes = {
20 | svgContext: PropTypes.object,
21 | }
22 |
23 | getChildContext() {
24 | return {
25 | svgContext: this.context.svgContext
26 | ? this.context.svgContext
27 | : this.isSvgContext() ? this : undefined
28 | }
29 | }
30 |
31 | isSvgContext() {
32 | const childLayers = this.props.layer.layers
33 | return childLayers.length !== 1 || childLayers[0]._class === 'shapePath'
34 | }
35 |
36 | render() {
37 | const layer = this.props.layer
38 | const {name, layers: childLayers} = layer
39 |
40 | const simpleStyle = new Style(layer)
41 | const svgStyle = new SvgStyle(layer)
42 |
43 | // if parent is svg
44 | if (this.context.svgContext) {
45 | const style = {
46 | ...getPositionStyle(layer),
47 | ...svgStyle.getStyle(),
48 | overflow: 'visible',
49 | }
50 | return (
51 |
52 | {this.renderChildLayers()}
53 |
54 | )
55 | }
56 |
57 | // if this layer has path layer, edited rectangle or edited oval in child layer
58 | if (this.isSvgContext() || !childLayers.every(layer => layer.edited === false)) {
59 | const style = {
60 | ...getPositionStyle(layer),
61 | ...svgStyle.getStyle(),
62 | overflow: 'visible',
63 | }
64 | return (
65 |
68 | )
69 | }
70 |
71 | const style = {
72 | ...getPositionStyle(layer),
73 | ...simpleStyle.getStyle(),
74 | borderRadius: layer.layers[0]._class === 'oval' ? '50%' : layer.layers[0].fixedRadius + 'px',
75 | overflow: 'visible',
76 | boxSizing: 'border-box',
77 | }
78 |
79 | // if shapeGroup has a single child layer and it is not a path, use div.
80 | return (
81 |
82 | )
83 | }
84 |
85 | renderChildLayers() {
86 | const childLayers = this.props.layer.layers
87 | // if this layer is just a path group, join the path
88 | if (childLayers.every(layer => layer._class !== 'shapeGroup')) {
89 | return (
90 |
91 | )
92 | }
93 |
94 | return childLayers.map(this.context.renderLayer)
95 | }
96 |
97 | }
98 |
99 | export default ShapeGroup
--------------------------------------------------------------------------------
/src/layers/SymbolInstance.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import SymbolStore from '../globals/SymbolStore'
5 |
6 | import {getPositionStyle} from '../utils/layerUtils'
7 |
8 | class SymbolInstance extends React.Component {
9 |
10 | static propTypes = {
11 | layer: PropTypes.object,
12 | }
13 |
14 | static contextTypes = {
15 | renderLayer: PropTypes.func,
16 | }
17 |
18 | render() {
19 | const layer = this.props.layer
20 | const {name, symbolID} = layer
21 |
22 | const symbol = SymbolStore.getSymbol(symbolID)
23 | const {layers: childLayers, overrides} = symbol
24 |
25 | const style = {
26 | ...getPositionStyle(layer),
27 | }
28 |
29 | return (
30 |
31 | {childLayers.map(this.context.renderLayer)}
32 |
33 | )
34 | }
35 | }
36 |
37 | export default SymbolInstance
--------------------------------------------------------------------------------
/src/layers/Text.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TextStyle from '../data/TextStyle'
4 | import {
5 | parseBuffer,
6 | unarchivePlist,
7 | simplifyPlist,
8 | } from '../utils/decodeUtils'
9 | import {getPositionStyle} from '../utils/layerUtils'
10 | import {Buffer} from 'buffer'
11 |
12 | class Text extends React.Component {
13 |
14 | static propTypes = {
15 | layer: PropTypes.object,
16 | }
17 |
18 | render() {
19 | const layer = this.props.layer
20 | const {attributedString} = layer
21 |
22 | const textStyle = new TextStyle(layer)
23 |
24 | const paragraphStyle = {
25 | display: 'inline-block',
26 | overflow: 'hidden',
27 | ...getPositionStyle(layer),
28 | ...textStyle.getParagraphStyle(),
29 | }
30 |
31 | const decodedTextAttributes = decodeText(attributedString.archivedAttributedString)
32 | const text = decodedTextAttributes.NSString
33 |
34 | return (
35 |
36 | {decodedTextAttributes.NSAttributeInfo
37 | ? this.renderSubTexts()
38 | : {text}
39 | }
40 |
41 | )
42 | }
43 |
44 | renderSubTexts() {
45 | const textStyle = new TextStyle(this.props.layer)
46 | const decodedTextAttributes = decodeText(this.props.layer.attributedString.archivedAttributedString)
47 | const text = decodedTextAttributes.NSString
48 | const subStringStyles = decodedTextAttributes.NSAttributeInfo["NS.data"]
49 | const subTexts = []
50 | for (let i = 0, s = 0, l = subStringStyles.length / 2; i < l; i++) {
51 | const charCount = subStringStyles[i * 2]
52 | const styleIndex = subStringStyles[i * 2 + 1]
53 | subTexts.push({text.slice(s, s + charCount)})
54 | s += charCount
55 | }
56 | return subTexts
57 | }
58 |
59 | }
60 |
61 | function decodeText(archived) {
62 | const buffer = new Buffer(archived._archive, 'base64')
63 | const plist = parseBuffer(buffer)[0]
64 | const unarchived = unarchivePlist(plist)
65 | const simplified = simplifyPlist(unarchived)
66 | return simplified
67 | }
68 |
69 | export default Text
--------------------------------------------------------------------------------
/src/utils/axiosUtils.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | axios.defaults.headers.common["Accept"] = "application/json"
4 | axios.defaults.headers.common["Content-Type"] = "application/json;charset=UTF-8"
5 |
6 | /**
7 | * for JSON request
8 | */
9 | const axiosJSON = axios.create({
10 | baseURL: "./target"
11 | })
12 |
13 | axiosJSON.interceptors.request.use(config => {
14 | if (!/\.json$/.test(config.url)) {
15 | config.url = config.url + '.json'
16 | }
17 | return config
18 | })
19 | axiosJSON.interceptors.response.use(response => {
20 | return response.data
21 | })
22 |
23 | export {
24 | axiosJSON
25 | }
--------------------------------------------------------------------------------
/src/utils/decodeUtils.js:
--------------------------------------------------------------------------------
1 | // from https://www.npmjs.com/package/bplist-parser
2 | const debug = false;
3 |
4 | const maxObjectSize = 100 * 1000 * 1000; // 100Meg
5 | const maxObjectCount = 32768;
6 |
7 | // EPOCH = new SimpleDateFormat("yyyy MM dd zzz").parse("2001 01 01 GMT").getTime();
8 | // ...but that's annoying in a static initializer because it can throw exceptions, ick.
9 | // So we just hardcode the correct value.
10 | const EPOCH = 978307200000;
11 |
12 | // UID object definition
13 | var UID = UID = function(id) {
14 | this.UID = id;
15 | }
16 |
17 | var parseBuffer = parseBuffer = function (buffer) {
18 | var result = {};
19 |
20 | // check header
21 | var header = buffer.slice(0, 'bplist'.length).toString('utf8');
22 | if (header !== 'bplist') {
23 | throw new Error("Invalid binary plist. Expected 'bplist' at offset 0.");
24 | }
25 |
26 | // Handle trailer, last 32 bytes of the file
27 | var trailer = buffer.slice(buffer.length - 32, buffer.length);
28 | // 6 null bytes (index 0 to 5)
29 | var offsetSize = trailer.readUInt8(6);
30 | if (debug) {
31 | console.log("offsetSize: " + offsetSize);
32 | }
33 | var objectRefSize = trailer.readUInt8(7);
34 | if (debug) {
35 | console.log("objectRefSize: " + objectRefSize);
36 | }
37 | var numObjects = readUInt64BE(trailer, 8);
38 | if (debug) {
39 | console.log("numObjects: " + numObjects);
40 | }
41 | var topObject = readUInt64BE(trailer, 16);
42 | if (debug) {
43 | console.log("topObject: " + topObject);
44 | }
45 | var offsetTableOffset = readUInt64BE(trailer, 24);
46 | if (debug) {
47 | console.log("offsetTableOffset: " + offsetTableOffset);
48 | }
49 |
50 | if (numObjects > maxObjectCount) {
51 | throw new Error("maxObjectCount exceeded");
52 | }
53 |
54 | // Handle offset table
55 | var offsetTable = [];
56 |
57 | for (var i = 0; i < numObjects; i++) {
58 | var offsetBytes = buffer.slice(offsetTableOffset + i * offsetSize, offsetTableOffset + (i + 1) * offsetSize);
59 | offsetTable[i] = readUInt(offsetBytes, 0);
60 | if (debug) {
61 | console.log("Offset for Object #" + i + " is " + offsetTable[i] + " [" + offsetTable[i].toString(16) + "]");
62 | }
63 | }
64 |
65 | // Parses an object inside the currently parsed binary property list.
66 | // For the format specification check
67 | //
68 | // Apple's binary property list parser implementation.
69 | function parseObject(tableOffset) {
70 | var offset = offsetTable[tableOffset];
71 | var type = buffer[offset];
72 | var objType = (type & 0xF0) >> 4; //First 4 bits
73 | var objInfo = (type & 0x0F); //Second 4 bits
74 | switch (objType) {
75 | case 0x0:
76 | return parseSimple();
77 | case 0x1:
78 | return parseInteger();
79 | case 0x8:
80 | return parseUID();
81 | case 0x2:
82 | return parseReal();
83 | case 0x3:
84 | return parseDate();
85 | case 0x4:
86 | return parseData();
87 | case 0x5: // ASCII
88 | return parsePlistString();
89 | case 0x6: // UTF-16
90 | return parsePlistString(true);
91 | case 0xA:
92 | return parseArray();
93 | case 0xD:
94 | return parseDictionary();
95 | default:
96 | throw new Error("Unhandled type 0x" + objType.toString(16));
97 | }
98 |
99 | function parseSimple() {
100 | //Simple
101 | switch (objInfo) {
102 | case 0x0: // null
103 | return null;
104 | case 0x8: // false
105 | return false;
106 | case 0x9: // true
107 | return true;
108 | case 0xF: // filler byte
109 | return null;
110 | default:
111 | throw new Error("Unhandled simple type 0x" + objType.toString(16));
112 | }
113 | }
114 |
115 | function bufferToHexString(buffer) {
116 | var str = '';
117 | var i;
118 | for (i = 0; i < buffer.length; i++) {
119 | if (buffer[i] != 0x00) {
120 | break;
121 | }
122 | }
123 | for (; i < buffer.length; i++) {
124 | var part = '00' + buffer[i].toString(16);
125 | str += part.substr(part.length - 2);
126 | }
127 | return str;
128 | }
129 |
130 | function parseInteger() {
131 | var length = Math.pow(2, objInfo);
132 | if (length > 4) {
133 | var data = buffer.slice(offset + 1, offset + 1 + length);
134 | var str = bufferToHexString(data);
135 | return bigInt(str, 16);
136 | } if (length < maxObjectSize) {
137 | return readUInt(buffer.slice(offset + 1, offset + 1 + length));
138 | } else {
139 | throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + maxObjectSize + " are available.");
140 | }
141 | }
142 |
143 | function parseUID() {
144 | var length = objInfo + 1;
145 | if (length < maxObjectSize) {
146 | return new UID(readUInt(buffer.slice(offset + 1, offset + 1 + length)));
147 | } else {
148 | throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + maxObjectSize + " are available.");
149 | }
150 | }
151 |
152 | function parseReal() {
153 | var length = Math.pow(2, objInfo);
154 | if (length < maxObjectSize) {
155 | var realBuffer = buffer.slice(offset + 1, offset + 1 + length);
156 | if (length === 4) {
157 | return realBuffer.readFloatBE(0);
158 | }
159 | else if (length === 8) {
160 | return realBuffer.readDoubleBE(0);
161 | }
162 | } else {
163 | throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + maxObjectSize + " are available.");
164 | }
165 | }
166 |
167 | function parseDate() {
168 | if (objInfo != 0x3) {
169 | console.error("Unknown date type :" + objInfo + ". Parsing anyway...");
170 | }
171 | var dateBuffer = buffer.slice(offset + 1, offset + 9);
172 | return new Date(EPOCH + (1000 * dateBuffer.readDoubleBE(0)));
173 | }
174 |
175 | function parseData() {
176 | var dataoffset = 1;
177 | var length = objInfo;
178 | if (objInfo == 0xF) {
179 | var int_type = buffer[offset + 1];
180 | var intType = (int_type & 0xF0) / 0x10;
181 | if (intType != 0x1) {
182 | console.error("0x4: UNEXPECTED LENGTH-INT TYPE! " + intType);
183 | }
184 | var intInfo = int_type & 0x0F;
185 | var intLength = Math.pow(2, intInfo);
186 | dataoffset = 2 + intLength;
187 | if (intLength < 3) {
188 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
189 | } else {
190 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
191 | }
192 | }
193 | if (length < maxObjectSize) {
194 | return buffer.slice(offset + dataoffset, offset + dataoffset + length);
195 | } else {
196 | throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + maxObjectSize + " are available.");
197 | }
198 | }
199 |
200 | function parsePlistString (isUtf16) {
201 | isUtf16 = isUtf16 || 0;
202 | var enc = "utf8";
203 | var length = objInfo;
204 | var stroffset = 1;
205 | if (objInfo == 0xF) {
206 | var int_type = buffer[offset + 1];
207 | var intType = (int_type & 0xF0) / 0x10;
208 | if (intType != 0x1) {
209 | console.err("UNEXPECTED LENGTH-INT TYPE! " + intType);
210 | }
211 | var intInfo = int_type & 0x0F;
212 | var intLength = Math.pow(2, intInfo);
213 | var stroffset = 2 + intLength;
214 | if (intLength < 3) {
215 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
216 | } else {
217 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
218 | }
219 | }
220 | // length is String length -> to get byte length multiply by 2, as 1 character takes 2 bytes in UTF-16
221 | length *= (isUtf16 + 1);
222 | if (length < maxObjectSize) {
223 | var plistString = new Buffer(buffer.slice(offset + stroffset, offset + stroffset + length));
224 | if (isUtf16) {
225 | plistString = swapBytes(plistString);
226 | enc = "ucs2";
227 | }
228 | return plistString.toString(enc);
229 | } else {
230 | throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + maxObjectSize + " are available.");
231 | }
232 | }
233 |
234 | function parseArray() {
235 | var length = objInfo;
236 | var arrayoffset = 1;
237 | if (objInfo == 0xF) {
238 | var int_type = buffer[offset + 1];
239 | var intType = (int_type & 0xF0) / 0x10;
240 | if (intType != 0x1) {
241 | console.error("0xa: UNEXPECTED LENGTH-INT TYPE! " + intType);
242 | }
243 | var intInfo = int_type & 0x0F;
244 | var intLength = Math.pow(2, intInfo);
245 | arrayoffset = 2 + intLength;
246 | if (intLength < 3) {
247 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
248 | } else {
249 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
250 | }
251 | }
252 | if (length * objectRefSize > maxObjectSize) {
253 | throw new Error("To little heap space available!");
254 | }
255 | var array = [];
256 | for (var i = 0; i < length; i++) {
257 | var objRef = readUInt(buffer.slice(offset + arrayoffset + i * objectRefSize, offset + arrayoffset + (i + 1) * objectRefSize));
258 | array[i] = parseObject(objRef);
259 | }
260 | return array;
261 | }
262 |
263 | function parseDictionary() {
264 | var length = objInfo;
265 | var dictoffset = 1;
266 | if (objInfo == 0xF) {
267 | var int_type = buffer[offset + 1];
268 | var intType = (int_type & 0xF0) / 0x10;
269 | if (intType != 0x1) {
270 | console.error("0xD: UNEXPECTED LENGTH-INT TYPE! " + intType);
271 | }
272 | var intInfo = int_type & 0x0F;
273 | var intLength = Math.pow(2, intInfo);
274 | dictoffset = 2 + intLength;
275 | if (intLength < 3) {
276 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
277 | } else {
278 | length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
279 | }
280 | }
281 | if (length * 2 * objectRefSize > maxObjectSize) {
282 | throw new Error("To little heap space available!");
283 | }
284 | if (debug) {
285 | console.log("Parsing dictionary #" + tableOffset);
286 | }
287 | var dict = {};
288 | for (var i = 0; i < length; i++) {
289 | var keyRef = readUInt(buffer.slice(offset + dictoffset + i * objectRefSize, offset + dictoffset + (i + 1) * objectRefSize));
290 | var valRef = readUInt(buffer.slice(offset + dictoffset + (length * objectRefSize) + i * objectRefSize, offset + dictoffset + (length * objectRefSize) + (i + 1) * objectRefSize));
291 | var key = parseObject(keyRef);
292 | var val = parseObject(valRef);
293 | if (debug) {
294 | console.log(" DICT #" + tableOffset + ": Mapped " + key + " to " + val);
295 | }
296 | dict[key] = val;
297 | }
298 | return dict;
299 | }
300 | }
301 |
302 | return [ parseObject(topObject) ];
303 | };
304 |
305 | function readUInt(buffer, start) {
306 | start = start || 0;
307 |
308 | var l = 0;
309 | for (var i = start; i < buffer.length; i++) {
310 | l <<= 8;
311 | l |= buffer[i] & 0xFF;
312 | }
313 | return l;
314 | }
315 |
316 | // we're just going to toss the high order bits because javascript doesn't have 64-bit ints
317 | function readUInt64BE(buffer, start) {
318 | var data = buffer.slice(start, start + 8);
319 | return data.readUInt32BE(4, 8);
320 | }
321 |
322 | function swapBytes(buffer) {
323 | var len = buffer.length;
324 | for (var i = 0; i < len; i += 2) {
325 | var a = buffer[i];
326 | buffer[i] = buffer[i+1];
327 | buffer[i+1] = a;
328 | }
329 | return buffer;
330 | }
331 |
332 | function unarchivePlist(plist) {
333 | const objects = plist["$objects"]
334 | const rootUID = plist["$top"].root.UID
335 | const unarchived = unarchiveObject(objects, objects[rootUID])
336 | return unarchived
337 | }
338 |
339 | function unarchiveObject(objects, object) {
340 | const type = Object.prototype.toString.call(object)
341 | // apply recursively only when plist is object or array
342 | if (type !== "[object Object]" && type !== "[object Array]") {
343 | return object
344 | }
345 | if (Array.isArray(object)) {
346 | // console.log(object)
347 | const result = []
348 | for (let element of object) {
349 | const valueUID = element.UID
350 | // console.log(element, valueUID)
351 | if (valueUID) {
352 | result.push(unarchiveObject(objects, objects[valueUID]))
353 | } else {
354 | result.push(unarchiveObject(objects, element))
355 | }
356 | }
357 | return result
358 | }
359 |
360 | if (typeof object === 'object' && object.UID >= 0) {
361 | return unarchiveObject(objects, objects[object.UID])
362 | }
363 |
364 | if (typeof object === 'object') {
365 | const result = {}
366 | for (let key in object) {
367 | const value = object[key]
368 | result[key] = unarchiveObject(objects, value)
369 | }
370 | return result
371 | }
372 |
373 | return object
374 | }
375 |
376 | function simplifyPlist(plist) {
377 | const type = Object.prototype.toString.call(plist)
378 | // apply recursively only when plist is object or array
379 | if (type !== "[object Object]" && type !== "[object Array]") {
380 | return plist
381 | }
382 | if (Array.isArray(plist)) {
383 | return plist.map(simplifyPlist)
384 | }
385 |
386 | // for key-value dictionary
387 | const keys = plist["NS.keys"]
388 | const values = plist["NS.objects"]
389 | if (keys && values) {
390 | const result = {}
391 | for (let idx in keys) {
392 | result[keys[idx]] = simplifyPlist(values[idx])
393 | if (keys[idx] === 'NSAttributes') {
394 | console.log(simplifyPlist(values[idx]))
395 | }
396 | }
397 | return result
398 | }
399 |
400 | // for common objects
401 | if (typeof plist === 'object') {
402 | const result = {}
403 | for (let key in plist) {
404 | result[key] = simplifyPlist(plist[key])
405 | }
406 | return result
407 | }
408 | return plist
409 | }
410 |
411 | export {
412 | parseBuffer,
413 | unarchivePlist,
414 | simplifyPlist,
415 | }
--------------------------------------------------------------------------------
/src/utils/layerUtils.js:
--------------------------------------------------------------------------------
1 | function getPositionStyle(layer) {
2 | const {frame, rotation, isFlippedHorizontal, isFlippedVertical} = layer
3 | let transforms = []
4 |
5 | if (rotation) {
6 | transforms.push(`rotate(${-rotation}deg)`)
7 | }
8 | if (isFlippedHorizontal) {
9 | transforms.push('scaleX(-1)')
10 | }
11 | if (isFlippedVertical) {
12 | transforms.push('scaleY(-1)')
13 | }
14 |
15 | const style = {
16 | position: 'absolute',
17 | width: frame.width,
18 | height: frame.height,
19 | top: frame.y,
20 | left: frame.x,
21 | transform: transforms.join(',')
22 | }
23 |
24 | return style
25 | }
26 |
27 | // traverse child and replace layer with it's result
28 | function update(rootLayer, selector, func) {
29 | let childLayers = rootLayer.layers
30 |
31 | // traverse child
32 | if (childLayers) {
33 | childLayers = childLayers.map(layer =>
34 | update(layer, selector, func)
35 | )
36 | rootLayer.layers = childLayers
37 | }
38 |
39 | // and for the root
40 | if (selector(rootLayer)) {
41 | rootLayer = func(rootLayer)
42 | }
43 |
44 | return rootLayer
45 | }
46 |
47 | // traverse child, but do not change the layer tree
48 | function traverse(rootLayer, selector, func) {
49 | let childLayers = rootLayer.layers
50 |
51 | // traverse child
52 | if (childLayers) {
53 | childLayers.forEach(layer =>
54 | traverse(layer, selector, func)
55 | )
56 | }
57 |
58 | // and for the root
59 | if (selector(rootLayer)) {
60 | func(rootLayer)
61 | }
62 | }
63 |
64 | export {
65 | getPositionStyle,
66 | update,
67 | traverse,
68 | }
--------------------------------------------------------------------------------
/static/index.css:
--------------------------------------------------------------------------------
1 | html, body, #app, #document {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | font-family: 'Helvetica Neue', sans-serif;
7 | tab-size: 7;
8 | -webkit-font-smoothing: antialiased;
9 | }
10 |
11 | nav {
12 | position: fixed;
13 | top: 0;
14 | left: 0;
15 | width: 245px;
16 | height: 100%;
17 | background-color: #ECECEC;
18 | z-index: 10;
19 | }
20 |
21 | nav .page-nav {
22 | height: 28px;
23 | line-height: 28px;
24 | padding-left: 25px;
25 | }
26 | nav .page-nav:nth-child(even) {
27 | background-color: #F0F0F0;
28 | }
29 | nav .page-nav.active {
30 | background-color: #6D9EE1;
31 | color: white;
32 | }
33 |
34 | .page-viewer {
35 | position: absolute;
36 | top: 0;
37 | left: 245px;
38 | width: 100%;
39 | height: 100%;
40 | }
41 |
42 | .artboard {
43 | background: white;
44 | box-shadow: 0 0 5px 0 rgba(0,0,0,0.2);
45 | }
46 | .artboard .artboard-name {
47 | position: absolute;
48 | top: -25px;
49 | font-size: 13px;
50 | }
51 | .artboard .artboard-contents {
52 | position: relative;
53 | width: 100%;
54 | height: 100%;
55 | overflow: hidden;
56 | }
57 |
58 | p {
59 | margin: 0;
60 | }
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Sketch Web Viewer
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------