├── .editorconfig ├── .eslintrc ├── .gitignore ├── .jscsrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── dist ├── bundle.js ├── bundle.js.map ├── fonts │ ├── dancing-script-ot │ │ └── DancingScript-Regular.otf │ └── montserrat │ │ ├── Montserrat-Hairline.otf │ │ └── Montserrat-Light.otf └── index.html ├── package.json ├── screenshot.png ├── src ├── fonts │ ├── dancing-script-ot │ │ ├── DancingScript-Regular.otf │ │ └── SIL Open Font License.txt │ └── montserrat │ │ ├── Montserrat-Black.otf │ │ ├── Montserrat-Bold.otf │ │ ├── Montserrat-Hairline.otf │ │ ├── Montserrat-Light.otf │ │ ├── Montserrat-Regular.otf │ │ └── SIL Open Font License.txt ├── index.html ├── main.js ├── mode │ └── vfl │ │ ├── vfl.css │ │ └── vfl.js ├── styles.css ├── vflToLayout.js └── views │ ├── EditorView.js │ ├── InputView.js │ ├── OutputView.js │ ├── SettingsView.js │ └── VisualOutputView.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [*.js] 17 | indent_style = space 18 | indent_size = 4 19 | 20 | [Gruntfile.js] 21 | indent_style = space 22 | indent_size = 2 23 | 24 | [grunt/*.js] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.json] 29 | indent_style = space 30 | indent_size = 2 31 | 32 | [*.css] 33 | indent_style = space 34 | indent_size = 2 35 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "amd": true 6 | }, 7 | "ecmaFeatures": { 8 | "classes": true, 9 | "modules": true, 10 | "blockBindings": true, 11 | "arrowFunctions": true, 12 | "templateStrings": true 13 | }, 14 | "rules": { 15 | "quotes": [2, "single"], 16 | "no-underscore-dangle": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | npm-debug.log 4 | .ftppass 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "requireCurlyBraces": ["do", "try", "catch"], 4 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 5 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 6 | "disallowSpaceAfterPrefixUnaryOperators": true, 7 | "disallowKeywords": ["with"], 8 | "disallowMultipleLineBreaks": true, 9 | "requireBlocksOnNewline": true, 10 | "disallowMixedSpacesAndTabs": true, 11 | "disallowTrailingWhitespace": true, 12 | "requireLineFeedAtFileEnd": true, 13 | "requireSpacesInFunctionExpression": { 14 | "beforeOpeningCurlyBrace": true 15 | }, 16 | "disallowSpacesInFunctionExpression": { 17 | "beforeOpeningRoundBrace": true 18 | }, 19 | "disallowMultipleVarDecl": true, 20 | "disallowSpacesInsideParentheses": true 21 | } 22 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | eslint: { 7 | target: ['src/**/*.js', 'src/**/*.es6'], 8 | options: { 9 | config: '.eslintrc' 10 | } 11 | }, 12 | jscs: { 13 | src: ['src/**/*.js', 'src/**/*.es6'], 14 | options: { 15 | config: '.jscsrc' 16 | } 17 | }, 18 | 'ftp-deploy': { 19 | build: { 20 | auth: { 21 | host: 'ftp.pcextreme.nl', 22 | port: 21, 23 | authKey: 'gloey.nl' 24 | }, 25 | src: 'dist', 26 | dest: '/domains/gloey.nl/htdocs/www/apps/autolayout' 27 | } 28 | }, 29 | exec: { 30 | clean: 'rm -rf ./dist', 31 | build: 'webpack -p', 32 | 'build-debug': 'webpack -d', 33 | 'serve': 'webpack-dev-server -d --inline --reload=localhost', 34 | 'open-serve': 'open http://localhost:8080' 35 | } 36 | }); 37 | 38 | // These plugins provide necessary tasks. 39 | grunt.loadNpmTasks('grunt-eslint'); 40 | grunt.loadNpmTasks('grunt-jscs'); 41 | grunt.loadNpmTasks('grunt-ftp-deploy'); 42 | grunt.loadNpmTasks('grunt-exec'); 43 | 44 | // Default task. 45 | grunt.registerTask('default', ['eslint', 'jscs', 'exec:build-debug']); 46 | grunt.registerTask('clean', ['exec:clean']); 47 | grunt.registerTask('serve', ['eslint', 'jscs', 'exec:open-serve', 'exec:serve']); 48 | grunt.registerTask('deploy', ['eslint', 'jscs', 'exec:clean', 'exec:build-debug', 'ftp-deploy']); 49 | }; 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 IjzerenHein 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://rawgit.com/IjzerenHein/visualformat-editor/master/dist/index.html) 2 | 3 | *Click on the image above to start Visual Formatting :D* 4 | 5 | The Visual Format Editor allows you to parse and preview Apple's Visual Format Language. It is built using [Cassowary.js](https://github.com/slightlyoff/cassowary.js), [AutoLayout.js](https://github.com/IjzerenHein/autolayout.js), [famo.us](http://famous.org) and [famous-flex](https://github.com/IjzerenHein/famous-flex). 6 | 7 | 8 | ## Query arguments 9 | 10 | The Visual Format Editor can be customized by specifying query-arguments: 11 | 12 | |Argument|Type|Description 13 | |---|---|---| 14 | |`vfl`|`string`|The vfl definition to display.| 15 | |`extended`|`0` / `1`|Enables or disables extended vfl mode (default: enabled).| 16 | |`spacing`|`array`,`number`|Spacing to use (default: 8).| 17 | |`mode`|`string`|Appearence mode: `default`, `compact`, `nolog`, `preview`.| 18 | |`settings`|`0` / `1`|Shows or hides the settings pane (default: 1).| 19 | 20 | Example: 21 | 22 | visualformat-editor/.../index.html?spacing=[20,10]&extended=0 23 | 24 | 25 | ## Visual format meta info 26 | 27 | Additional meta-info can be added in the form of comments. Using this meta-info, you can instruct the viewer to for instance use a specific aspect ratio or a specific color for a sub-view: 28 | 29 | ```vfl 30 | //viewport aspect-ratio:3/1 max-height:300 31 | //colors red:#FF0000 green:#00FF00 blue:#0000FF 32 | //shapes red:circle green:circle blue:circle 33 | H:|-[row:[red(green,blue)]-[green]-[blue]]-| 34 | V:|[row]| 35 | ``` 36 | [View this example online](https://rawgit.com/IjzerenHein/visualformat-editor/master/dist/index.html?vfl=rgb) 37 | 38 | Meta-info is also processed by renderers. If you want to set the meta-info only for previewing purposes, then prefix it with a `-`. The following example sets the `max-width` and `max-height` for previewing a mobile layout. The actual layout renderer will ignore this meta-info. 39 | 40 | ```vfl 41 | //-viewport max-width:320 max-height:500 42 | //heights header:44 footer:44 43 | V:|[col:[header][content][footer]]| 44 | H:|[col]| 45 | ``` 46 | 47 | |Category|Property|Example| 48 | |--------|--------|-------| 49 | |`viewport`|`aspect-ratio:{width}/{height}`|`//viewport aspect-ratio:16/9`| 50 | ||`width:[{number}/intrinsic]`|`//viewport width:10`| 51 | ||`height:[{number}/intrinsic]`|`//viewport height:intrinsic`| 52 | ||`min-width:{number}`| 53 | ||`max-width:{number}`| 54 | ||`min-height:{number}`| 55 | ||`max-height:{number}`| 56 | |`spacing`|`[{number}/array]`|`//spacing:8` or `//spacing:[10, 20, 5]`| 57 | |`widths`|`{view-name}:[{number}/intrinsic]`|`//widths subview1:100`| 58 | |`heights`|`{view-name}:[{number}/intrinsic]`|`//heights subview1:intrinsic`| 59 | |`colors`|`{view-name}:{color}`|`//colors redview:#FF0000 blueview:#00FF00`| 60 | |`shapes`|`{view-name}:[circle/square]`|`//shapes avatar:circle`| 61 | 62 | 63 | ## Contribute 64 | 65 | If you like this project and want to support it, show some love 66 | and give it a star. 67 | 68 | 69 | © 2015 Hein Rutjes 70 | -------------------------------------------------------------------------------- /dist/fonts/dancing-script-ot/DancingScript-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/dist/fonts/dancing-script-ot/DancingScript-Regular.otf -------------------------------------------------------------------------------- /dist/fonts/montserrat/Montserrat-Hairline.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/dist/fonts/montserrat/Montserrat-Hairline.otf -------------------------------------------------------------------------------- /dist/fonts/montserrat/Montserrat-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/dist/fonts/montserrat/Montserrat-Light.otf -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
' : ''),
87 | size: [undefined, 124]
88 | });
89 | mainLC.insert('banner', banner);
90 |
91 | // Create input view
92 | var inputView = new InputView();
93 | mainLC.insert('input', inputView);
94 | inputView.editor.on('update', _update); //eslint-disable-line no-use-before-define
95 | inputView.settings.on('update', _updateSettings); //eslint-disable-line no-use-before-define
96 |
97 | // Create output view
98 | var outputView = new OutputView();
99 | mainLC.insert('output', outputView);
100 |
101 | // Create visualoutput view
102 | var visualOutputView = new VisualOutputView();
103 | mainLC.insert('visualOutput', visualOutputView);
104 |
105 | // Update handling
106 | function _update() {
107 | var constraints = outputView.parse(inputView.editor.visualFormat, inputView.settings.getExtended());
108 | if (constraints) {
109 | var view = new AutoLayout.View();
110 | view.addConstraints(constraints);
111 | visualOutputView.view = view;
112 | }
113 | _updateSettings(); //eslint-disable-line no-use-before-define
114 | _updateMetaInfo(); //eslint-disable-line no-use-before-define
115 | }
116 | function _updateMetaInfo() {
117 | var metaInfo = AutoLayout.VisualFormat.parseMetaInfo(inputView.editor.visualFormat, {prefix: '-'});
118 | visualOutputView.viewPort = metaInfo.viewport;
119 | visualOutputView.spacing = metaInfo.spacing;
120 | visualOutputView.colors = metaInfo.colors;
121 | visualOutputView.shapes = metaInfo.shapes;
122 | visualOutputView.widths = metaInfo.widths;
123 | visualOutputView.heights = metaInfo.heights;
124 | }
125 | function _updateSettings(forceParse) {
126 | if (forceParse) {
127 | return _update.call(this);
128 | }
129 | var view = visualOutputView.view;
130 | if (view) {
131 | inputView.settings.updateAutoLayoutView(view);
132 | visualOutputView.view = view;
133 | }
134 | }
135 | _update();
136 |
--------------------------------------------------------------------------------
/src/mode/vfl/vfl.css:
--------------------------------------------------------------------------------
1 | .cm-s-vfl span.cm-def {color: #D8D8D8;}
2 | .cm-s-vfl span.cm-atom {color: #62a35c;}
3 | .cm-s-vfl span.cm-meta {color: #a71d5d;}
4 | .cm-s-vfl span.cm-number {color: #0086b3;}
5 | .cm-s-vfl span.cm-bracket {color: #193691;}
6 | .cm-s-vfl span.cm-keyword {color: #193691; font-weight: bold;}
7 | .cm-s-vfl span.cm-variable {color: #f09e53;}
8 | .cm-s-vfl span.cm-operator {color: #795da3;}
9 | .cm-s-vfl span.cm-comment {color: #999999;}
10 |
--------------------------------------------------------------------------------
/src/mode/vfl/vfl.js:
--------------------------------------------------------------------------------
1 | /* Visual format language definition.
2 | */
3 | /*eslint quotes:[2, "double"]*/
4 | var CodeMirror = require("codemirror");
5 | require("codemirror/addon/mode/simple");
6 | CodeMirror.defineSimpleMode("vfl", {
7 | // The start state contains the rules that are intially used
8 | start: [
9 | {regex: /^(HV|H|V|Z|C)/, token: "meta", push: "orientation"},
10 | {regex: /\|/, token: "keyword"},
11 | {regex: /->/, token: "def"},
12 | {regex: /-/, token: "def", push: "connection"},
13 | {regex: /~/, token: "def", push: "connection"},
14 | {regex: /\[/, token: "bracket", push: "view"},
15 | {regex: /\(/, token: "atom", push: "predicates"},
16 | {regex: /\w+/, token: "variable"},
17 | {regex: /\./, token: "meta", push: "attribute"},
18 | {regex: /.*\/\/.*/, token: "comment"}
19 | ],
20 | orientation: [
21 | {regex: /:/, token: "def", pop: true}
22 | ],
23 | connection: [
24 | {regex: /\(/, token: "atom", push: "predicates"},
25 | {regex: /[0-9]+/, token: "number"},
26 | {regex: /\[/, token: "bracket", pop: true, push: "view"},
27 | {regex: /|/, token: "bracket", pop: true},
28 | {regex: /-/, token: "def", pop: true},
29 | {regex: /~/, token: "def", pop: true}
30 | ],
31 | view: [
32 | {regex: /\]/, token: "bracket", pop: true},
33 | {regex: /\(/, token: "atom", push: "predicates"},
34 | {regex: /\w(\.\.\d+)?/, token: "variable"}
35 | ],
36 | predicates: [
37 | {regex: /\)/, token: "atom", pop: true},
38 | {regex: /[0-9]+/, token: "number"},
39 | {regex: /[=><]=/, token: "operator"},
40 | {regex: /[\*\/]/, token: "operator", push: "operator"},
41 | {regex: /\./, token: "meta", push: "attribute"},
42 | {regex: /\w+/, token: "variable"}
43 | ],
44 | operator: [
45 | {regex: /\d+/, token: "number", pop: true}
46 | ],
47 | attribute: [
48 | {regex: /left/, token: "meta", pop: true},
49 | {regex: /top/, token: "meta", pop: true},
50 | {regex: /right/, token: "meta", pop: true},
51 | {regex: /bottom/, token: "meta", pop: true},
52 | {regex: /width/, token: "meta", pop: true},
53 | {regex: /height/, token: "meta", pop: true},
54 | {regex: /centerX/, token: "meta", pop: true},
55 | {regex: /centerY/, token: "meta", pop: true}
56 | ]
57 | });
58 |
59 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Montserrat';
3 | src: url('./fonts/montserrat/Montserrat-Hairline.otf');
4 | font-weight: 100;
5 | }
6 |
7 | @font-face {
8 | font-family: 'Montserrat';
9 | src: url('./fonts/montserrat/Montserrat-Light.otf');
10 | font-weight: 200;
11 | }
12 |
13 | @font-face {
14 | font-family: 'DancingScriptOT';
15 | src: url('./fonts/dancing-script-ot/DancingScript-Regular.otf');
16 | }
17 |
18 | body {
19 | color: #555555;
20 | font-family: 'Montserrat';
21 | font-weight: 200;
22 | font-size: 15px;
23 | }
24 | textarea, input {
25 | font-family: 'droid sans mono', monospace, 'courier new', courier, sans-serif;
26 | font-size: 17px;
27 | border: 1px solid #DDDDDD;
28 | -moz-tab-size: 2;
29 | -o-tab-size: 2;
30 | tab-size: 2;
31 | resize: none;
32 | }
33 | .CodeMirror {
34 | position: absolute !important;
35 | width: 100% !important;
36 | height: 100% !important;
37 | }
38 | input {
39 | text-align: center;
40 | }
41 | .banner {
42 | font-weight: 100;
43 | font-size: 60px;
44 | text-align: center;
45 | color: black;
46 | z-index: 10;
47 | }
48 | .banner > iframe {
49 | position: absolute;
50 | left: 10px;
51 | top: 10px;
52 | }
53 | .banner .subTitle {
54 | font-family: DancingScriptOT;
55 | font-weight: bold;
56 | font-size: 26px;
57 | color: orange;
58 | }
59 | .ff-tabbar.selectedItemOverlay {
60 | border-bottom: 3px solid orange;
61 | }
62 | .ff-tabbar.item > div {
63 | text-overflow: ellipsis;
64 | white-space: nowrap;
65 | overflow: hidden;
66 | }
67 | .ff-tabbar.item.selected {
68 | color: orange;
69 | font-weight: bold;
70 | }
71 | .setting.text {
72 | text-align: right;
73 | }
74 |
75 | .constraints, .log, .raw {
76 | overflow: scroll;
77 | font-size: 17px;
78 | }
79 | .log {
80 | padding: 5px;
81 | }
82 |
83 | .va {
84 | /* align vertical */
85 | display: block;
86 | position: relative;
87 | top: 50%;
88 | -webkit-transform: translateY(-50%);
89 | -moz-transform: translateY(-50%);
90 | -ms-transform: translateY(-50%);
91 | -o-transform: translateY(-50%);
92 | transform: translateY(-50%);
93 | }
94 |
95 | .subView {
96 | border: 1px solid #DDDDDD;
97 | border-radius: 5px;
98 | text-align: center;
99 | }
100 | .subView.circle {
101 | border-radius: 50%;
102 | }
103 |
--------------------------------------------------------------------------------
/src/vflToLayout.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This Source Code is licensed under the MIT license. If a copy of the
3 | * MIT-license was not distributed with this file, You can obtain one at:
4 | * http://opensource.org/licenses/mit-license.html.
5 | *
6 | * @author: Hein Rutjes (IjzerenHein)
7 | * @license MIT
8 | * @copyright Gloey Apps, 2015
9 | */
10 |
11 | // import dependencies
12 | var AutoLayout = require('autolayout');
13 |
14 | function _setSpacing(view, spacing) {
15 | view.setSpacing(spacing);
16 | }
17 |
18 | function _setIntrinsicWidths(context, view, widths) {
19 | for (var key in widths) {
20 | var subView = view.subViews[key];
21 | if (subView) {
22 | subView.intrinsicWidth = (widths[key] === true) ? context.resolveSize(key, context.size)[0] : widths[key];
23 | }
24 | }
25 | }
26 |
27 | function _setIntrinsicHeights(context, view, heights) {
28 | for (var key in heights) {
29 | var subView = view.subViews[key];
30 | if (subView) {
31 | subView.intrinsicHeight = (heights[key] === true) ? context.resolveSize(key, context.size)[1] : heights[key];
32 | }
33 | }
34 | }
35 |
36 | function _setViewPortSize(context, view, vp) {
37 | var size = [
38 | ((vp.width !== undefined) && (vp.width !== true)) ? vp.width : Math.max(Math.min(context.size[0], vp['max-width'] || context.size[0]), vp['min-width'] || 0),
39 | ((vp.height !== undefined) && (vp.height !== true)) ? vp.height : Math.max(Math.min(context.size[1], vp['max-height'] || context.size[1]), vp['min-height'] || 0)
40 | ];
41 | if ((vp.width === true) && (vp.height === true)) {
42 | size[0] = view.fittingWidth;
43 | size[1] = view.fittingHeight;
44 | }
45 | else if (vp.width === true) {
46 | view.setSize(undefined, size[1]);
47 | size[0] = view.fittingWidth;
48 | // TODO ASPECT RATIO?
49 | }
50 | else if (vp.height === true) {
51 | view.setSize(size[0], undefined);
52 | size[1] = view.fittingHeight;
53 | // TODO ASPECT RATIO?
54 | }
55 | else {
56 | size = vp['aspect-ratio'] ? [
57 | Math.min(size[0], size[1] * vp['aspect-ratio']),
58 | Math.min(size[1], size[0] / vp['aspect-ratio'])
59 | ] : size;
60 | view.setSize(size[0], size[1]);
61 | }
62 | return size;
63 | }
64 |
65 | function vflToLayout(visualFormat, viewOptions) {
66 | var view = new AutoLayout.View(viewOptions);
67 | try {
68 | var constraints = AutoLayout.VisualFormat.parse(visualFormat, {extended: true, strict: false});
69 | var metaInfo = AutoLayout.VisualFormat.parseMetaInfo ? AutoLayout.VisualFormat.parseMetaInfo(visualFormat) : {};
70 | view.addConstraints(constraints);
71 | var dummyOptions = {};
72 | return function(context, options) {
73 | options = options || dummyOptions;
74 | var key;
75 | var subView;
76 | var x;
77 | var y;
78 | var zIndex = options.zIndex || 0;
79 | if (options.spacing || metaInfo.spacing) {
80 | _setSpacing(view, options.spacing || metaInfo.spacing);
81 | }
82 | if (options.widths || metaInfo.widths) {
83 | _setIntrinsicWidths(context, view, options.widths || metaInfo.widths);
84 | }
85 | if (options.heights || metaInfo.heights) {
86 | _setIntrinsicHeights(context, view, options.heights || metaInfo.heights);
87 | }
88 | if (options.viewport || metaInfo.viewport) {
89 | var size = _setViewPortSize(context, view, options.viewport || metaInfo.viewport);
90 | x = (context.size[0] - size[0]) / 2;
91 | y = (context.size[1] - size[1]) / 2;
92 | }
93 | else {
94 | view.setSize(context.size[0], context.size[1]);
95 | x = 0;
96 | y = 0;
97 | }
98 | for (key in view.subViews) {
99 | subView = view.subViews[key];
100 | if ((key.indexOf('_') !== 0) && (subView.type !== 'stack')) {
101 | context.set(subView.name, {
102 | size: [subView.width, subView.height],
103 | translate: [x + subView.left, y + subView.top, zIndex + (subView.zIndex * 5)]
104 | });
105 | }
106 | }
107 | };
108 | }
109 | catch (err) {
110 | console.log(err); //eslint-disable-line no-console
111 | }
112 | }
113 |
114 | module.exports = vflToLayout;
115 |
--------------------------------------------------------------------------------
/src/views/EditorView.js:
--------------------------------------------------------------------------------
1 | import View from 'famous/core/View';
2 | import LayoutController from 'famous-flex/LayoutController';
3 | import vflToLayout from '../vflToLayout';
4 | import Surface from 'famous/core/Surface';
5 | import CodeMirror from 'codemirror';
6 | import VflMode from '../mode/vfl/vfl'; //eslint-disable-line no-unused-vars
7 |
8 | function getParameterByName(name) {
9 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
10 | var regex = new RegExp('[\\?&]' + name + '=([^]*)');
11 | var results = regex.exec(location.search);
12 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
13 | }
14 |
15 | var vfl = getParameterByName('vfl');
16 | if (vfl === 'example') {
17 | vfl = `|-[child1(child3)]-[child3]-|
18 | |-[child2(child4)]-[child4]-|
19 | [child5(child4)]-|
20 | V:|-[child1(child2)]-[child2]-|
21 | V:|-[child3(child4,child5)]-[child4]-[child5]-|`;
22 | }
23 | if (vfl === 'stack') {
24 | vfl = `V:|-[col1:[child1(child2,child3)]-[child2]-[child3]]-|
25 | V:|-[col2:[child4(child5)]-[child5]]-|
26 | H:|-[col1(col2)]-[col2]-|`;
27 | }
28 | if (vfl === 'rgb') {
29 | vfl = `//viewport aspect-ratio:3/1 max-height:300
30 | //colors red:#FF0000 green:#00FF00 blue:#0000FF
31 | //shapes red:circle green:circle blue:circle
32 | H:|-[row:[red(green,blue)]-[green]-[blue]]-|
33 | V:|[row]|`;
34 | }
35 | vfl = vfl || `|-[child(==child2/2)]-[child2]-|
36 | V:|-[child]-|
37 | V:|-[child2]-|`;
38 |
39 | class EditorView extends View {
40 | constructor(options) {
41 | super(options);
42 |
43 | this.elm = document.createElement('div');
44 | this.surface = new Surface({
45 | content: this.elm,
46 | classes: ['editor']
47 | });
48 | this.surface.on('deploy', () => {
49 | if (!this.editor) {
50 | this.editor = new CodeMirror(this.elm, {
51 | lineNumbers: true,
52 | theme: 'vfl'
53 | });
54 | this.editor.setValue(vfl);
55 | this.editor.on('change', this._onChange.bind(this));
56 | }
57 | });
58 |
59 | this.layout = new LayoutController({
60 | layout: vflToLayout(`
61 | |[content]|
62 | V:|[content]|
63 | `),
64 | dataSource: {
65 | content: this.surface
66 | }
67 | });
68 | this.add(this.layout);
69 | }
70 |
71 | _onChange() {
72 | var val = this.editor.getValue();
73 | if (val !== this._vfl) {
74 | this._vfl = val;
75 | this._eventOutput.emit('update');
76 | }
77 | }
78 |
79 | get visualFormat() {
80 | return this.editor ? this.editor.getValue() : vfl;
81 | }
82 | }
83 |
84 | export {EditorView as default};
85 |
--------------------------------------------------------------------------------
/src/views/InputView.js:
--------------------------------------------------------------------------------
1 | import View from 'famous/core/View';
2 | import LayoutController from 'famous-flex/LayoutController';
3 | import vflToLayout from '../vflToLayout';
4 | import TabBarController from 'famous-flex/widgets/TabBarController';
5 | import EditorView from './EditorView';
6 | import SettingsView from './SettingsView';
7 |
8 | function getParameterByName(name) {
9 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
10 | var regex = new RegExp('[\\?&]' + name + '=([^]*)');
11 | var results = regex.exec(location.search);
12 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
13 | }
14 |
15 | class InputView extends View {
16 | constructor(options) {
17 | super(options);
18 |
19 | this.tabBarController = new TabBarController({
20 | tabBarSize: 44,
21 | tabBarPosition: TabBarController.Position.TOP,
22 | tabBar: {
23 | createRenderables: {
24 | selectedItemOverlay: true
25 | }
26 | }
27 | });
28 |
29 | this.editor = new EditorView();
30 | this.settings = new SettingsView();
31 |
32 | this.tabBarController.setItems([
33 | {tabItem: 'Visual Format', view: this.editor},
34 | {tabItem: 'Settings', view: this.settings}
35 | ]);
36 |
37 | this.layout = new LayoutController({
38 | layout: vflToLayout(`
39 | |[content]|
40 | V:|[content]|
41 | `),
42 | dataSource: {
43 | content: parseInt(getParameterByName('settings') || '1') ? this.tabBarController : this.editor
44 | }
45 | });
46 |
47 | this.add(this.layout);
48 | }
49 | }
50 |
51 | export {InputView as default};
52 |
--------------------------------------------------------------------------------
/src/views/OutputView.js:
--------------------------------------------------------------------------------
1 | import View from 'famous/core/View';
2 | import Surface from 'famous/core/Surface';
3 | import LayoutController from 'famous-flex/LayoutController';
4 | import TabBarController from 'famous-flex/widgets/TabBarController';
5 | import vflToLayout from '../vflToLayout';
6 | import AutoLayout from 'autolayout';
7 |
8 | class OutputView extends View {
9 | constructor(options) {
10 | super(options);
11 |
12 | this.tabBarController = new TabBarController({
13 | tabBarSize: 44,
14 | tabBarPosition: TabBarController.Position.TOP,
15 | tabBar: {
16 | createRenderables: {
17 | selectedItemOverlay: true
18 | }
19 | }
20 | });
21 |
22 | this.constraints = new Surface({
23 | classes: ['constraints']
24 | });
25 | this.logContent = '';
26 | this.log = new Surface({
27 | classes: ['log']
28 | });
29 | this.log.commit = function() {
30 | const res = Surface.prototype.commit.apply(this.log, arguments);
31 | if (this._scrollToBottom) {
32 | this._scrollToBottom = false;
33 | this.log._currentTarget.scrollTop = Math.max(0, this.log._currentTarget.scrollHeight - this.log._currentTarget.clientHeight);
34 | }
35 | return res;
36 | }.bind(this);
37 | this.raw = new Surface({
38 | classes: ['raw']
39 | });
40 | this.tabBarController.setItems([
41 | {tabItem: 'Log', view: this.log},
42 | {tabItem: 'Constraints', view: this.constraints},
43 | {tabItem: 'Raw', view: this.raw}
44 | ]);
45 |
46 | this.layout = new LayoutController({
47 | layout: vflToLayout(`
48 | |[content]|
49 | V:|[content]|
50 | `),
51 | dataSource: {
52 | content: this.tabBarController
53 | }
54 | });
55 | this.add(this.layout);
56 | }
57 |
58 | _log(message) {
59 | this.logContent += message;
60 | this.log.setContent(this.logContent);
61 | this._scrollToBottom = true;
62 | }
63 |
64 | parse(visualFormat, extended) {
65 | visualFormat = visualFormat.replace(/[\\]/g, '\n');
66 | try {
67 | const json = visualFormat.replace(/["']/g, '\"');
68 | visualFormat = JSON.parse(json);
69 | } catch (err) {
70 | //
71 | }
72 | try {
73 | // update constraints
74 | const constraints = AutoLayout.VisualFormat.parse(visualFormat, {extended: extended, strict: false});
75 | this.constraints.setContent('' + JSON.stringify(constraints, undefined, 2) + ''); 76 | // update raw 77 | const raw = AutoLayout.VisualFormat.parse(visualFormat, {extended: extended, outFormat: 'raw'}); 78 | this.raw.setContent('
' + JSON.stringify(raw, undefined, 2) + ''); 79 | // update log 80 | this._log('
Visual format parsed successfully.' +
91 | 'ERROR: ' +
92 | '' + err.source.substring(0, err.column - 1) + '' +
93 | err.source.substring(err.column - 1) + '\n' +
94 | 'line ' + err.line + arrow + (new Array(2 + err.column - arrow.length - ('' + err.line).length)).join(' ') + '^ ' + err.message +
95 | ''
96 | );
97 | }
98 | else {
99 | this._log('ERROR: ' + err.toString() + ''); 100 | } 101 | } 102 | } 103 | } 104 | 105 | export {OutputView as default}; 106 | -------------------------------------------------------------------------------- /src/views/SettingsView.js: -------------------------------------------------------------------------------- 1 | import View from 'famous/core/View'; 2 | import InputSurface from 'famous/surfaces/InputSurface'; 3 | import Surface from 'famous/core/Surface'; 4 | import LayoutController from 'famous-flex/LayoutController'; 5 | import vflToLayout from '../vflToLayout'; 6 | 7 | function getParameterByName(name) { 8 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 9 | var regex = new RegExp('[\\?&]' + name + '=([^]*)'); 10 | var results = regex.exec(location.search); 11 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); 12 | } 13 | 14 | class SettingsView extends View { 15 | constructor(options) { 16 | super(options); 17 | 18 | this._extendedFormat = (getParameterByName('extended') !== '') ? (parseInt(getParameterByName('extended')) !== 0) : 1; 19 | 20 | this._spacing = 8; 21 | try { 22 | this._spacing = JSON.parse(getParameterByName('spacing')); 23 | } 24 | catch (err) { 25 | // 26 | } 27 | 28 | this.renderables = { 29 | widthText: new Surface({ 30 | content: '