├── .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 | [![Screenshot](screenshot.png)](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 | AutoLayout.js - Visual Format Editor 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visualformat-editor", 3 | "private": "true", 4 | "version": "0.0.1", 5 | "engines": { 6 | "node": ">= 0.10.0" 7 | }, 8 | "devDependencies": { 9 | "autolayout": "^0.5.3", 10 | "babel-core": "^5.5.3", 11 | "babel-loader": "^5.1.4", 12 | "codemirror": "^5.3.0", 13 | "css-loader": "^0.7.0", 14 | "expose-loader": "^0.5.3", 15 | "famous": "^0.3.5", 16 | "famous-autolayout": "^0.2.6", 17 | "famous-flex": "^0.3.5", 18 | "famous-polyfills": "^0.2.2", 19 | "fastclick": "^1.0.3", 20 | "file-loader": "^0.6.1", 21 | "glob": "^4.0.5", 22 | "grunt": "latest", 23 | "grunt-eslint": "latest", 24 | "grunt-exec": "latest", 25 | "grunt-ftp-deploy": "latest", 26 | "grunt-jscs": "latest", 27 | "html-loader": "^0.2.2", 28 | "ismobilejs": "^0.3.5", 29 | "json-loader": "^0.5.1", 30 | "less": "^1.7.4", 31 | "less-loader": "^0.7.5", 32 | "moment": "^2.9.0", 33 | "optimist": "^0.6.1", 34 | "script-loader": "^0.6.0", 35 | "style-loader": "^0.6.4", 36 | "url-loader": "^0.5.5", 37 | "webpack": "latest", 38 | "webpack-dev-server": "latest" 39 | }, 40 | "scripts": {} 41 | } 42 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/screenshot.png -------------------------------------------------------------------------------- /src/fonts/dancing-script-ot/DancingScript-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/dancing-script-ot/DancingScript-Regular.otf -------------------------------------------------------------------------------- /src/fonts/dancing-script-ot/SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Pablo Impallari (www.impallari.com|impallari@gmail.com), 2 | Copyright (c) 2010, Igino Marini. (www.ikern.com|mail@iginomarini.com), 3 | with Reserved Font Name Dancing Script. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 21 | 22 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /src/fonts/montserrat/Montserrat-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/montserrat/Montserrat-Black.otf -------------------------------------------------------------------------------- /src/fonts/montserrat/Montserrat-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/montserrat/Montserrat-Bold.otf -------------------------------------------------------------------------------- /src/fonts/montserrat/Montserrat-Hairline.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/montserrat/Montserrat-Hairline.otf -------------------------------------------------------------------------------- /src/fonts/montserrat/Montserrat-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/montserrat/Montserrat-Light.otf -------------------------------------------------------------------------------- /src/fonts/montserrat/Montserrat-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IjzerenHein/visualformat-editor/38e3c3630b75503ecd9d4bee79f8f4cf0f576341/src/fonts/montserrat/Montserrat-Regular.otf -------------------------------------------------------------------------------- /src/fonts/montserrat/SIL Open Font License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat' 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 7 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 15 | 16 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 17 | 18 | DEFINITIONS 19 | "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 20 | 21 | "Reserved Font Name" refers to any names specified as such after the copyright statement(s). 22 | 23 | "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). 24 | 25 | "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 26 | 27 | "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 28 | 29 | PERMISSION & CONDITIONS 30 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 31 | 32 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 33 | 34 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 35 | 36 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 37 | 38 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 39 | 40 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 41 | 42 | TERMINATION 43 | This license becomes null and void if any of the above conditions are not met. 44 | 45 | DISCLAIMER 46 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AutoLayout.js - Visual Format Editor 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.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 | // 12 | require('famous-polyfills'); 13 | require('famous/core/famous.css'); 14 | require('famous-flex/widgets/styles.css'); 15 | require('./styles.css'); 16 | require('./index.html'); 17 | require('codemirror/lib/codemirror.css'); 18 | require('./mode/vfl/vfl.css'); 19 | // 20 | 21 | function getParameterByName(name) { 22 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 23 | var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); 24 | var results = regex.exec(location.search); 25 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); 26 | } 27 | 28 | // Fast-click 29 | var FastClick = require('fastclick/lib/fastclick'); 30 | FastClick.attach(document.body); 31 | 32 | // import dependencies 33 | var Engine = require('famous/core/Engine'); 34 | var LayoutController = require('famous-flex/LayoutController'); 35 | var AutoLayout = require('autolayout'); 36 | var InputView = require('./views/InputView'); 37 | var OutputView = require('./views/OutputView'); 38 | var VisualOutputView = require('./views/VisualOutputView'); 39 | var vflToLayout = require('./vflToLayout'); 40 | var Surface = require('famous/core/Surface'); 41 | 42 | // create the main context and layout 43 | var mainContext = Engine.createContext(); 44 | var layout; 45 | switch (getParameterByName('mode')) { 46 | case 'preview': 47 | layout = vflToLayout(` 48 | |-[visualOutput]-| 49 | V:|-[visualOutput]-| 50 | `); 51 | break; 52 | case 'compact': 53 | layout = vflToLayout(` 54 | V:|-[input(output)]-[output]-| 55 | V:|-[visualOutput]-| 56 | |-[input(output,visualOutput)]-[visualOutput]-| 57 | |-[output]-[visualOutput]-| 58 | `, {spacing: [10, 10]}); 59 | break; 60 | case 'nolog': 61 | layout = vflToLayout(` 62 | V:|-[input]-| 63 | V:|-[visualOutput]-| 64 | |-[input(visualOutput)]-[visualOutput]-| 65 | `, {spacing: [10, 10]}); 66 | break; 67 | default: 68 | layout = vflToLayout(` 69 | //heights banner:intrinsic 70 | |[banner]| 71 | V:|[banner]-[input(output)]-[output]-| 72 | V:[banner]-[visualOutput]-| 73 | |-[input(output,visualOutput)]-[visualOutput]-| 74 | |-[output]-[visualOutput]-| 75 | `, {spacing: [10, 10]}); 76 | } 77 | var mainLC = new LayoutController({ 78 | layout: layout 79 | }); 80 | mainContext.add(mainLC); 81 | 82 | // Create banner 83 | var banner = new Surface({ 84 | classes: ['banner'], 85 | content: '
AUTOLAYOUT.JS
Visual Format Editor
' + 86 | (parseInt(getParameterByName('fork') || '1') ? 'Fork me on GitHub' : ''), 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.
'); 81 | return constraints; 82 | } 83 | catch (err) { 84 | if ((err instanceof SyntaxError) || 85 | (err.name === 'SyntaxError') || 86 | (err.line && err.column)) { 87 | this.constraints.setContent(''); 88 | this.raw.setContent(''); 89 | let arrow = (err.column > 10) ? ' --->' : ''; 90 | this._log('
' +
 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: '
Width:
' 31 | }), 32 | widthInput: new InputSurface({ 33 | placeholder: 'auto' 34 | }), 35 | heightText: new Surface({ 36 | content: '
Height:
' 37 | }), 38 | heightInput: new InputSurface({ 39 | placeholder: 'auto' 40 | }), 41 | spacingText: new Surface({ 42 | content: '
Spacing:
', 43 | classes: ['setting', 'text'] 44 | }), 45 | spacingInput: new InputSurface({ 46 | value: JSON.stringify(this._spacing), 47 | classes: ['setting', 'input'] 48 | }), 49 | extendedText: new Surface({ 50 | content: '
Extended format (EVFL):
', 51 | classes: ['setting', 'text'] 52 | }), 53 | extendedInput: new InputSurface({ 54 | type: 'checkbox', 55 | classes: ['setting', 'input'] 56 | }) 57 | }; 58 | if (this._extendedFormat) { 59 | this.renderables.extendedInput.setAttributes({ 60 | checked: true 61 | }); 62 | } 63 | this.layout = new LayoutController({ 64 | layout: vflToLayout(` 65 | |[spacing:[spacingText(spacingInput)]-[spacingInput]]| 66 | |[extended:[extendedText(extendedInput)]-[extendedInput]]| 67 | V:|-[spacing(30)]-[extended(30)] 68 | `), 69 | dataSource: this.renderables 70 | }); 71 | this.add(this.layout); 72 | this.renderables.spacingInput.on('change', this._updateSpacing.bind(this)); 73 | this.renderables.spacingInput.on('keyup', this._updateSpacing.bind(this)); 74 | 75 | this.renderables.extendedInput.on('change', this._updateExtended.bind(this)); 76 | } 77 | 78 | _updateSpacing() { 79 | try { 80 | const spacing = JSON.parse(this.renderables.spacingInput.getValue()); 81 | if (spacing !== this._spacing) { 82 | this._spacing = spacing; 83 | this._eventOutput.emit('update'); 84 | } 85 | } 86 | catch (err) { 87 | // 88 | } 89 | } 90 | 91 | _updateExtended() { 92 | this._extendedFormat = this.renderables.extendedInput.getAttributes().checked; 93 | if (this.renderables.extendedInput._currentTarget) { 94 | this._extendedFormat = this.renderables.extendedInput._currentTarget.checked ? true : false; 95 | } 96 | this._eventOutput.emit('update', true); 97 | } 98 | 99 | updateAutoLayoutView(alView) { 100 | if (this._spacing !== undefined) { 101 | alView.setSpacing(this._spacing); 102 | } 103 | } 104 | 105 | getExtended() { 106 | return this._extendedFormat; 107 | } 108 | } 109 | 110 | export {SettingsView as default}; 111 | -------------------------------------------------------------------------------- /src/views/VisualOutputView.js: -------------------------------------------------------------------------------- 1 | import View from 'famous/core/View'; 2 | import Surface from 'famous/core/Surface'; 3 | import LayoutController from 'famous-flex/LayoutController'; 4 | import Colors from 'colors.js'; 5 | 6 | class VisualOutputView extends View { 7 | constructor(options) { 8 | super(options); 9 | 10 | this._spacing = undefined; 11 | this._viewPort = {}; 12 | this._colors = {}; 13 | this._shapes = {}; 14 | this._intrinsicWidths = {}; 15 | this._intrinsicHeight = {}; 16 | 17 | this.layout = new LayoutController({ 18 | flow: true, 19 | flowOptions: { 20 | reflowOnResize: false 21 | }, 22 | layout: (context) => { 23 | if (!this.alView) { 24 | return; 25 | } 26 | if (this._spacing) { 27 | this.alView.setSpacing(this._spacing); 28 | } 29 | const iw = this._widths; 30 | var key; 31 | for (key in iw) { 32 | const subView = this.alView.subViews[key]; 33 | if (subView) { 34 | subView.intrinsicWidth = iw[key]; 35 | } 36 | } 37 | const ih = this._heights; 38 | for (key in ih) { 39 | const subView = this.alView.subViews[key]; 40 | if (subView) { 41 | subView.intrinsicHeight = ih[key]; 42 | } 43 | } 44 | const vp = this._viewPort; 45 | var contentSize = [ 46 | ((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), 47 | ((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) 48 | ]; 49 | if ((vp.width === true) && (vp.height === true)) { 50 | contentSize[0] = this.alView.fittingWidth; 51 | contentSize[1] = this.alView.fittingHeight; 52 | } 53 | else if (vp.width === true) { 54 | this.alView.setSize(undefined, contentSize[1]); 55 | contentSize[0] = this.alView.fittingWidth; 56 | // TODO ASPECT RATIO? 57 | } 58 | else if (vp.height === true) { 59 | this.alView.setSize(contentSize[0], undefined); 60 | contentSize[1] = this.alView.fittingHeight; 61 | // TODO ASPECT RATIO? 62 | } 63 | else { 64 | contentSize = vp['aspect-ratio'] ? [ 65 | Math.min(contentSize[0], contentSize[1] * vp['aspect-ratio']), 66 | Math.min(contentSize[1], contentSize[0] / vp['aspect-ratio']) 67 | ] : contentSize; 68 | this.alView.setSize(contentSize[0], contentSize[1]); 69 | } 70 | var left = (context.size[0] - contentSize[0]) / 2; 71 | var top = (context.size[1] - contentSize[1]) / 2; 72 | for (key in this.alView.subViews) { 73 | const subView = this.alView.subViews[key]; 74 | if ((subView.type !== 'stack') && (key.indexOf('_') !== 0)) { 75 | const node = context.get(subView.name); 76 | context.set(node, { 77 | size: [subView.width, subView.height], 78 | translate: [left + subView.left, top + subView.top, subView.zIndex * 5] 79 | }); 80 | var color = 204 - (subView.zIndex * 20); 81 | var backgroundColor = this._colors[key] || Colors.rgb2hex(color, color, color); 82 | var textColor = Colors.complement(backgroundColor); 83 | node.renderNode.setProperties({ 84 | backgroundColor: backgroundColor, 85 | color: textColor 86 | }); 87 | } 88 | } 89 | } 90 | }); 91 | this.add(this.layout); 92 | }; 93 | 94 | set view(alView) { 95 | this.alView = alView; 96 | this.contentRenderables = this.contentRenderables || {}; 97 | this.contentPool = this.contentPool || {}; 98 | for (var key in this.contentRenderables) { 99 | this.contentPool[key] = this.contentRenderables[key]; 100 | } 101 | this.contentRenderables = {}; 102 | if (this.alView) { 103 | for (var subView in this.alView.subViews) { 104 | if (subView.indexOf('_') !== 0) { 105 | this.contentRenderables[subView] = this.contentPool[subView] || new Surface({ 106 | content: '
' + subView + '
', 107 | classes: ['subView'] 108 | }); 109 | } 110 | } 111 | } 112 | this.layout.setDataSource(this.contentRenderables); 113 | } 114 | 115 | get view() { 116 | return this.alView; 117 | } 118 | 119 | get viewPort() { 120 | return this._viewPort; 121 | } 122 | 123 | set viewPort(value) { 124 | this._viewPort = value || {}; 125 | this.layout.reflowLayout(); 126 | } 127 | 128 | get spacing() { 129 | return this._spacing; 130 | } 131 | 132 | set spacing(value) { 133 | this._spacing = value; 134 | this.layout.reflowLayout(); 135 | } 136 | 137 | get widths() { 138 | return this._widths; 139 | } 140 | 141 | set widths(value) { 142 | this._widths = value || {}; 143 | this.layout.reflowLayout(); 144 | } 145 | 146 | get heights() { 147 | return this._heights; 148 | } 149 | 150 | set heights(value) { 151 | this._heights = value || {}; 152 | this.layout.reflowLayout(); 153 | } 154 | 155 | get colors() { 156 | return this._colors; 157 | } 158 | 159 | set colors(colors) { 160 | this._colors = colors || {}; 161 | this.layout.reflowLayout(); 162 | } 163 | 164 | get shapes() { 165 | return this._shapes; 166 | } 167 | 168 | set shapes(shapes) { 169 | this._shapes = shapes || {}; 170 | for (var key in this.contentRenderables) { 171 | if (this._shapes[key] === 'circle') { 172 | this.contentRenderables[key].addClass('circle'); 173 | } 174 | else { 175 | this.contentRenderables[key].removeClass('circle'); 176 | } 177 | } 178 | } 179 | } 180 | 181 | export {VisualOutputView as default}; 182 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*global module, process*/ 2 | /*eslint no-use-before-define:0 */ 3 | 4 | var webpack = require('webpack'); 5 | var webpackDevServer = require('webpack-dev-server'); 6 | var path = require('path'); 7 | 8 | // Support for extra commandline arguments 9 | var argv = require('optimist') 10 | //--env=XXX: sets a global ENV variable (i.e. window.ENV='XXX') 11 | .alias('e','env').default('e','dev') 12 | .argv; 13 | 14 | var config = { 15 | context: path.join(__dirname, 'src'), 16 | entry: {'bundle': './main'}, 17 | output:{ 18 | path: path.join(__dirname, 'dist'), 19 | filename: '[name].js', 20 | publicPath: isDevServer() ? '/' : '' 21 | }, 22 | devServer: { 23 | publicPath: '/' 24 | }, 25 | reload: isDevServer() ? 'localhost' : null, 26 | module:{ 27 | loaders:[ 28 | { test: /visualformat\-editor\/src\/.*\.js$/, loader: 'babel-loader'}, 29 | { test: /\.json$/, loader: 'json-loader' }, 30 | { test: /\.css$/, loader: 'style-loader!css-loader' }, 31 | { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, 32 | { test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=5000&name=[path][name].[ext]&context=./src' }, 33 | { test: /\.(eot|ttf|otf|woff)$/,loader: 'file-loader?name=[path][name].[ext]&context=./src' }, 34 | { test: /\.svg$/, loader: 'file-loader?name=[path][name].[ext]&context=./src' }, 35 | { test: /index\.html$/, loader: 'file-loader?name=[path][name].[ext]&context=./src' } 36 | ], 37 | noParse: [ 38 | /dist\/autolayout\.js$/, 39 | /dist\/autolayout\.kiwi\.js$/ 40 | ] 41 | }, 42 | resolve: { 43 | alias: { 44 | 'famous-flex': 'famous-flex/src', 45 | 'autolayout': 'autolayout/dist/autolayout.kiwi.js' 46 | } 47 | }, 48 | plugins:[ 49 | new webpack.DefinePlugin({ 50 | VERSION: JSON.stringify(require('./package.json').version), 51 | ENV: JSON.stringify(argv.env) 52 | }) 53 | ] 54 | }; 55 | 56 | function isDevServer() { 57 | return process.argv.join('').indexOf('webpack-dev-server') > -1; 58 | } 59 | 60 | module.exports = config; 61 | --------------------------------------------------------------------------------