├── .classpath ├── .gitattributes ├── .gitignore ├── .project ├── AUTHORS ├── CONTRIBUTING.md ├── ESH-INF └── config │ └── config.xml ├── LICENSE ├── MAINTAINERS ├── META-INF └── MANIFEST.MF ├── OSGI-INF └── dashboardtile.xml ├── README.md ├── about.html ├── build.properties ├── pom.xml ├── src └── main │ └── java │ └── org │ └── openhab │ └── ui │ └── flowsbuilder │ └── internal │ └── FlowsBuilderDashboardTile.java └── web ├── .csscomb.json ├── .eslintrc.yaml ├── app ├── app.js ├── designer │ ├── content.html │ ├── designer.controller.js │ ├── designer.html │ ├── menubar.html │ ├── node.html │ ├── sidebar-left.html │ ├── sidebar-right.html │ └── toolbar.html ├── error │ └── error.html ├── services │ ├── eventsource.service.js │ ├── flow.service.js │ ├── flowcompiler.service.js │ ├── items.service.js │ ├── localfile.service.js │ ├── modulestypes-ext.service.js │ ├── ruleengine.service.js │ ├── services.js │ └── storage.service.js └── shared │ ├── config-form.directive.js │ ├── config-form.tpl.html │ ├── dragdrop.directive.js │ ├── itempicker.directive.js │ ├── resize.directive.js │ ├── scripteditor.directive.js │ └── shared.js ├── assets ├── defs │ └── esh-script-scope.json ├── manifest │ ├── logo144.png │ ├── logo192.png │ ├── logo36.png │ ├── logo48.png │ ├── logo72.png │ └── logo96.png └── styles │ ├── common.scss │ ├── designer.scss │ ├── flowchart.scss │ ├── reset.scss │ └── themes │ ├── default.css │ └── themes.json ├── bower.json ├── favicon.ico ├── fonts ├── bootstrap │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── roboto │ ├── Roboto-Regular.eot │ ├── Roboto-Regular.svg │ ├── Roboto-Regular.ttf │ ├── Roboto-Regular.woff │ └── Roboto-Regular.woff2 ├── gulpfile.js ├── index.html ├── manifest.json ├── package.json ├── service-worker.js ├── tile.png └── vendor ├── angular-web-colorpicker.js ├── cm ├── addon │ ├── dialog │ │ ├── dialog.css │ │ └── dialog.js │ ├── edit │ │ ├── closebrackets.js │ │ ├── closetag.js │ │ ├── matchbrackets.js │ │ ├── matchtags.js │ │ └── xml.js │ ├── fold │ │ └── xml-fold.js │ ├── hint │ │ ├── show-hint.css │ │ └── show-hint.js │ ├── mode │ │ └── overlay.js │ └── tern │ │ ├── ecma5.json │ │ ├── tern-libs.js │ │ ├── tern.css │ │ └── tern.js ├── lib │ ├── codemirror.css │ └── codemirror.js ├── mode │ ├── javascript │ │ └── javascript.js │ └── xml │ │ └── xml.js └── theme │ └── rubyblue.css ├── styles.min.css ├── styles.scss └── vendor.js /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | target/ 3 | bin/ 4 | .antlr* 5 | .metadata/ 6 | */plugin.xml_gen 7 | node_modules 8 | bower_components 9 | .tmp 10 | .vscode 11 | .sass-cache 12 | nbproject 13 | coverage 14 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.openhab.ui.flowsbuilder 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.pde.ManifestBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.pde.SchemaBuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.pde.ds.core.builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.pde.PluginNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Yannick Schaus (@ghys) 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Flows Builder 2 | 3 | Want to hack on Flows Builder? Awesome! 4 | Here are instructions to get you started. They are similar to those of openHAB itself. 5 | 6 | ## Reporting Issues 7 | 8 | Please report [Flows Builder specific issues here](https://github.com/ghys/flowsbuilder/issues), 9 | while issues that are related to openHAB2 or Eclipse SmartHome should be reported in the 10 | [openHAB2 GitHub repository](https://github.com/openhab/openhab2/issues) or the 11 | [Eclipse SmartHome repository](https://github.com/eclipse/smarthome/issues), respectively. 12 | Do not worry, if you are not clear, which category your issue belongs to - we will 13 | redirect you, if necessary. 14 | 15 | ## Build Environment 16 | 17 | It is assumed you have ```npm```, ```bower``` and ```gulp``` available; if not, 18 | check their respective docs. 19 | 20 | To build the Javascript part of Flows Builder, navigate to the ```web/``` subfolder, then: 21 | 1. Run ```npm install``` 22 | 2. Run ```bower install``` 23 | 3. Run ```gulp``` 24 | 25 | Files in the ```vendor/``` directory should be rebuilt with the above operations. 26 | (note: if adding a new dependency, never add it directly to the project, 27 | add it as a bower and/or npm dependency and rebuild the project using the 28 | instructions above! You would have to modify the targets in ```gulpfile.js``` as well) 29 | 30 | You also need to set up an openHAB development environment. 31 | For instructions on doing so, please refer to the [IDE setup guide](https://github.com/openhab/openhab/wiki/IDE-Setup). 32 | 33 | Once it's done: 34 | - Package a new version of the bundle using Maven (```mvn clean package``` or use m2e) 35 | - Copy the resulting ```target/org.openhab.ui.flowsbuilder.{VERSION}.jar``` into your 36 | server's ```addons``` subfolder to test it. 37 | 38 | If everything went well, push your branch to GitHub, create a pull request and request 39 | a merge approval - see instructions below. 40 | 41 | 42 | ## Contribution guidelines 43 | 44 | ### Pull requests are always welcome 45 | 46 | We are always thrilled to receive pull requests, and do our best to 47 | process them as fast as possible. Not sure if that typo is worth a pull 48 | request? Do it! We will appreciate it. 49 | 50 | If your pull request is not accepted on the first try, don't be 51 | discouraged! If there's a problem with the implementation, hopefully you 52 | received feedback on what to improve. 53 | 54 | We're trying very hard to keep openHAB lean and focused. We don't want it 55 | to do everything for everybody. This means that we might decide against 56 | incorporating a new feature. However, there might be a way to implement 57 | that feature *on top of* openHAB. 58 | 59 | ### Discuss your design on the mailing list 60 | 61 | We recommend discussing your plans [in the discussion forum](https://community.openhab.org/c/apps-services/flowsbuilder) 62 | before starting to code - especially for more ambitious contributions. 63 | This gives other contributors a chance to point you in the right 64 | direction, give feedback on your design, and maybe point out if someone 65 | else is working on the same thing. 66 | 67 | ### Create issues... 68 | 69 | Any significant improvement should be documented as [a GitHub 70 | issue](https://github.com/openhab/flowsbuilder/issues?labels=enhancement&page=1&state=open) before anybody 71 | starts working on it. 72 | 73 | ### ...but check for existing issues first! 74 | 75 | Please take a moment to check that an issue doesn't already exist 76 | documenting your bug report or improvement proposal. If it does, it 77 | never hurts to add a quick "+1" or "I have this problem too". This will 78 | help prioritize the most common problems and requests. 79 | 80 | ### Conventions 81 | 82 | Fork the repo and make changes on your fork in a feature branch: 83 | 84 | - If it's a bugfix branch, name it XXX-something where XXX is the number of the 85 | issue 86 | - If it's a feature branch, create an enhancement issue to announce your 87 | intentions, and name it XXX-something where XXX is the number of the issue. 88 | 89 | Submit unit tests for your changes. openHAB has a great test framework built in; use 90 | it! Take a look at existing tests for inspiration. Run the full test suite on 91 | your branch before submitting a pull request. 92 | 93 | Update the documentation when creating or modifying features. Test 94 | your documentation changes for clarity, concision, and correctness, as 95 | well as a clean documentation build. 96 | 97 | Write clean code. Universally formatted code promotes ease of writing, reading, 98 | and maintenance. 99 | 100 | Pull requests descriptions should be as clear as possible and include a 101 | reference to all the issues that they address. 102 | 103 | Pull requests must not contain commits from other users or branches. 104 | 105 | Commit messages must start with a capitalized and short summary (max. 50 106 | chars) written in the imperative, followed by an optional, more detailed 107 | explanatory text which is separated from the summary by an empty line. 108 | 109 | Code review comments may be added to your pull request. Discuss, then make the 110 | suggested modifications and push additional commits to your feature branch. Be 111 | sure to post a comment after pushing. The new commits will show up in the pull 112 | request automatically, but the reviewers will not be notified unless you 113 | comment. 114 | 115 | Before the pull request is merged, make sure that you squash your commits into 116 | logical units of work using `git rebase -i` and `git push -f`. After every 117 | commit the test suite should be passing. Include documentation changes in the 118 | same commit so that a revert would remove all traces of the feature or fix. 119 | 120 | Commits that fix or close an issue should include a reference like `Closes #XXX` 121 | or `Fixes #XXX`, which will automatically close the issue when merged. 122 | 123 | Add your name to the AUTHORS file, but make sure the list is sorted and your 124 | name and email address match your git configuration. The AUTHORS file is 125 | regenerated occasionally from the git commit history, so a mismatch may result 126 | in your changes being overwritten. 127 | 128 | ### Merge approval 129 | 130 | openHAB maintainers use LGTM (Looks Good To Me) in comments on the code review 131 | to indicate acceptance. 132 | 133 | A change requires LGTMs from an absolute majority of the maintainers of each 134 | component affected. For example, if a change affects `docs/` and `addons/`, it 135 | needs an absolute majority from the maintainers of `docs/` AND, separately, an 136 | absolute majority of the maintainers of `addons/`. 137 | 138 | ### Sign your work 139 | 140 | The sign-off is a simple line at the end of the explanation for the 141 | patch, which certifies that you wrote it or otherwise have the right to 142 | pass it on as an open-source patch. The rules are pretty simple: if you 143 | can certify the below (from 144 | [developercertificate.org](http://developercertificate.org/)): 145 | 146 | ``` 147 | Developer Certificate of Origin 148 | Version 1.1 149 | 150 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 151 | 660 York Street, Suite 102, 152 | San Francisco, CA 94110 USA 153 | 154 | Everyone is permitted to copy and distribute verbatim copies of this 155 | license document, but changing it is not allowed. 156 | 157 | 158 | Developer's Certificate of Origin 1.1 159 | 160 | By making a contribution to this project, I certify that: 161 | 162 | (a) The contribution was created in whole or in part by me and I 163 | have the right to submit it under the open source license 164 | indicated in the file; or 165 | 166 | (b) The contribution is based upon previous work that, to the best 167 | of my knowledge, is covered under an appropriate open source 168 | license and I have the right under that license to submit that 169 | work with modifications, whether created in whole or in part 170 | by me, under the same open source license (unless I am 171 | permitted to submit under a different license), as indicated 172 | in the file; or 173 | 174 | (c) The contribution was provided directly to me by some other 175 | person who certified (a), (b) or (c) and I have not modified 176 | it. 177 | 178 | (d) I understand and agree that this project and the contribution 179 | are public and that a record of the contribution (including all 180 | personal information I submit with it, including my sign-off) is 181 | maintained indefinitely and may be redistributed consistent with 182 | this project or the open source license(s) involved. 183 | ``` 184 | 185 | then you just add a line to every git commit message: 186 | 187 | Signed-off-by: Joe Smith (github: github_handle) 188 | 189 | using your real name (sorry, no pseudonyms or anonymous contributions.) 190 | 191 | One way to automate this, is customise your get ``commit.template`` by adding 192 | a ``prepare-commit-msg`` hook to your openHAB checkout: 193 | 194 | ``` 195 | curl -L -o .git/hooks/prepare-commit-msg https://raw.github.com/openhab/openhab2/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg 196 | ``` 197 | 198 | * Note: the above script expects to find your GitHub user name in ``git config --get github.user`` 199 | 200 | #### Small patch exception 201 | 202 | There are several exceptions to the signing requirement. Currently these are: 203 | 204 | * Your patch fixes spelling or grammar errors. 205 | * Your patch is a single line change to documentation. 206 | 207 | ### How can I become a maintainer? 208 | 209 | * Step 1: learn the component inside out 210 | * Step 2: make yourself useful by contributing code, bugfixes, support etc. 211 | * Step 3: volunteer on [the discussion group](https://community.openhab.org/) or on [GitHub](https://github.com/ghys/flowsbuilder/issues?labels=question&page=1&state=open) 212 | 213 | Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available. 214 | You don't have to be a maintainer to make a difference on the project! 215 | 216 | ## Community Guidelines 217 | 218 | We want to keep the openHAB community awesome, growing and collaborative. We 219 | need your help to keep it that way. To help with this we've come up with some 220 | general guidelines for the community as a whole: 221 | 222 | * Be nice: Be courteous, respectful and polite to fellow community members: no 223 | regional, racial, gender, or other abuse will be tolerated. We like nice people 224 | way better than mean ones! 225 | 226 | * Encourage diversity and participation: Make everyone in our community 227 | feel welcome, regardless of their background and the extent of their 228 | contributions, and do everything possible to encourage participation in 229 | our community. 230 | 231 | * Keep it legal: Basically, don't get us in trouble. Share only content that 232 | you own, do not share private or sensitive information, and don't break the 233 | law. 234 | 235 | * Stay on topic: Make sure that you are posting to the correct channel 236 | and avoid off-topic discussions. Remember when you update an issue or 237 | respond to an email you are potentially sending to a large number of 238 | people. Please consider this before you update. Also remember that 239 | nobody likes spam. 240 | 241 | ## Acknowledgements 242 | 243 | HABPanel was made possible (apart from ESH and openHAB themselves, of course) 244 | thanks to these awesome JavaScript libraries, most notably: 245 | 246 | - [AngularJS](https://github.com/angular/angular.js) 247 | - [angular-clipboard](https://github.com/omichelsen/angular-clipboard) 248 | - [angular-sanitize](https://github.com/angular/bower-angular-sanitize) 249 | - [angular-local-storage](https://github.com/grevory/angular-local-storage) 250 | - [angular-prompt](https://github.com/cgross/angular-prompt) 251 | - [angular-ui-bootstrap](https://github.com/angular-ui/bootstrap) 252 | - [angular-ui-select](https://github.com/angular-ui/ui-select) 253 | - [angular-ui-codemirror](https://github.com/angular-ui/ui-codemirror) 254 | - [CodeMirror](https://github.com/codemirror/codemirror) 255 | - [sprintf.js](https://github.com/alexei/sprintf.js) 256 | - [event-source-polyfill](https://github.com/Yaffle/EventSource/) 257 | - [FileSaver](https://github.com/eligrey/FileSaver.js) 258 | - [angular-file-saver](https://github.com/alferov/angular-file-saver) 259 | - [ngFlowchart](https://github.com/ONE-LOGIC/ngFlowchart) 260 | - [my-2d-diagram-editor](https://github.com/Robinyo/my-2d-diagram-editor) 261 | 262 | Check out the ```web/bower.json``` and ```web/package.json``` files for the complete list. 263 | -------------------------------------------------------------------------------- /ESH-INF/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | script 10 | 11 | JSON object containing the registry of flows. Normally only modified with the designer, edit by hand at your own risk! 12 | 13 | 14 | 15 | 16 | The ID of the existing flow to show at startup. Set by the designer. 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' 19 | from a Contributor if it was added to the Program by such Contributor 20 | itself or anyone acting on such Contributor's behalf. Contributions do not 21 | include additions to the Program which: (i) are separate modules of 22 | software distributed in conjunction with the Program under their own 23 | license agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this 32 | Agreement. 33 | 34 | "Recipient" means anyone who receives the Program under this Agreement, 35 | including all Contributors. 36 | 37 | 2. GRANT OF RIGHTS 38 | a) Subject to the terms of this Agreement, each Contributor hereby grants 39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 40 | reproduce, prepare derivative works of, publicly display, publicly 41 | perform, distribute and sublicense the Contribution of such Contributor, 42 | if any, and such derivative works, in source code and object code form. 43 | b) Subject to the terms of this Agreement, each Contributor hereby grants 44 | Recipient a non-exclusive, worldwide, royalty-free patent license under 45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 46 | transfer the Contribution of such Contributor, if any, in source code and 47 | object code form. This patent license shall apply to the combination of 48 | the Contribution and the Program if, at the time the Contribution is 49 | added by the Contributor, such addition of the Contribution causes such 50 | combination to be covered by the Licensed Patents. The patent license 51 | shall not apply to any other combinations which include the Contribution. 52 | No hardware per se is licensed hereunder. 53 | c) Recipient understands that although each Contributor grants the licenses 54 | to its Contributions set forth herein, no assurances are provided by any 55 | Contributor that the Program does not infringe the patent or other 56 | intellectual property rights of any other entity. Each Contributor 57 | disclaims any liability to Recipient for claims brought by any other 58 | entity based on infringement of intellectual property rights or 59 | otherwise. As a condition to exercising the rights and licenses granted 60 | hereunder, each Recipient hereby assumes sole responsibility to secure 61 | any other intellectual property rights needed, if any. For example, if a 62 | third party patent license is required to allow Recipient to distribute 63 | the Program, it is Recipient's responsibility to acquire that license 64 | before distributing the Program. 65 | d) Each Contributor represents that to its knowledge it has sufficient 66 | copyright rights in its Contribution, if any, to grant the copyright 67 | license set forth in this Agreement. 68 | 69 | 3. REQUIREMENTS 70 | 71 | A Contributor may choose to distribute the Program in object code form under 72 | its own license agreement, provided that: 73 | 74 | a) it complies with the terms and conditions of this Agreement; and 75 | b) its license agreement: 76 | i) effectively disclaims on behalf of all Contributors all warranties 77 | and conditions, express and implied, including warranties or 78 | conditions of title and non-infringement, and implied warranties or 79 | conditions of merchantability and fitness for a particular purpose; 80 | ii) effectively excludes on behalf of all Contributors all liability for 81 | damages, including direct, indirect, special, incidental and 82 | consequential damages, such as lost profits; 83 | iii) states that any provisions which differ from this Agreement are 84 | offered by that Contributor alone and not by any other party; and 85 | iv) states that source code for the Program is available from such 86 | Contributor, and informs licensees how to obtain it in a reasonable 87 | manner on or through a medium customarily used for software exchange. 88 | 89 | When the Program is made available in source code form: 90 | 91 | a) it must be made available under this Agreement; and 92 | b) a copy of this Agreement must be included with each copy of the Program. 93 | Contributors may not remove or alter any copyright notices contained 94 | within the Program. 95 | 96 | Each Contributor must identify itself as the originator of its Contribution, 97 | if 98 | any, in a manner that reasonably allows subsequent Recipients to identify the 99 | originator of the Contribution. 100 | 101 | 4. COMMERCIAL DISTRIBUTION 102 | 103 | Commercial distributors of software may accept certain responsibilities with 104 | respect to end users, business partners and the like. While this license is 105 | intended to facilitate the commercial use of the Program, the Contributor who 106 | includes the Program in a commercial product offering should do so in a manner 107 | which does not create potential liability for other Contributors. Therefore, 108 | if a Contributor includes the Program in a commercial product offering, such 109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 110 | every other Contributor ("Indemnified Contributor") against any losses, 111 | damages and costs (collectively "Losses") arising from claims, lawsuits and 112 | other legal actions brought by a third party against the Indemnified 113 | Contributor to the extent caused by the acts or omissions of such Commercial 114 | Contributor in connection with its distribution of the Program in a commercial 115 | product offering. The obligations in this section do not apply to any claims 116 | or Losses relating to any actual or alleged intellectual property 117 | infringement. In order to qualify, an Indemnified Contributor must: 118 | a) promptly notify the Commercial Contributor in writing of such claim, and 119 | b) allow the Commercial Contributor to control, and cooperate with the 120 | Commercial Contributor in, the defense and any related settlement 121 | negotiations. The Indemnified Contributor may participate in any such claim at 122 | its own expense. 123 | 124 | For example, a Contributor might include the Program in a commercial product 125 | offering, Product X. That Contributor is then a Commercial Contributor. If 126 | that Commercial Contributor then makes performance claims, or offers 127 | warranties related to Product X, those performance claims and warranties are 128 | such Commercial Contributor's responsibility alone. Under this section, the 129 | Commercial Contributor would have to defend claims against the other 130 | Contributors related to those performance claims and warranties, and if a 131 | court requires any other Contributor to pay any damages as a result, the 132 | Commercial Contributor must pay those damages. 133 | 134 | 5. NO WARRANTY 135 | 136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 140 | Recipient is solely responsible for determining the appropriateness of using 141 | and distributing the Program and assumes all risks associated with its 142 | exercise of rights under this Agreement , including but not limited to the 143 | risks and costs of program errors, compliance with applicable laws, damage to 144 | or loss of data, programs or equipment, and unavailability or interruption of 145 | operations. 146 | 147 | 6. DISCLAIMER OF LIABILITY 148 | 149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 156 | OF SUCH DAMAGES. 157 | 158 | 7. GENERAL 159 | 160 | If any provision of this Agreement is invalid or unenforceable under 161 | applicable law, it shall not affect the validity or enforceability of the 162 | remainder of the terms of this Agreement, and without further action by the 163 | parties hereto, such provision shall be reformed to the minimum extent 164 | necessary to make such provision valid and enforceable. 165 | 166 | If Recipient institutes patent litigation against any entity (including a 167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 168 | (excluding combinations of the Program with other software or hardware) 169 | infringes such Recipient's patent(s), then such Recipient's rights granted 170 | under Section 2(b) shall terminate as of the date such litigation is filed. 171 | 172 | All Recipient's rights under this Agreement shall terminate if it fails to 173 | comply with any of the material terms or conditions of this Agreement and does 174 | not cure such failure in a reasonable period of time after becoming aware of 175 | such noncompliance. If all Recipient's rights under this Agreement terminate, 176 | Recipient agrees to cease use and distribution of the Program as soon as 177 | reasonably practicable. However, Recipient's obligations under this Agreement 178 | and any licenses granted by Recipient relating to the Program shall continue 179 | and survive. 180 | 181 | Everyone is permitted to copy and distribute copies of this Agreement, but in 182 | order to avoid inconsistency the Agreement is copyrighted and may only be 183 | modified in the following manner. The Agreement Steward reserves the right to 184 | publish new versions (including revisions) of this Agreement from time to 185 | time. No one other than the Agreement Steward has the right to modify this 186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 187 | Eclipse Foundation may assign the responsibility to serve as the Agreement 188 | Steward to a suitable separate entity. Each new version of the Agreement will 189 | be given a distinguishing version number. The Program (including 190 | Contributions) may always be distributed subject to the version of the 191 | Agreement under which it was received. In addition, after a new version of the 192 | Agreement is published, Contributor may elect to distribute the Program 193 | (including its Contributions) under the new version. Except as expressly 194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 195 | licenses to the intellectual property of any Contributor under this Agreement, 196 | whether expressly, by implication, estoppel or otherwise. All rights in the 197 | Program not expressly granted under this Agreement are reserved. 198 | 199 | This Agreement is governed by the laws of the State of New York and the 200 | intellectual property laws of the United States of America. No party to this 201 | Agreement will bring a legal action under this Agreement more than one year 202 | after the cause of action arose. Each party waives its rights to a jury trial in 203 | any resulting litigation. -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Yannick Schaus (@ghys) 2 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Bundle-ManifestVersion: 2 3 | Bundle-Name: Flows Builder 4 | Bundle-SymbolicName: org.openhab.ui.flowsbuilder 5 | Bundle-Version: 2.2.0.qualifier 6 | Bundle-RequiredExecutionEnvironment: JavaSE-1.8 7 | Service-Component: OSGI-INF/*.xml 8 | Import-Package: javax.servlet, 9 | javax.servlet.http, 10 | org.openhab.ui.dashboard, 11 | org.apache.commons.io, 12 | org.osgi.framework, 13 | org.osgi.service.component, 14 | org.osgi.service.http, 15 | org.slf4j 16 | Export-Package: org.openhab.ui.flowsbuilder 17 | -------------------------------------------------------------------------------- /OSGI-INF/dashboardtile.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flows Builder 2 | 3 | An experimental GUI to build flows compiled into rules for the automation engine embedded in Eclipse SmartHome / openHAB 2.0. 4 | 5 | ![Screenshot](http://i.imgur.com/6tam9Hi.png) 6 | 7 | **IMPORTANT: This app is experimental, and not part of Eclipse SmartHome or the official openHAB 2 distribution. Don't use the ESH or openHAB issue trackers for problems related to this app!** 8 | 9 | ## Getting started 10 | 11 | * Drop the compiled .jar in your distribution's `addons` directory; 12 | * Enable the experimental rule engine in Paper UI, _Addons > Misc_ 13 | * Launch Flows Builder from the dashboard 14 | * Design your flow: drag and drop nodes from the toolbox on the left - add a trigger first, then conditions and actions; unlike simple rules, conditions might follow actions, and have an "else "path. Click on nodes and configure them as required in the right pane. 15 | * Save it on the server with the save button (Ctrl+S or Meta+S work too) - give it a name if asked. 16 | * Try to "build" your flow to check its validity by clicking the "Check" button or with the _Flow > Build only_ menu, if there is any error it will be reported in the build output pane. If successful, one or more rules will be built, **but not deployed yet to the rule engine** - this is your chance to review what's being done. You can click the small "View" link in the build output pane after a successful build to get the JSON output of the rules resulting from the compilation of your flow. 17 | * Click the "Publish" button or the _Flow > Build and publish_ menu to deploy the rules built from the flow - if there are previous rules existing, they will be removed first. 18 | * You can also unpublish the rules with the "unpublish" button or the _Flow > Unpublish_ menu 19 | 20 | ## Not supported 21 | 22 | * Merged paths (and _a fortiori_ loops); this includes multi-trigger rules even though they're allowed by the engine 23 | * Else path for conditions not in the core bundle - this implies knowing how to "negate" the conditions, which is hardcoded in the client for now (dirty hack to address) 24 | * Undocumented features of the engine like rule templates, rule parameters etc. 25 | 26 | ## Missing features 27 | 28 | - Check for dirtyness to avoid data loss if the flow is not saved, for instance when switching flows or closing the window 29 | - Import from file or from an existing rule 30 | - Snap to grid 31 | - ~~Duplicate, copy-paste nodes~~ 32 | - Undo/redo engine 33 | - ... 34 | -------------------------------------------------------------------------------- /about.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | About 7 | 8 | 9 |

About This Content

10 | 11 |

<August 20, 2017>

12 |

License

13 | 14 |

The openHAB community makes available all content in this plug-in ("Content"). Unless otherwise 15 | indicated below, the Content is provided to you under the terms and conditions of the 16 | Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available 17 | at http://www.eclipse.org/legal/epl-v10.html. 18 | For purposes of the EPL, "Program" will mean the Content.

19 | 20 |

If you did not receive this Content directly from the openHAB community, the Content is 21 | being redistributed by another party ("Redistributor") and different terms and conditions may 22 | apply to your use of any object code in the Content. Check the Redistributor's license that was 23 | provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise 24 | indicated below, the terms and conditions of the EPL still apply to any source code in the Content 25 | and such source code may be obtained at openhab.org.

26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /build.properties: -------------------------------------------------------------------------------- 1 | bin.includes = META-INF/,\ 2 | .,\ 3 | about.html,\ 4 | ESH-INF/,\ 5 | OSGI-INF/,\ 6 | web/ 7 | output = target/classes/ 8 | source.. = src/main/java/ 9 | bin.excludes = web/bower.json,\ 10 | web/bower_components/,\ 11 | web/node_modules/,\ 12 | web/gulpfile.js,\ 13 | web/.eslintrc.yaml,\ 14 | web/package.json 15 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | org.openhab 6 | pom-tycho 7 | 2.2.0-SNAPSHOT 8 | 9 | 10 | 11 | 4.0.0 12 | 13 | org.openhab.ui 14 | org.openhab.ui.flowsbuilder 15 | 2.2.0-SNAPSHOT 16 | 17 | Flows Builder user interface 18 | 19 | eclipse-plugin 20 | 21 | 22 | ${project.version} 23 | 24 | 25 | 26 | 27 | org.openhab.core 28 | org.openhab.core 29 | ${ohc.version} 30 | 31 | 32 | org.openhab.core 33 | org.openhab.ui.dashboard 34 | ${ohc.version} 35 | 36 | 37 | 38 | 39 | 40 | jcenter 41 | JCenter Repository 42 | https://jcenter.bintray.com/ 43 | 44 | true 45 | never 46 | 47 | 48 | false 49 | 50 | 51 | 52 | artifactory 53 | JFrog Artifactory Repository 54 | https://openhab.jfrog.io/openhab/libs-release 55 | 56 | true 57 | never 58 | 59 | 60 | false 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | jcenter 70 | JCenter Repository 71 | https://jcenter.bintray.com/ 72 | 73 | true 74 | never 75 | 76 | 77 | false 78 | 79 | 80 | 81 | 82 | openhab-artifactory-release 83 | JFrog Artifactory Repository 84 | https://openhab.jfrog.io/openhab/libs-release 85 | 86 | true 87 | never 88 | 89 | 90 | false 91 | 92 | 93 | 94 | 95 | 96 | openhab-artifactory-snapshot 97 | JFrog Artifactory Repository 98 | https://openhab.jfrog.io/openhab/libs-snapshot 99 | 100 | false 101 | 102 | 103 | true 104 | always 105 | 106 | 107 | 108 | 109 | 110 | p2-smarthome 111 | https://openhab.jfrog.io/openhab/eclipse-smarthome-stable 112 | p2 113 | 114 | 115 | 116 | 117 | p2-openhab-deps-repo 118 | https://dl.bintray.com/openhab/p2/openhab-deps-repo/${ohdr.version} 119 | p2 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/main/java/org/openhab/ui/flowsbuilder/internal/FlowsBuilderDashboardTile.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-2016 by the respective copyright holders. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | */ 9 | package org.openhab.ui.flowsbuilder.internal; 10 | 11 | import org.openhab.ui.dashboard.DashboardTile; 12 | import org.osgi.service.http.HttpService; 13 | import org.osgi.service.http.NamespaceException; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * The dashboard tile and resource registering for the Flows Builder app 19 | * 20 | * @author Yannick Schaus 21 | * 22 | */ 23 | public class FlowsBuilderDashboardTile implements DashboardTile { 24 | 25 | @Override 26 | public String getName() { 27 | return "Flows Builder"; 28 | } 29 | 30 | @Override 31 | public String getUrl() { 32 | return "../flowsbuilder/index.html"; 33 | } 34 | 35 | @Override 36 | public String getOverlay() { 37 | return null; 38 | } 39 | 40 | @Override 41 | public String getImageUrl() { 42 | return "../flowsbuilder/tile.png"; 43 | } 44 | 45 | public static final String FLOWSBUILER_ALIAS = "/flowsbuilder"; 46 | 47 | private static final Logger logger = LoggerFactory.getLogger(FlowsBuilderDashboardTile.class); 48 | 49 | protected HttpService httpService; 50 | 51 | protected void activate() { 52 | try { 53 | httpService.registerResources(FLOWSBUILER_ALIAS, "web", null); 54 | logger.info("Started Flows Builder at " + FLOWSBUILER_ALIAS); 55 | } catch (NamespaceException e) { 56 | logger.error("Error during Flows Builder startup: {}", e.getMessage()); 57 | } 58 | } 59 | 60 | protected void deactivate() { 61 | httpService.unregister(FLOWSBUILER_ALIAS); 62 | logger.info("Stopped Flows Builder"); 63 | } 64 | 65 | protected void setHttpService(HttpService httpService) { 66 | this.httpService = httpService; 67 | } 68 | 69 | protected void unsetHttpService(HttpService httpService) { 70 | this.httpService = null; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /web/.csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "remove-empty-rulesets": true, 3 | "always-semicolon": true, 4 | "color-case": "lower", 5 | "block-indent": " ", 6 | "color-shorthand": true, 7 | "element-case": "lower", 8 | "eof-newline": true, 9 | "leading-zero": false, 10 | "quotes": "double", 11 | "sort-order-fallback": "abc", 12 | "space-before-colon": "", 13 | "space-after-colon": " ", 14 | "space-before-combinator": " ", 15 | "space-after-combinator": " ", 16 | "space-between-declarations": "\n", 17 | "space-before-opening-brace": " ", 18 | "space-after-opening-brace": "\n", 19 | "space-after-selector-delimiter": "\n", 20 | "space-before-selector-delimiter": "", 21 | "space-before-closing-brace": "\n", 22 | "strip-spaces": true, 23 | "tab-size": true, 24 | "unitless-zero": true, 25 | "vendor-prefix-align": true 26 | } -------------------------------------------------------------------------------- /web/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: recommended 2 | -------------------------------------------------------------------------------- /web/app/app.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | 'ui.bootstrap', 6 | 'ui.select', 7 | 'ngRoute', 8 | 'ngSanitize', 9 | 'app.services', 10 | 'app.shared', 11 | 'cgPrompt', 12 | 'LocalStorageModule', 13 | 'oc.lazyLoad', 14 | 'angular-clipboard', 15 | 'ngFileSaver', 16 | 'flowchart' 17 | ]) 18 | .config(['$routeProvider', '$ocLazyLoadProvider', 'localStorageServiceProvider', 'NodeTemplatePathProvider', function($routeProvider, $ocLazyLoadProvider, localStorageServiceProvider, NodeTemplatePathProvider) { 19 | localStorageServiceProvider.setStorageType('localStorage'); 20 | 21 | NodeTemplatePathProvider.setTemplatePath("app/designer/node.html"); 22 | 23 | $routeProvider 24 | .when('/', { 25 | templateUrl: 'app/designer/designer.html', 26 | controller: 'DesignerCtrl', 27 | controllerAs: 'vm', 28 | resolve: { 29 | module_types: ['RuleEngineService', function (RuleEngineService) { 30 | return RuleEngineService.getModuleTypes(); 31 | }], 32 | flowinfo: ['StorageService', 'FlowService', function (StorageService, FlowService) { 33 | return StorageService.getServiceConfiguration().then(function () { 34 | return { 35 | currentFlowId: FlowService.getCurrentFlowId(), 36 | currentFlow: FlowService.getCurrentFlow(), 37 | flowIds: FlowService.getFlowIds() 38 | } 39 | }) 40 | }], 41 | } 42 | }) 43 | .when('/error', { 44 | templateUrl: 'app/error/error.html' 45 | }) 46 | .otherwise({ 47 | redirectTo: '/' 48 | }); 49 | 50 | $ocLazyLoadProvider.config({ 51 | serie: true, 52 | modules: [{ 53 | name: 'codemirror', 54 | files: [ 55 | 'vendor/cm/lib/codemirror.css', 56 | 'vendor/cm/lib/codemirror.js', 57 | 'vendor/cm/theme/rubyblue.css', 58 | 'vendor/cm/addon/edit/matchbrackets.js', 59 | 'vendor/cm/addon/edit/closebrackets.js', 60 | 'vendor/cm/mode/javascript/javascript.js', 61 | 'vendor/cm/addon/hint/show-hint.js', 62 | 'vendor/cm/addon/hint/show-hint.css', 63 | 'vendor/cm/addon/dialog/dialog.js', 64 | 'vendor/cm/addon/dialog/dialog.css', 65 | 'vendor/cm/addon/tern/tern.js', 66 | 'vendor/cm/addon/tern/tern.css', 67 | 'vendor/cm/addon/tern/tern-libs.js' 68 | ], 69 | serie: true 70 | }] 71 | }); 72 | }]) 73 | .run(['$rootScope', '$location', function($rootScope, $location) { 74 | 75 | $rootScope.$on('$routeChangeError', function (evt, current, previous, rejection) { 76 | $location.path('/error'); 77 | }); 78 | }]) 79 | })(); -------------------------------------------------------------------------------- /web/app/designer/content.html: -------------------------------------------------------------------------------- 1 |
2 | 4 |
5 | -------------------------------------------------------------------------------- /web/app/designer/designer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 | 12 | 13 |
14 |
15 |
16 | 17 | 20 | 21 |
22 | 23 |
-------------------------------------------------------------------------------- /web/app/designer/menubar.html: -------------------------------------------------------------------------------- 1 | 181 | 182 | -------------------------------------------------------------------------------- /web/app/designer/node.html: -------------------------------------------------------------------------------- 1 |
5 |
6 |

{{ node.name || callbacks.getDefaultNodeLabel(node) }}

7 | 8 |
9 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 | × 23 |
24 |
25 | -------------------------------------------------------------------------------- /web/app/designer/sidebar-left.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Triggers 11 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

When {{module.label}}

20 |
21 |
22 |
23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | Conditions 33 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |

If {{module.label}}

42 |
43 |
44 |
45 |
46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Actions 56 | 58 | 59 | 60 | 61 |
62 |
63 |
64 |

{{module.label}}

65 |
66 |
67 |
68 |
69 | 70 |
71 | 72 |
73 | 74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /web/app/designer/sidebar-right.html: -------------------------------------------------------------------------------- 1 | 2 | 46 | 47 | 187 | -------------------------------------------------------------------------------- /web/app/designer/toolbar.html: -------------------------------------------------------------------------------- 1 | 100 | 101 | 102 | 123 | -------------------------------------------------------------------------------- /web/app/error/error.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Error 5 |
6 |
7 |

Check that the experimental rule engine is enabled.

8 | 9 |

Try again 10 |

11 |
12 |
13 | -------------------------------------------------------------------------------- /web/app/services/eventsource.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('EventSourceService', EventSourceService); 7 | 8 | EventSourceService.$inject = ['$rootScope', '$filter']; 9 | function EventSourceService($rootScope, $filter) { 10 | this.registerEventSource = registerEventSource; 11 | this.closeEventSource = closeEventSource; 12 | 13 | var liveUpdatesEnabled = false; 14 | var eventSource = null; 15 | 16 | //////////////// 17 | 18 | function registerEventSource(callback) { 19 | if (typeof(EventSource) !== "undefined") { 20 | eventSource = new EventSource('/rest/events'); 21 | liveUpdatesEnabled = true; 22 | 23 | eventSource.onmessage = function (event) { 24 | try { 25 | var evtdata = JSON.parse(event.data); 26 | var topicparts = evtdata.topic.split('/'); 27 | 28 | var payload = JSON.parse(evtdata.payload); 29 | callback(evtdata, topicparts, payload); 30 | 31 | if (evtdata.type === 'ItemStateEvent' || evtdata.type === 'ItemStateChangedEvent' || evtdata.type === 'GroupItemStateChangedEvent') { 32 | var newstate = payload.value; 33 | var item = $filter('filter')($rootScope.items, {name: topicparts[2]}, true)[0]; 34 | if (item && item.state !== payload.value) { 35 | $rootScope.$apply(function () { 36 | console.log("Updating " + item.name + " state from " + item.state + " to " + payload.value); 37 | item.state = payload.value; 38 | 39 | // no transformation on state 40 | $rootScope.$emit('openhab-update', item); 41 | }); 42 | } 43 | } 44 | } catch (e) { 45 | console.warn('SSE event issue: ' + e.message); 46 | } 47 | } 48 | eventSource.onerror = function (event) { 49 | console.error('SSE error, closing EventSource'); 50 | liveUpdatesEnabled = false; 51 | this.close(); 52 | $timeout(loadItems, 5000); 53 | } 54 | } 55 | } 56 | 57 | function closeEventSource() { 58 | if (eventSource) { 59 | eventSource.close(); 60 | } 61 | } 62 | 63 | } 64 | })(); -------------------------------------------------------------------------------- /web/app/services/flow.service.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('FlowService', FlowService); 7 | 8 | FlowService.$inject = ['$rootScope', 'StorageService', 'OH2ServiceConfiguration', '$q']; 9 | function FlowService($rootScope, StorageService, OH2ServiceConfiguration, $q) { 10 | this.getNewFlow = getNewFlow; 11 | this.getCurrentFlow = getCurrentFlow; 12 | this.getCurrentFlowId = getCurrentFlowId; 13 | this.setCurrentFlowId = setCurrentFlowId; 14 | this.getFlowIds = getFlowIds; 15 | this.saveFlowAs = saveFlowAs; 16 | this.deleteCurrentFlow = deleteCurrentFlow; 17 | 18 | //////////////// 19 | 20 | function getNewFlow() { 21 | return { 22 | next_node_id: 1, 23 | next_connector_id: 1, 24 | nodes: [], 25 | edges: [], 26 | }; 27 | } 28 | 29 | function getCurrentFlow() { 30 | // we assume the loading of the OH2ServiceConfiguration has been done before (router's resolve...) 31 | if (!$rootScope.currentFlow) { 32 | $rootScope.currentFlow = (OH2ServiceConfiguration.flowsRegistry && OH2ServiceConfiguration.flowsRegistry[OH2ServiceConfiguration.currentFlow]) 33 | ? angular.copy(OH2ServiceConfiguration.flowsRegistry[OH2ServiceConfiguration.currentFlow]) 34 | : getNewFlow(); 35 | } 36 | return $rootScope.currentFlow; 37 | } 38 | 39 | function getCurrentFlowId() { 40 | // we assume the loading of the OH2ServiceConfiguration has been done before (router's resolve...) 41 | return OH2ServiceConfiguration.currentFlow || 'untitled'; 42 | } 43 | 44 | function setCurrentFlowId(id) { 45 | OH2ServiceConfiguration.currentFlow = id; 46 | $rootScope.currentFlow = null; 47 | } 48 | 49 | function getFlowIds() { 50 | return (OH2ServiceConfiguration.flowsRegistry) ? Object.keys(OH2ServiceConfiguration.flowsRegistry) : ['untitled']; 51 | } 52 | 53 | function saveFlowAs(id, flow) { 54 | var deferred = $q.defer(); 55 | 56 | // TODO: poor man's optimistic concurrency 57 | flow.updatedTime = new Date().toISOString(); 58 | if (!OH2ServiceConfiguration.flowsRegistry) { 59 | console.info('Initializing registry'); 60 | OH2ServiceConfiguration.flowsRegistry = {}; 61 | } 62 | 63 | OH2ServiceConfiguration.flowsRegistry[id] = flow; 64 | setCurrentFlowId(id); 65 | getCurrentFlow(); 66 | StorageService.saveServiceConfiguration().then(function () { 67 | deferred.resolve(); 68 | }); 69 | 70 | return deferred.promise; 71 | } 72 | 73 | function deleteCurrentFlow() { 74 | if (!OH2ServiceConfiguration.flowsRegistry) return; 75 | var deferred = $q.defer(); 76 | 77 | delete OH2ServiceConfiguration.flowsRegistry[OH2ServiceConfiguration.currentFlow]; 78 | if (getFlowIds().length > 0) { 79 | setCurrentFlowId(getFlowIds()[0]); 80 | } else { 81 | delete OH2ServiceConfiguration.currentFlow; 82 | setCurrentFlowId(null); 83 | } 84 | 85 | getCurrentFlow(); 86 | StorageService.saveServiceConfiguration().then(function () { 87 | deferred.resolve(); 88 | }); 89 | } 90 | } 91 | })(); -------------------------------------------------------------------------------- /web/app/services/items.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('ItemsService', ItemsService); 7 | 8 | ItemsService.$inject = ['$rootScope', '$http', '$q', '$timeout', '$interval', '$filter', '$location']; 9 | function ItemsService($rootScope, $http, $q, $timeout, $interval, $filter, $location) { 10 | this.getItem = getItem; 11 | this.getItems = getItems; 12 | this.getLocale = getLocale; 13 | this.onUpdate = onUpdate; 14 | this.sendCmd = sendCmd; 15 | this.reloadItems = reloadItems; 16 | 17 | var liveUpdatesEnabled = false, prevAudioUrl = '', locale = null, eventSource = null; 18 | 19 | //////////////// 20 | 21 | function onUpdate(scope, name, callback) { 22 | var handler = $rootScope.$on('openhab-update', callback); 23 | scope.$on('$destroy', handler); 24 | //watchItem(name); 25 | //longPollUpdates(name); 26 | } 27 | 28 | function loadItems() { 29 | $http.get('/rest/items') 30 | .then(function (data) { 31 | if (angular.isArray(data.data)) { 32 | console.log("Loaded " + data.data.length + " openHAB items"); 33 | $rootScope.reconnecting = false; 34 | $rootScope.items = data.data; 35 | //if (!liveUpdatesEnabled) registerEventSource(); 36 | } else { 37 | console.warn("Items not found? Retrying in 5 seconds"); 38 | $rootScope.reconnecting = true; 39 | $rootScope.items = []; 40 | $timeout(loadItems, 5000); 41 | } 42 | $rootScope.$emit('openhab-update'); 43 | }, 44 | function (err) { 45 | console.warn("Error loading openHAB items... retrying in 5 seconds"); 46 | $rootScope.reconnecting = true; 47 | $timeout(loadItems, 5000); 48 | }); 49 | } 50 | 51 | function getItem(name) { 52 | var item = $filter('filter')($rootScope.items, {name: name}, true); 53 | return (item) ? item[0] : null; 54 | } 55 | 56 | function getItems() { 57 | return $rootScope.items; 58 | } 59 | 60 | /** 61 | * Sends command to openHAB 62 | * @param {string} item Item's id 63 | * @param {string} cmd Command 64 | */ 65 | function sendCmd(item, cmd) { 66 | $http({ 67 | method : 'POST', 68 | url : '/rest/items/' + item, 69 | data : cmd, 70 | headers: { 'Content-Type': 'text/plain' } 71 | }).then(function (data) { 72 | console.log('Command sent: ' + item + '=' + cmd); 73 | 74 | // should be handled by server push messages but their delivery is erratic 75 | // so perform a full refresh every time a command is sent 76 | //loadItems(); 77 | }); 78 | } 79 | 80 | /** 81 | * Returns a promise with the configured locale 82 | */ 83 | function getLocale() { 84 | var deferred = $q.defer(); 85 | 86 | if (locale) { 87 | deferred.resolve(locale); 88 | } else { 89 | $http.get('/rest/services/org.eclipse.smarthome.core.localeprovider/config') 90 | .then(function (response) { 91 | locale = response.data.language + '-' + response.data.region; 92 | deferred.resolve(locale); 93 | }, function(error) { 94 | console.warn('Couldn\'t retrieve locale settings. Setting default to "en-US"'); 95 | locale = 'en-US'; 96 | deferred.resolve(locale); 97 | }); 98 | } 99 | 100 | return deferred.promise; 101 | } 102 | 103 | function reloadItems() { 104 | loadItems(); 105 | } 106 | 107 | } 108 | })(); 109 | -------------------------------------------------------------------------------- /web/app/services/localfile.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('LocalFileReader', LocalFileReader) 7 | .directive('localFileSelect', LocalFileSelect); 8 | 9 | LocalFileReader.$inject = ['$q']; 10 | function LocalFileReader($q) { 11 | this.readFile = readFile; 12 | 13 | function onLoad(reader, deferred, scope) { 14 | return function () { 15 | scope.$apply(function () { 16 | deferred.resolve(reader.result); 17 | }) 18 | } 19 | } 20 | 21 | function onError(reader, deferred, scope) { 22 | return function () { 23 | scope.$apply(function () { 24 | deferred.resolve(reader.result); 25 | }) 26 | } 27 | } 28 | 29 | function readFile(file, scope) { 30 | var reader = new FileReader(); 31 | var deferred = $q.defer(); 32 | reader.onload = onLoad(reader, deferred, scope); 33 | reader.onerror = onLoad(reader, deferred, scope); 34 | reader.readAsText(file); 35 | 36 | return deferred.promise; 37 | } 38 | } 39 | 40 | function LocalFileSelect() { 41 | var directive = { 42 | link: link, 43 | restrict: 'A', 44 | scope: { 45 | localFileSelect: '=', 46 | onFileSelected: '=', 47 | } 48 | }; 49 | return directive; 50 | 51 | function link(scope, element, attrs) { 52 | element.bind("change", function (e) { 53 | scope.localFileSelect = (e.srcElement || e.target).files[0]; 54 | scope.onFileSelected(scope.localFileSelect); 55 | }) 56 | } 57 | } 58 | })(); 59 | -------------------------------------------------------------------------------- /web/app/services/modulestypes-ext.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('ModulesTypesExtensions', ModulesTypesExtensionsService) 7 | .value('ModuleTypeExtensionHooks', { 8 | 9 | // triggers 10 | 'timer.TimeOfDayTrigger': { 11 | defaultLabel: function (moduletype, config) { 12 | if (!config.time) return; 13 | return 'When the time is ' + config.time; 14 | } 15 | }, 16 | 'core.ItemCommandTrigger': { 17 | defaultLabel: function (moduletype, config) { 18 | if (!config.itemName || !config.command) return; 19 | return 'When ' + config.itemName + ' received command ' + config.command; 20 | } 21 | }, 22 | 'core.ItemStateUpdateTrigger': { 23 | defaultLabel: function (moduletype, config) { 24 | if (!config.itemName) return; 25 | return 'When ' + config.itemName + ' was updated' + 26 | ((config.state) ? ' to ' + config.state : ''); 27 | } 28 | }, 29 | 'core.ItemStateChangeTrigger': { 30 | defaultLabel: function (moduletype, config) { 31 | if (!config.itemName) return; 32 | return 'When ' + config.itemName + ' changed' + 33 | ((config.previousState) ? ' from ' + config.previousState : '') + 34 | ((config.state) ? ' to ' + config.state : ''); 35 | } 36 | }, 37 | 'core.ChannelEventTrigger': { 38 | defaultLabel: function (moduletype, config) { 39 | if (!config.channelUID) return; 40 | return 'When channel ' + config.channelUID + ' was triggered'; 41 | } 42 | }, 43 | 44 | // conditions 45 | 'script.ScriptCondition': { 46 | negate: function (moduletype, config) { 47 | if (!config.script) return; 48 | config.script = '!(' + config.script + ')'; 49 | } 50 | }, 51 | 'timer.DayOfWeekCondition': { 52 | defaultLabel: function (moduletype, config) { 53 | if (!config.days || !config.days.length) return; 54 | return 'If the day is ' + config.days.join(','); 55 | }, 56 | negate: function (moduletype, config) { 57 | if (!config.days) return; 58 | var invert = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'].filter(function (x) { 59 | return config.days.indexOf(x) < 0; 60 | }); 61 | config.days = invert; 62 | } 63 | }, 64 | 'core.ItemStateCondition': { 65 | defaultLabel: function (moduletype, config) { 66 | if (!config.itemName || !config.operator || !config.state) return; 67 | return 'If ' + config.itemName + ' ' + config.operator + ' ' + config.state; 68 | }, 69 | negate: function (moduletype, config) { 70 | if (!config.itemName || !config.state) return; 71 | switch (config.operator) { 72 | case '=': config.operator = '!='; break; 73 | case '!=': config.operator = '='; break; 74 | case '<': config.operator = '>='; break; 75 | case '>': config.operator = '<='; break; 76 | case '<=': config.operator = '>'; break; 77 | case '>=': config.operator = '<'; break; 78 | default: throw 'during negation of core.ItemStateCondition: invalid operator'; 79 | } 80 | } 81 | }, 82 | 83 | // actions 84 | 'core.ItemCommandAction': { 85 | defaultLabel: function (moduletype, config) { 86 | if (!config.itemName || !config.command) return; 87 | return 'Send command ' + config.command + ' to ' + config.itemName; 88 | } 89 | }, 90 | 'media.SayAction': { 91 | defaultLabel: function (moduletype, config) { 92 | if (!config.text) return; 93 | return 'Say "' + config.text + '"'; 94 | } 95 | }, 96 | 'media.PlayAction': { 97 | defaultLabel: function (moduletype, config) { 98 | if (!config.sound) return; 99 | return 'Play ' + config.sound; 100 | } 101 | }, 102 | 103 | 104 | }); 105 | 106 | ModulesTypesExtensionsService.$inject = ['ModuleTypeExtensionHooks', 'RuleEngineService', '$filter']; 107 | function ModulesTypesExtensionsService(ModuleTypeExtensionHooks, RuleEngineService, $filter) { 108 | this.getDefaultLabel = getDefaultLabel; 109 | this.negateCondition = negateCondition; 110 | 111 | var module_types = null; 112 | 113 | RuleEngineService.getModuleTypes().then (function (ret) { 114 | module_types = ret; 115 | }); 116 | 117 | //////////////// 118 | 119 | function negateCondition(moduleTypeUid, config) { 120 | var moduleTypeHooks = ModuleTypeExtensionHooks[moduleTypeUid]; 121 | if (!moduleTypeHooks || !moduleTypeHooks.negate || !config) return null; 122 | var negatedConfig = angular.copy(config); 123 | moduleTypeHooks.negate(moduleTypeUid, negatedConfig); 124 | return negatedConfig; 125 | } 126 | 127 | function getDefaultLabel(moduleTypeUid, category, config) { 128 | var label = ""; 129 | var moduleTypeHooks = ModuleTypeExtensionHooks[moduleTypeUid]; 130 | if (moduleTypeHooks && moduleTypeHooks.defaultLabel && config) { 131 | label = moduleTypeHooks.defaultLabel(moduleTypeUid, config); 132 | } 133 | if (!label) { 134 | var module_type = $filter('filter')(module_types.all, { uid: moduleTypeUid })[0]; 135 | label = ((category === 'trigger') ? 'When ' : (category === 'condition') ? 'If ' : '') + module_type.label; 136 | } 137 | return label; 138 | } 139 | } 140 | })(); -------------------------------------------------------------------------------- /web/app/services/ruleengine.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .service('RuleEngineService', RuleEngineService); 7 | 8 | RuleEngineService.$inject = ['$http', '$q', '$filter']; 9 | function RuleEngineService($http, $q, $filter) { 10 | this.getModuleTypes = getModuleTypes; 11 | this.getRules = getRules; 12 | this.getRulesWithPrefix = getRulesWithPrefix; 13 | this.unpublishFlow = unpublishFlow; 14 | this.publishFlow = publishFlow; 15 | 16 | //////////////// 17 | 18 | var moduleTypes = null; 19 | 20 | function getModuleTypes() { 21 | var deferred = $q.defer(); 22 | if (moduleTypes) { 23 | deferred.resolve(moduleTypes); 24 | } else { 25 | var all = $http.get('/rest/module-types'); 26 | var triggers = $http.get('/rest/module-types?type=trigger'); 27 | var conditions = $http.get('/rest/module-types?type=condition'); 28 | var actions = $http.get('/rest/module-types?type=action'); 29 | 30 | $q.all([all, triggers, conditions, actions]).then(function (resp) { 31 | if (resp) { 32 | moduleTypes = { 33 | all: resp[0].data, 34 | triggers: resp[1].data, 35 | conditions: resp[2].data, 36 | actions: resp[3].data 37 | }; 38 | deferred.resolve(moduleTypes); 39 | } 40 | }, function (err) { 41 | deferred.reject(err); 42 | }); 43 | } 44 | 45 | return deferred.promise; 46 | } 47 | 48 | function getRules() { 49 | return $http.get('/rest/rules'); 50 | } 51 | 52 | function getRulesWithPrefix(prefix, tags) { 53 | return $http.get('/rest/rules', { params: { prefix: prefix, tags: tags }}); 54 | } 55 | 56 | function unpublishFlow(prefix) { 57 | return getRulesWithPrefix(prefix, prefix).then(function (resp) { 58 | if (resp.data) { 59 | // var matchingRules = $filter('filter')(resp.data, function (r) { 60 | // return r.uid.indexOf(prefix) === 0; 61 | // }); 62 | var deleteRequests = resp.data.map(function (rule) { 63 | return $http.delete('/rest/rules/' + rule.uid); 64 | }); 65 | 66 | return $q.all(deleteRequests); 67 | } 68 | }) 69 | } 70 | 71 | function addRules(rules) { 72 | var putRequests = rules.map(function (rule) { 73 | return $http({ 74 | method: 'POST', 75 | url: '/rest/rules', 76 | data: rule, 77 | headers: { 'Content-Type': 'application/json' } 78 | }); 79 | }); 80 | 81 | return $q.all(putRequests); 82 | } 83 | 84 | function publishFlow(rules, flowid) { 85 | var deferred = $q.defer(); 86 | 87 | unpublishFlow(flowid).then(function (deleteresp) { 88 | deferred.notify(deleteresp); 89 | addRules(rules).then(function (putresp) { 90 | deferred.notify(putresp); 91 | deferred.resolve(rules); 92 | }); 93 | }); 94 | 95 | return deferred.promise; 96 | } 97 | 98 | } 99 | })(); -------------------------------------------------------------------------------- /web/app/services/services.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.services', [ 5 | 'flowchart' 6 | ]); 7 | })(); 8 | -------------------------------------------------------------------------------- /web/app/services/storage.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.services') 6 | .value('OH2ServiceConfiguration', {}) 7 | .service('StorageService', StorageService); 8 | 9 | StorageService.$inject = ['$http', '$q', 'OH2ServiceConfiguration']; 10 | function StorageService($http, $q, OH2ServiceConfiguration) { 11 | var SERVICE_NAME = 'org.openhab.flowsbuilder'; 12 | 13 | this.getServiceConfiguration = getServiceConfiguration; 14 | this.saveServiceConfiguration = saveServiceConfiguration; 15 | 16 | //////////////// 17 | 18 | function getServiceConfiguration() { 19 | var deferred = $q.defer(); 20 | 21 | if (OH2ServiceConfiguration.currentFlow) { 22 | deferred.resolve(OH2ServiceConfiguration); 23 | } else { 24 | $http.get('/rest/services/' + SERVICE_NAME + '/config').then(function (resp) { 25 | console.log('openHAB 2 service configuration loaded'); 26 | if (resp.data.flowsRegistry) 27 | OH2ServiceConfiguration.flowsRegistry = JSON.parse(resp.data.flowsRegistry); 28 | if (resp.data.currentFlow) 29 | OH2ServiceConfiguration.currentFlow = resp.data.currentFlow; 30 | deferred.resolve(OH2ServiceConfiguration); 31 | }, function (err) { 32 | deferred.reject(err); 33 | }); 34 | } 35 | 36 | return deferred.promise; 37 | } 38 | 39 | function saveServiceConfiguration() { 40 | var deferred = $q.defer(); 41 | var postData = {}; 42 | if (OH2ServiceConfiguration.flowsRegistry) 43 | postData.flowsRegistry = JSON.stringify(OH2ServiceConfiguration.flowsRegistry, null, 4); 44 | if (OH2ServiceConfiguration.currentFlow) 45 | postData.currentFlow = OH2ServiceConfiguration.currentFlow; 46 | else 47 | postData.currentFlow = null; 48 | 49 | $http({ 50 | method: 'PUT', 51 | url: '/rest/services/' + SERVICE_NAME + '/config', 52 | data: postData, 53 | headers: { 'Content-Type': 'application/json' } 54 | }).then (function (resp) { 55 | console.log('openHAB 2 service configuration saved'); 56 | deferred.resolve(); 57 | }, function (err) { 58 | console.error('Error while saving openHAB 2 service configuration: ' + JSON.stringify(err)); 59 | deferred.reject(err); 60 | }); 61 | 62 | return deferred.promise; 63 | } 64 | } 65 | })(); -------------------------------------------------------------------------------- /web/app/shared/config-form.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.shared') 6 | .directive('configForm', ConfigFormDirective) 7 | .directive('daysOfWeekPicker', DaysOfWeekPickerDirective); 8 | 9 | ConfigFormDirective.$inject = ['ItemsService']; 10 | function ConfigFormDirective(ItemsService) { 11 | // Usage: 12 | // 13 | // Creates: 14 | // 15 | var directive = { 16 | bindToController: true, 17 | controller: ConfigFormController, 18 | controllerAs: 'vm', 19 | link: link, 20 | restrict: 'EA', 21 | scope: { 22 | node: '=', 23 | moduleType: '=' 24 | }, 25 | templateUrl: 'app/shared/config-form.tpl.html' 26 | }; 27 | return directive; 28 | 29 | function link(scope, element, attrs) { 30 | } 31 | } 32 | /* @ngInject */ 33 | function ConfigFormController () { 34 | var vm = this; 35 | 36 | vm.configDescriptions = vm.moduleType.configDescriptions; 37 | } 38 | 39 | function DaysOfWeekToArrayFilter() { 40 | return function (input) { 41 | return input; 42 | } 43 | } 44 | 45 | 46 | DaysOfWeekPickerDirective.$inject = []; 47 | function DaysOfWeekPickerDirective() { 48 | // Usage: 49 | // 50 | // Creates: 51 | // 52 | var directive = { 53 | link: link, 54 | restrict: 'EA', 55 | scope: { 56 | ngModel: '=' 57 | }, 58 | template: '
' + 59 | ' ' + 60 | '
' // + '
{{ngModel | json }}
' 61 | }; 62 | return directive; 63 | 64 | function link(scope, element, attrs) { 65 | scope.daysOfWeek = { 66 | 'MON': { label: 'Mon' }, 67 | 'TUE': { label: 'Tue' }, 68 | 'WED': { label: 'Wed' }, 69 | 'THU': { label: 'Thu' }, 70 | 'FRI': { label: 'Fri' }, 71 | 'SAT': { label: 'Sat' }, 72 | 'SUN': { label: 'Sun' } 73 | }; 74 | 75 | if (scope.ngModel) { 76 | angular.forEach(scope.daysOfWeek, function (day, val) { 77 | if (scope.ngModel.indexOf(val) >= 0) { 78 | day.selected = true; 79 | } 80 | }); 81 | } 82 | 83 | scope.$watch('daysOfWeek', function (dow) { 84 | var model = []; 85 | angular.forEach(dow, function (day, val) { 86 | if (day.selected) { 87 | model.push(val); 88 | } 89 | scope.ngModel = model; 90 | }); 91 | }, true); 92 | 93 | } 94 | } 95 | })(); -------------------------------------------------------------------------------- /web/app/shared/config-form.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | {{$select.selected.value}} 22 | 23 | {{option.label}} 24 | 25 | 26 |
27 | 28 | 29 | 31 | 32 | 33 |
34 | 35 | {{$select.selected.value}} 36 | 37 | {{option.label}} 38 | 39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 |
TODO: rules picker... Don't use this module for now.
47 | 48 | 49 | 50 | 51 | 54 |
{{confdesc.description}}
55 |
-------------------------------------------------------------------------------- /web/app/shared/dragdrop.directive.js: -------------------------------------------------------------------------------- 1 | // credits: https://parkji.co.uk/2013/08/11/native-drag-and-drop-in-angularjs.html 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | angular 7 | .module('app.shared') 8 | 9 | .directive('toolboxDraggable', function () { 10 | return function (scope, element) { 11 | // this gives us the native JS object 12 | var el = element[0]; 13 | 14 | el.draggable = true; 15 | 16 | el.addEventListener( 17 | 'dragstart', 18 | function (e) { 19 | e.dataTransfer.effectAllowed = 'move'; 20 | e.dataTransfer.setData('moduleId', this.id); 21 | this.classList.add('drag'); 22 | return false; 23 | }, 24 | false 25 | ); 26 | 27 | el.addEventListener( 28 | 'dragend', 29 | function (e) { 30 | this.classList.remove('drag'); 31 | return false; 32 | }, 33 | false 34 | ); 35 | } 36 | }) 37 | 38 | .directive('toolboxDroppable', function () { 39 | return { 40 | scope: { 41 | drop: '&', 42 | bin: '=' 43 | }, 44 | link: function (scope, element) { 45 | // again we need the native object 46 | var el = element[0]; 47 | 48 | el.addEventListener( 49 | 'dragover', 50 | function (e) { 51 | e.dataTransfer.dropEffect = 'move'; 52 | // allows us to drop 53 | if (e.preventDefault) e.preventDefault(); 54 | this.classList.add('over'); 55 | return false; 56 | }, 57 | false 58 | ); 59 | 60 | el.addEventListener( 61 | 'dragenter', 62 | function (e) { 63 | this.classList.add('over'); 64 | return false; 65 | }, 66 | false 67 | ); 68 | 69 | el.addEventListener( 70 | 'dragleave', 71 | function (e) { 72 | this.classList.remove('over'); 73 | return false; 74 | }, 75 | false 76 | ); 77 | 78 | el.addEventListener( 79 | 'drop', 80 | function (e) { 81 | // Stops some browsers from redirecting. 82 | if (e.stopPropagation) e.stopPropagation(); 83 | 84 | this.classList.remove('over'); 85 | 86 | var itemId = e.dataTransfer.getData('moduleId'); 87 | // call the passed drop function 88 | scope.$apply(function (scope) { 89 | var fn = scope.drop(); 90 | if ('undefined' !== typeof fn) { 91 | fn(itemId, e.offsetX, e.offsetY); 92 | } 93 | }); 94 | 95 | return false; 96 | }, 97 | false 98 | ); 99 | } 100 | } 101 | }); 102 | 103 | })(); 104 | -------------------------------------------------------------------------------- /web/app/shared/itempicker.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.shared') 6 | .directive('itemTypeIcon', ItemTypeIcon) 7 | .directive('itemPicker', ItemPicker); 8 | 9 | function ItemTypeIcon() { 10 | var directive = { 11 | link: link, 12 | restrict: 'AE', 13 | template: 14 | '#' + 15 | '', 16 | scope: { 17 | type: '=' 18 | } 19 | }; 20 | return directive; 21 | 22 | function link(scope, element, attrs) { 23 | scope.getGlyph = function () { 24 | switch (scope.type) { 25 | case 'Group': return 'th-large'; 26 | case 'Switch': return 'off'; 27 | case 'String': return 'font'; 28 | case 'Number': return 'usd'; 29 | case 'Color': return 'tint'; 30 | case 'DateTime': return 'calendar'; 31 | case 'Dimmer': return 'sort-by-attributes'; 32 | case 'Rollershutter': return 'oil'; 33 | case 'Contact': return 'resize-small'; 34 | case 'Player': return 'fast-forward'; 35 | case 'Image': return 'picture'; 36 | case 'Location': return 'map-marker'; 37 | case 'Call': return 'earphone'; 38 | default: return 'asterisk'; 39 | } 40 | }; 41 | } 42 | } 43 | 44 | ItemPicker.$inject = ['$filter', 'ItemsService']; 45 | function ItemPicker($filter, ItemsService) { 46 | var directive = { 47 | bindToController: true, 48 | link: link, 49 | controller: ItemPickerController, 50 | controllerAs: 'vm', 51 | restrict: 'AE', 52 | template: 53 | '' + 54 | '  {{$select.selected.name}}' + 55 | ' ' + 56 | '
 
' + 57 | ' ' + 58 | '
' + 59 | '
', 60 | scope: { 61 | ngModel: '=', 62 | filterType: '@', 63 | includeGroups: '=?' 64 | } 65 | }; 66 | return directive; 67 | 68 | function link(scope, element, attrs) { 69 | } 70 | } 71 | ItemPickerController.$inject = ['$scope', '$filter', 'ItemsService']; 72 | function ItemPickerController ($scope, $filter, ItemsService) { 73 | var vm = this; 74 | vm.selectedItem = ItemsService.getItem(this.ngModel); 75 | vm.itemlist = ItemsService.getItems(); 76 | if (this.filterType) { 77 | vm.itemlist = $filter('filter')(vm.itemlist, function (item) { 78 | if (vm.includeGroups) { 79 | return !item.type.indexOf(vm.filterType) || !item.type.indexOf('Group'); 80 | } else { 81 | return !item.type.indexOf(vm.filterType); 82 | } 83 | }); 84 | } 85 | 86 | $scope.$watch("vm.selectedItem", function (newitem, oldvalue) { 87 | if (newitem && newitem.name) 88 | $scope.vm.ngModel = newitem.name; 89 | }); 90 | 91 | } 92 | })(); -------------------------------------------------------------------------------- /web/app/shared/resize.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.shared') 6 | .directive('resize', resize); 7 | 8 | 9 | resize.$inject = ['$log', '$window']; 10 | function resize($log, $window) { 11 | 12 | return function (scope, element) { 13 | 14 | var w = angular.element($window); 15 | //$log.info('w: ' + w[0]); 16 | scope.getWindowDimensions = function () { 17 | return { 'h': w[0].innerHeight, 'w': w[0].innerWidth }; 18 | }; 19 | scope.$watch(scope.getWindowDimensions, function (newValue, oldValue) { 20 | scope.windowHeight = newValue.h; 21 | scope.windowWidth = newValue.w; 22 | 23 | scope.style = function () { 24 | return { 25 | 'height': (newValue.h - 101) + 'px', 26 | 'width': '100%' // (newValue.w - 400) + 'px' 27 | }; 28 | }; 29 | 30 | }, true); 31 | 32 | w.bind('resize', function () { 33 | scope.$apply(); 34 | }); 35 | } 36 | 37 | } 38 | 39 | })(); -------------------------------------------------------------------------------- /web/app/shared/shared.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app.shared', [ 5 | 'ui.codemirror', 6 | 'flowchart' 7 | ]) 8 | .directive('enforceEmpty', function () { 9 | return { 10 | restrict: 'A', 11 | require: '?ngModel', 12 | link: function (scope, element, attr, ngModel) { 13 | if (ngModel) { 14 | var convertToModel = function (value) { 15 | return (!value) ? '' : value; 16 | }; 17 | ngModel.$parsers.push(convertToModel); 18 | } 19 | } 20 | }; 21 | }); 22 | })(); -------------------------------------------------------------------------------- /web/assets/defs/esh-script-scope.json: -------------------------------------------------------------------------------- 1 | { 2 | "!name": "esh-script-scope", 3 | "!define": { 4 | "Item": { 5 | "getName": { 6 | "!doc": "The name of the item", 7 | "!type": "fn() -> string" 8 | }, 9 | "getState": { 10 | "!doc": "The current state of the item", 11 | "!type": "fn() -> ESHState" 12 | }, 13 | "getLabel": { 14 | "!doc": "The label of the item", 15 | "!type": "fn() -> string" 16 | }, 17 | "getType": { 18 | "!doc": "The type of the item", 19 | "!type": "fn() -> string" 20 | }, 21 | "getGroupNames": { 22 | "!doc": "An array of group names the item belongs to", 23 | "!type": "fn() -> [string]" 24 | }, 25 | "getTags": { 26 | "!doc": "An array of tag names attached to the item", 27 | "!type": "fn() -> [string]" 28 | }, 29 | "hasTag": { 30 | "!doc": "Returns whether or not the item has the specified tag", 31 | "!type": "fn(string) -> boolean" 32 | } 33 | }, 34 | "ScriptBusEvent": { 35 | "postUpdate": { 36 | "!doc": "Posts a status update for a specified item to the event bus.", 37 | "!type": "fn(itemName: string, state: ?)" 38 | }, 39 | "sendCommand": { 40 | "!doc": "Sends a command for a specified item to the event bus.", 41 | "!type": "fn(itemName: string, command: ?)" 42 | } 43 | }, 44 | "ItemRegistry": { 45 | "getItem": { 46 | "!doc": "This method retrieves a single item from the registry.", 47 | "!type": "fn(name: string) -> Item" 48 | }, 49 | "getItemsOfType": { 50 | "!doc": "This method retrieves all items with the given type", 51 | "!type": "fn(type: string) -> [Item]" 52 | }, 53 | "getItems": { 54 | "!doc": "This method retrieves all items that match an (optional) given search pattern", 55 | "!type": "fn(pattern?: string) -> [Item]" 56 | }, 57 | "getItemsByTag": { 58 | "!doc": "Returns a list of items containing all of the given tags.", 59 | "!type": "fn(tag?: [string]) -> [Item]" 60 | }, 61 | "getItemsByTagAndType": { 62 | "!doc": "Returns a list of items with a certain type containing all of the given tags.", 63 | "!type": "fn(type: string, pattern?: [string]) -> [Item]" 64 | } 65 | }, 66 | "ESHEvent": { 67 | "getSource": { 68 | "!doc": "The source of the event", 69 | "!type": "fn() -> string" 70 | }, 71 | "getType": { 72 | "!doc": "The type of the event", 73 | "!type": "fn() -> string" 74 | }, 75 | "getTopic": { 76 | "!doc": "The topic of the event", 77 | "!type": "fn() -> string" 78 | }, 79 | "getPayload": { 80 | "!doc": "The payload of the event", 81 | "!type": "fn() -> string" 82 | }, 83 | "toString": { 84 | "!doc": "Returns a string representation of the event", 85 | "!type": "fn() -> string" 86 | } 87 | }, 88 | "ESHState": { 89 | "toString": { 90 | "!doc": "Returns a string representation of the state", 91 | "!type": "fn() -> string" 92 | } 93 | } 94 | }, 95 | "events": { 96 | "!doc": "Provides functionality to send commands to the SmartHome event bus", 97 | "!type": "ScriptBusEvent" 98 | }, 99 | "itemRegistry": { 100 | "!doc": "The Item Registry provides access to the list of SmartHome items and their state", 101 | "!type": "ItemRegistry" 102 | }, 103 | "ir": { 104 | "!doc": "Alias for itemRegistry", 105 | "!type": "ItemRegistry" 106 | }, 107 | 108 | "ON": "OnOffType", 109 | "OFF": "OnOffType", 110 | "MOVE": "StopMoveType", 111 | "STOP": "StopMoveType", 112 | "OPEN": "OpenClosedType", 113 | "CLOSED": "OpenClosedType", 114 | "DOWN": "UpDownType", 115 | "UP": "UpDownType" 116 | } -------------------------------------------------------------------------------- /web/assets/manifest/logo144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo144.png -------------------------------------------------------------------------------- /web/assets/manifest/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo192.png -------------------------------------------------------------------------------- /web/assets/manifest/logo36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo36.png -------------------------------------------------------------------------------- /web/assets/manifest/logo48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo48.png -------------------------------------------------------------------------------- /web/assets/manifest/logo72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo72.png -------------------------------------------------------------------------------- /web/assets/manifest/logo96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/assets/manifest/logo96.png -------------------------------------------------------------------------------- /web/assets/styles/common.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | background: #000; 4 | background: var(--body-bg, #000); 5 | height: 100%; 6 | } 7 | 8 | .snap-content.image-bg { 9 | //background-image: url("//source.unsplash.com/random"); 10 | background-repeat: no-repeat; 11 | background-position: center center; 12 | background-attachment: fixed; 13 | -webkit-background-size: cover; 14 | -moz-background-size: cover; 15 | -o-background-size: cover; 16 | background-size: cover; 17 | body { 18 | background: inherit; 19 | overflow: auto; 20 | } 21 | } 22 | 23 | .main.container { 24 | background: #000; 25 | background: var(--body-bg, #000); 26 | height: 100%; 27 | position: absolute; 28 | } 29 | 30 | h1, 31 | h2, 32 | h3, 33 | h4, 34 | h5 { 35 | font-weight: 300; 36 | margin: 10px 0; 37 | } 38 | 39 | h1, 40 | h2, 41 | h3.add-widgets { 42 | color: #def; 43 | color: var(--widget-text-color, #def); 44 | } 45 | 46 | h1 { 47 | font-size: $font-size-base; 48 | text-align: center; 49 | margin-left: 80px; 50 | margin-right: 80px; 51 | //line-height: 1.5; 52 | //padding-top: 6px; 53 | position: absolute; 54 | width: calc(100% - 160px); 55 | } 56 | 57 | .navbar { 58 | padding: 0 20px; 59 | } 60 | 61 | a:hover { 62 | cursor: pointer; 63 | } 64 | 65 | input { 66 | font-family: $font-family-base; 67 | font-size: $font-size-base; 68 | padding: 4px; 69 | } 70 | 71 | .container { 72 | background: #000; 73 | background: var(--body-bg, #000); 74 | margin: auto; 75 | width: 100%; 76 | height: 100%; 77 | padding-top: 48px; 78 | box-shadow: 0 0 20px var(--body-bg, #000); 79 | } 80 | 81 | .container.kiosk { 82 | padding-top: 15px; 83 | } 84 | 85 | .scrollable { 86 | -webkit-overflow-scrolling: touch; 87 | } 88 | 89 | .reconnecting { 90 | background-color: maroon; 91 | color: white; 92 | text-align: center; 93 | font-weight: bold; 94 | position: absolute; 95 | bottom: 0; 96 | z-index: 10000; 97 | padding: 5px; 98 | } 99 | 100 | .handle { 101 | cursor: move; 102 | } 103 | 104 | .modal-header h3 { 105 | margin: 0; 106 | } 107 | 108 | .CodeMirror-hints { 109 | z-index: 60000 !important; 110 | } 111 | 112 | .CodeMirror-Tern-tooltip { 113 | z-index: 60000 !important; 114 | } -------------------------------------------------------------------------------- /web/assets/styles/designer.scss: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | margin: 0 -10px; 3 | } 4 | 5 | .header { 6 | position: fixed; 7 | margin: -50px -15px -10px -15px; 8 | width: 100%; 9 | padding: 8px; 10 | z-index: 99; 11 | background: #000; 12 | background: var(--header-bg, #000); 13 | .btn:not(.btn-default):not(.btn-primary):not(.btn-success):hover { 14 | color: #def; 15 | color: var(--widget-text-color, #def); 16 | } 17 | .btn-edit-dashboard { 18 | display: none; 19 | padding: 0 6px; 20 | } 21 | h2 { 22 | display: inline; 23 | &.dashboard-title { 24 | user-select: none; 25 | -webkit-user-select: none; 26 | -moz-user-select: none; 27 | -ms-user-select: none; 28 | span { 29 | &:hover { 30 | .btn-edit-dashboard { 31 | display: inline-block; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | .fab { 40 | position: fixed; 41 | bottom: 20px; 42 | right: 20px; 43 | background: #0db9f0; 44 | background: var(--primary-color, #0db9f0); 45 | color: #fff !important; 46 | font-size: 1.25em; 47 | width: 56px; 48 | line-height: 47px; 49 | padding: 5px 10px; 50 | height: 56px; 51 | vertical-align: middle; 52 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26); 53 | border-radius: 50%; 54 | background-clip: padding-box; 55 | overflow: hidden; 56 | transition: all .3s cubic-bezier(.55, 0, .55, .2); 57 | z-index: 410; 58 | &:hover { 59 | color: rgba(255, 255, 255, .8) !important; 60 | } 61 | } 62 | 63 | .toolbar { 64 | top: 51px; 65 | } 66 | 67 | .sidebar-left { 68 | top: 101px; /* menubar 50 + toolbar 51 */ 69 | left: 0; 70 | 71 | border-right: 1px solid #eee; 72 | bottom: 0; 73 | display: block; 74 | padding: 0px; /* padding: 5px; */ 75 | position: fixed; 76 | overflow-x: hidden; 77 | overflow-y: auto; 78 | 79 | /* z-index: 1000; */ 80 | } 81 | 82 | .content-container { 83 | top: 101px; /* menubar 50 + toolbar 51 */ 84 | 85 | display: block; 86 | padding: 0px; 87 | 88 | /* z-index: 1001; */ 89 | } 90 | 91 | .content { 92 | top: 101px; /* menubar 50 + toolbar 51 */ 93 | 94 | background-color: #ffffff; 95 | overflow-x: auto; 96 | overflow-y: auto; 97 | 98 | width: calc(100%); 99 | height: calc(100%); 100 | } 101 | 102 | .sidebar-right { 103 | top: 101px; /* menubar 50 + toolbar 51 */ 104 | 105 | border-left: 1px solid #eee; 106 | bottom: 0; 107 | display: block; 108 | padding: 0px; /* padding: 5px; */ 109 | position: fixed; 110 | overflow-x: hidden; /* hide the scrollbar */ 111 | overflow-y: auto; 112 | 113 | /* z-index: 1002; */ 114 | } 115 | 116 | .modal-script-editor .CodeMirror { 117 | height: 600px; 118 | } 119 | 120 | .debugger-watch { 121 | margin-top: 5px; 122 | margin-bottom: 0; 123 | 124 | td { 125 | width: 105px; 126 | } 127 | label { 128 | line-height: 1.7; 129 | margin-bottom: 0; 130 | font-weight: normal; 131 | //overflow: hidden; 132 | text-overflow: ellipsis; 133 | width: 100px; 134 | } 135 | 136 | .input-sm { 137 | height: auto; 138 | padding: 2px 5px; 139 | margin: 0; 140 | width: 90px; 141 | } 142 | 143 | .debugger-watch-close { 144 | padding: 2px 3px; 145 | margin-left: -2px; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /web/assets/styles/flowchart.scss: -------------------------------------------------------------------------------- 1 | .fc-canvas { 2 | position: relative; 3 | min-width: 800px; 4 | min-height: 800px; 5 | width: calc(100%); 6 | height: calc(100%) - 10px; 7 | } 8 | 9 | .fc-canvas svg { 10 | position: relative; 11 | min-width: 800px; 12 | min-height: 800px; 13 | width: 100%; 14 | height: 100%; 15 | } 16 | 17 | .button-overlay { 18 | position: absolute; 19 | top: 40px; 20 | left: 40px; 21 | z-index: 10; 22 | } 23 | 24 | .button-overlay button { 25 | display: block; 26 | padding: 10px; 27 | margin-bottom: 15px; 28 | border-radius: 10px; 29 | border: none; 30 | box-shadow: none; 31 | color: #fff; 32 | font-size: 20px; 33 | background-color: #F15B26; 34 | } 35 | 36 | .button-overlay button:hover:not(:disabled) { 37 | border: 4px solid #b03911; 38 | border-radius: 5px; 39 | 40 | margin: -4px; 41 | margin-bottom: 11px; 42 | } 43 | 44 | .button-overlay button:disabled { 45 | -webkit-filter: brightness(70%); 46 | filter: brightness(70%); 47 | } 48 | 49 | .fc-node { 50 | z-index: 1; 51 | } 52 | 53 | .innerNode { 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | min-width: 100px; 58 | border-radius: 5px; 59 | 60 | background-color: #F15B26; 61 | color: #fff; 62 | font-size: 13px; 63 | 64 | &.trigger { 65 | border-radius: 15px; 66 | background-color: #F15B26; 67 | } 68 | 69 | &.condition { 70 | background: #070; /* fallback */ 71 | background: 72 | linear-gradient(135deg, transparent 10px, #070 0) top left, 73 | linear-gradient(225deg, transparent 10px, #070 0) top right, 74 | linear-gradient(315deg, transparent 10px, #070 0) bottom right, 75 | linear-gradient(45deg, transparent 10px, #070 0) bottom left; 76 | background-size: 51% 51%; 77 | background-repeat: no-repeat; 78 | } 79 | 80 | &.action { 81 | background-color: #66d; 82 | } 83 | 84 | &.draggable { 85 | cursor: move; 86 | } 87 | } 88 | 89 | .innerNode.trigger { 90 | border-radius: 15px; 91 | background-color: #F15B26; 92 | } 93 | 94 | .fc-node.fc-hover { 95 | -webkit-filter: brightness(70%); 96 | filter: brightness(70%);; 97 | } 98 | 99 | .fc-node.fc-selected { 100 | -webkit-filter: brightness(70%); 101 | filter: brightness(70%); 102 | } 103 | 104 | .fc-node.fc-dragging { 105 | z-index: 10; 106 | } 107 | 108 | .fc-node p { 109 | padding: 5px 15px; 110 | text-align: center; 111 | margin: auto; 112 | } 113 | 114 | .fc-topConnectors, .fc-bottomConnectors { 115 | position: absolute; 116 | left: 0; 117 | width: 100%; 118 | 119 | display: flex; 120 | flex-direction: row; 121 | 122 | z-index: -10; 123 | } 124 | 125 | .fc-topConnectors { 126 | top: -52px; 127 | } 128 | 129 | .fc-bottomConnectors { 130 | bottom: -52px; 131 | } 132 | 133 | .fc-magnet { 134 | display: flex; 135 | flex-grow: 1; 136 | height: 60px; 137 | 138 | justify-content: center; 139 | } 140 | 141 | .fc-topConnectors .fc-magnet { 142 | align-items: flex-end; 143 | } 144 | 145 | .fc-bottomConnectors .fc-magnet { 146 | align-items: flex-start; 147 | } 148 | 149 | .fc-connector { 150 | width: 18px; 151 | height: 18px; 152 | 153 | // border: 10px solid transparent; 154 | -moz-background-clip: padding; /* Firefox 3.6 */ 155 | -webkit-background-clip: padding; /* Safari 4? Chrome 6? */ 156 | background-clip: padding-box; 157 | border-radius: 50% 50%; 158 | background-color: #F7A789; 159 | color: #fff; 160 | 161 | &.path-true { 162 | background-color: #090; 163 | } 164 | &.path-false { 165 | background-color: #900; 166 | } 167 | } 168 | 169 | .fc-connector.fc-hover { 170 | background-color: #000; 171 | } 172 | 173 | .fc-edge { 174 | stroke: gray; 175 | stroke-width: 4; 176 | fill: transparent; 177 | } 178 | 179 | .fc-edge.fc-hover { 180 | stroke: gray; 181 | stroke-width: 6; 182 | fill: transparent; 183 | } 184 | 185 | @keyframes dash { 186 | from { 187 | stroke-dashoffset: 500; 188 | } 189 | } 190 | 191 | .fc-edge.fc-selected { 192 | stroke: red; 193 | stroke-width: 4; 194 | fill: transparent; 195 | } 196 | 197 | .fc-edge.fc-active { 198 | animation: dash 3s linear infinite; 199 | stroke-dasharray: 20; 200 | } 201 | 202 | .fc-edge.fc-dragging { 203 | pointer-events: none; 204 | } 205 | 206 | .edge-endpoint { 207 | fill: gray; 208 | } 209 | 210 | .fc-nodedelete { 211 | display: none; 212 | } 213 | 214 | .fc-selected .fc-nodedelete { 215 | display: block; 216 | position: absolute; 217 | right: -13px; 218 | top: -13px; 219 | border: solid 2px white; 220 | cursor: pointer; 221 | 222 | border-radius: 50%; 223 | font-weight: 600; 224 | font-size: 20px; 225 | line-height: 0.8; 226 | 227 | height: 25px; 228 | padding-top: 2px; 229 | width: 27px; 230 | 231 | background: #494949; 232 | color: #fff; 233 | text-align: center; 234 | } 235 | 236 | -------------------------------------------------------------------------------- /web/assets/styles/reset.scss: -------------------------------------------------------------------------------- 1 | // Overwrite Bootstrap variables here 2 | // $brand-primary: $primary-color; 3 | $brand-success: #5cb85c; 4 | $brand-info: #5bc0de; 5 | $brand-warning: #f0ad4e; 6 | $brand-danger: #d9534f; 7 | $link-color: #abc; // Bootstrap variable 8 | $link-hover-decoration: none; 9 | $icon-font-path: '../fonts/bootstrap/'; 10 | $font-family-base: "Roboto", 11 | -apple-system, 12 | BlinkMacSystemFont, 13 | "Segoe UI", 14 | Helvetica, 15 | Arial, 16 | sans-serif, 17 | "Apple Color Emoji", 18 | "Segoe UI Emoji", 19 | "Segoe UI Symbol"; 20 | $nav-tabs-active-link-hover-bg: #fff; 21 | $border-radius-base: 0; 22 | $border-radius-large: $border-radius-base; 23 | $border-radius-small: $border-radius-base; 24 | $text-muted: var(--body-color, #ccc); -------------------------------------------------------------------------------- /web/assets/styles/themes/default.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Basic styles */ 3 | --body-bg: #f5f5f5; 4 | --body-color: #000; 5 | --primary-color: #f15b26; 6 | --box-bg: #234; 7 | --header-bg: var(--body-bg); 8 | } -------------------------------------------------------------------------------- /web/assets/styles/themes/themes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default", 4 | "name": "Default" 5 | } 6 | ] -------------------------------------------------------------------------------- /web/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esh-flowsbuilder", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/ghys/org.openhab.ui.flowseditor", 5 | "authors": [ 6 | "Yannick Schaus" 7 | ], 8 | "description": "Dashboard user interface for openHAB", 9 | "main": "", 10 | "moduleType": [], 11 | "keywords": [ 12 | "dashboard", 13 | "panel", 14 | "openhab", 15 | "smarthome", 16 | "ui" 17 | ], 18 | "license": "EPL", 19 | "private": true, 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ], 27 | "dependencies": { 28 | "angular": "~1.5.11", 29 | "angular-bootstrap": "^1.x", 30 | "angular-prompt": "~1.2", 31 | "angular-local-storage": "~0.4.0", 32 | "angular-route": "~1.5.11", 33 | "angular-sanitize": "~1.5.11", 34 | "angular-touch": "~1.5.11", 35 | "angularjs-slider": "~5.4.3", 36 | "angular-ui-codemirror": "~0.3.0", 37 | "oclazyload": "ocLazyLoad#~1.0.9", 38 | "angular-clipboard": "~1.5.0", 39 | "bootstrap-sass": "^3.3.7", 40 | "roboto-fontface": "^0.5.0", 41 | "angular-ui-clock": "latest", 42 | "angular-file-saver": "~1.1.2", 43 | "event-source-polyfill": "latest", 44 | "angular-ui-select": "^0.19.6", 45 | "selectize": "~0.12.4", 46 | "ngFlowchart": "^0.5.1", 47 | "tern": "0.19.0", 48 | "acorn-dist": "^3.3.0" 49 | }, 50 | "resolutions": { 51 | "angular": "~1.5.11" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/favicon.ico -------------------------------------------------------------------------------- /web/fonts/bootstrap/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/bootstrap/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/fonts/bootstrap/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/bootstrap/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/fonts/bootstrap/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/bootstrap/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/fonts/bootstrap/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/bootstrap/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /web/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /web/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /web/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /web/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /web/gulpfile.js: -------------------------------------------------------------------------------- 1 | var clean = require('gulp-clean'); 2 | var concat = require('gulp-concat'); 3 | var cleanCSS = require('gulp-clean-css'); 4 | var eslint = require('gulp-eslint'); 5 | var gulp = require('gulp'); 6 | var gulpFilter = require('gulp-filter'); 7 | var mainBowerFiles = require('gulp-main-bower-files'); 8 | var path = require('path'); 9 | var plumber = require('gulp-plumber'); 10 | var rename = require('gulp-rename'); 11 | var sass = require('gulp-sass'); 12 | var sassGlob = require('gulp-sass-glob'); 13 | var uglify = require('gulp-uglify'); 14 | var watch = require('gulp-watch'); 15 | var webserver = require('gulp-webserver'); 16 | 17 | gulp.task('lint', function() { 18 | return gulp.src(['app/**/*.js']) 19 | .pipe(eslint()) 20 | .pipe(eslint.format()); 21 | }); 22 | 23 | gulp.task('web-server', function() { 24 | gulp.src('./') 25 | .pipe(webserver({ 26 | livereload: true, 27 | directoryListing: false, 28 | open: true 29 | })); 30 | }); 31 | 32 | gulp.task('watch', function() { 33 | gulp.watch([ 34 | './assets/styles/**/*.scss', 35 | './vendor/**/*.scss' 36 | ], ['sass']); 37 | }); 38 | 39 | gulp.task('server', [ 40 | 'watch', 41 | 'web-server' 42 | ], function() {}); 43 | 44 | gulp.task('sass-vendor', function() { 45 | gulp.src('./vendor/styles.scss') 46 | .pipe(plumber()) 47 | .pipe(sassGlob()) 48 | .pipe(sass()) 49 | .pipe(cleanCSS({ 50 | compatibility: '*,-properties.merging' 51 | })) 52 | .pipe(rename({ 53 | suffix: '.min' 54 | })) 55 | .pipe(gulp.dest('./vendor')); 56 | }); 57 | 58 | gulp.task('sass', [ 59 | 'sass-vendor' 60 | ], function() {}); 61 | 62 | gulp.task('vendor-fonts', function() { 63 | return gulp.src([ 64 | 'bower_components/bootstrap-sass/assets/fonts/*/**', 65 | 'bower_components/roboto-fontface/fonts/*/Roboto-Regular.*' 66 | ]).pipe(gulp.dest('fonts')); 67 | }); 68 | 69 | 70 | gulp.task('uglify-flowchart', function() { 71 | return gulp.src('bower_components/ngFlowchart/dist/ngFlowchart.js') 72 | .pipe(uglify()) 73 | .pipe(gulp.dest('bower_components/ngFlowchart/dist')); 74 | }); 75 | 76 | gulp.task('vendor-js', ['uglify-flowchart'], function() { 77 | return gulp.src([ 78 | 'bower_components/angular/angular.min.js', 79 | 'bower_components/angular-route/angular-route.min.js', 80 | 'bower_components/sprintf/dist/sprintf.min.js', 81 | 'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js', 82 | 'bower_components/angular-sanitize/angular-sanitize.min.js', 83 | 'bower_components/sprintf/dist/angular-sprintf.min.js', 84 | 'bower_components/angular-prompt/dist/angular-prompt.min.js', 85 | 'bower_components/angular-local-storage/dist/angular-local-storage.min.js', 86 | 'bower_components/angular-ui-codemirror/ui-codemirror.min.js', 87 | 'bower_components/angular-clipboard/angular-clipboard.js', 88 | 'bower_components/oclazyload/dist/ocLazyLoad.min.js', 89 | 'bower_components/angular-ui-select/dist/select.min.js', 90 | 'bower_components/angular-file-saver/dist/angular-file-saver.bundle.min.js', 91 | 'bower_components/angular-file-saver/dist/angular-file-saver.bundle.min.js', 92 | 'bower_components/event-source-polyfill/eventsource.min.js', 93 | 'bower_components/ngFlowchart/dist/ngFlowchart.js', 94 | 'vendor/angular-web-colorpicker.js' 95 | ]).pipe(concat('vendor.js')).pipe(gulp.dest('vendor')); 96 | 97 | }); 98 | 99 | 100 | gulp.task('codemirror-lib', function() { 101 | return gulp.src([ 102 | 'bower_components/codemirror/lib/codemirror.js' 103 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/lib')); 104 | }); 105 | 106 | gulp.task('codemirror-css', function() { 107 | return gulp.src([ 108 | 'bower_components/codemirror/lib/codemirror.css' 109 | ]).pipe(gulp.dest('vendor/cm/lib')); 110 | }); 111 | 112 | gulp.task('codemirror-addon-dialog', function() { 113 | return gulp.src([ 114 | 'bower_components/codemirror/addon/dialog/dialog.js', 115 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/dialog')); 116 | }); 117 | 118 | gulp.task('codemirror-addon-dialog-resources', function() { 119 | return gulp.src([ 120 | 'bower_components/codemirror/addon/dialog/dialog.css', 121 | ]).pipe(gulp.dest('vendor/cm/addon/dialog')); 122 | }); 123 | 124 | gulp.task('codemirror-addon-edit', function() { 125 | return gulp.src([ 126 | 'bower_components/codemirror/addon/edit/matchbrackets.js', 127 | 'bower_components/codemirror/addon/edit/matchtags.js', 128 | 'bower_components/codemirror/addon/edit/closebrackets.js', 129 | 'bower_components/codemirror/addon/edit/closetag.js', 130 | 'bower_components/codemirror/mode/xml/xml.js' 131 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/edit')); 132 | }); 133 | 134 | gulp.task('codemirror-addon-fold', function() { 135 | return gulp.src([ 136 | 'bower_components/codemirror/addon/fold/xml-fold.js', 137 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/fold')); 138 | }); 139 | 140 | gulp.task('codemirror-addon-hint', function() { 141 | return gulp.src([ 142 | 'bower_components/codemirror/addon/hint/show-hint.js', 143 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/hint')); 144 | }); 145 | 146 | gulp.task('codemirror-addon-hint-resources', function() { 147 | return gulp.src([ 148 | 'bower_components/codemirror/addon/hint/show-hint.css', 149 | ]).pipe(gulp.dest('vendor/cm/addon/hint')); 150 | }); 151 | 152 | gulp.task('codemirror-addon-mode', function() { 153 | return gulp.src([ 154 | 'bower_components/codemirror/addon/mode/overlay.js', 155 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/mode')); 156 | }); 157 | 158 | gulp.task('codemirror-addon-tern', function() { 159 | return gulp.src([ 160 | 'bower_components/codemirror/addon/tern/tern.js', 161 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/tern')); 162 | }); 163 | 164 | gulp.task('codemirror-addon-tern-libs', function() { 165 | return gulp.src([ 166 | 'bower_components/acorn-dist/dist/acorn.js', 167 | 'bower_components/acorn-dist/dist/acorn_loose.js', 168 | 'bower_components/acorn-dist/dist/walk.js', 169 | 'bower_components/tern/lib/signal.js', 170 | 'bower_components/tern/lib/tern.js', 171 | 'bower_components/tern/lib/def.js', 172 | 'bower_components/tern/lib/comment.js', 173 | 'bower_components/tern/lib/infer.js', 174 | 'bower_components/tern/plugin/doc_comment.js', 175 | ]).pipe(concat('tern-libs.js')).pipe(uglify()).pipe(gulp.dest('vendor/cm/addon/tern')); 176 | }); 177 | 178 | gulp.task('codemirror-addon-tern-resources', function() { 179 | return gulp.src([ 180 | 'bower_components/codemirror/addon/tern/tern.css', 181 | 'bower_components/tern/defs/ecma5.json' 182 | ]).pipe(gulp.dest('vendor/cm/addon/tern')); 183 | }); 184 | 185 | gulp.task('codemirror-mode-xml', function() { 186 | return gulp.src([ 187 | 'bower_components/codemirror/mode/xml/xml.js' 188 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/mode/xml')); 189 | }); 190 | 191 | gulp.task('codemirror-mode-javascript', function() { 192 | return gulp.src([ 193 | 'bower_components/codemirror/mode/javascript/javascript.js' 194 | ]).pipe(uglify()).pipe(gulp.dest('vendor/cm/mode/javascript')); 195 | }); 196 | 197 | gulp.task('codemirror-theme', function() { 198 | return gulp.src([ 199 | 'bower_components/codemirror/theme/rubyblue.css' 200 | ]).pipe(gulp.dest('vendor/cm/theme')); 201 | }); 202 | 203 | gulp.task('codemirror', [ 204 | 'codemirror-lib', 205 | 'codemirror-css', 206 | 'codemirror-addon-dialog', 207 | 'codemirror-addon-dialog-resources', 208 | 'codemirror-addon-edit', 209 | 'codemirror-addon-fold', 210 | 'codemirror-addon-hint', 211 | 'codemirror-addon-hint-resources', 212 | 'codemirror-addon-mode', 213 | 'codemirror-addon-tern', 214 | 'codemirror-addon-tern-resources', 215 | 'codemirror-addon-tern-libs', 216 | 'codemirror-mode-xml', 217 | 'codemirror-mode-javascript', 218 | 'codemirror-theme' 219 | ], function() {}); 220 | 221 | gulp.task('vendor', [ 222 | 'vendor-js', 223 | 'vendor-fonts' 224 | ], function() {}); 225 | 226 | gulp.task('default', ['vendor', 'codemirror'], function() {}); 227 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{(currentFlowId) ? currentFlowId + ' - ' : ''}}Flows Builder 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 |   Connection lost! Trying to reconnect...  46 |
47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flows Builder for Eclipse SmartHome/openHAB", 3 | "short_name": "Flows Builder", 4 | "icons": [ 5 | { 6 | "src": "assets/manifest/logo36.png", 7 | "sizes": "36x36", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "assets/manifest/logo48.png", 12 | "sizes": "48x48", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/manifest/logo72.png", 17 | "sizes": "72x72", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/manifest/logo96.png", 22 | "sizes": "96x96", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/manifest/logo144.png", 27 | "sizes": "144x144", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/manifest/logo192.png", 32 | "sizes": "192x192", 33 | "type": "image/png" 34 | } 35 | ], 36 | "display": "fullscreen", 37 | "background_color": "black" 38 | } -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esh-flowsbuilder", 3 | "version": "1.0.0", 4 | "author": "Yannick Schaus", 5 | "description": "Graphical flows builder for the openHAB rule engine", 6 | "license": "EPL-1.0", 7 | "contributors": [ 8 | { 9 | "name": "Yannick Schaus", 10 | "email": "habpanel@schaus.net" 11 | } 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/ghys/org.openhab.ui.flowseditor" 16 | }, 17 | "keywords": [ 18 | "openhab", 19 | "smarthome", 20 | "ui", 21 | "flows", 22 | "editor", 23 | "rules" 24 | ], 25 | "devDependencies": { 26 | "babel-eslint": "^6.1.2", 27 | "eslint": "^3.2.2", 28 | "eslint-config-esnext": "^1.4.3", 29 | "eslint-config-recommended": "^1.3.1", 30 | "eslint-plugin-babel": "^3.3.0", 31 | "eslint-plugin-react": "^5.2.2", 32 | "eslint-plugin-react-native": "^1.1.0", 33 | "gulp": "^3.9.1", 34 | "gulp-clean": "^0.3.2", 35 | "gulp-clean-css": "^3.4.2", 36 | "gulp-concat": "^2.6.0", 37 | "gulp-eslint": "^3.0.1", 38 | "gulp-filter": "^4.0.0", 39 | "gulp-main-bower-files": "1.5.3", 40 | "gulp-plumber": "^1.1.0", 41 | "gulp-rename": "^1.2.2", 42 | "gulp-sass": "^2.3.2", 43 | "gulp-sass-glob": "^1.0.6", 44 | "gulp-uglify": "^2.0.0", 45 | "gulp-watch": "^4.3.10", 46 | "gulp-webserver": "^0.9.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /web/service-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | Dummy service worker to enable app install banner 3 | in Chrome on Android when using HTTPS. 4 | */ -------------------------------------------------------------------------------- /web/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ghys/flowsbuilder/baed3b607e85486c988f53b663ced624feb9f526/web/tile.png -------------------------------------------------------------------------------- /web/vendor/angular-web-colorpicker.js: -------------------------------------------------------------------------------- 1 | angular.module('web.colorpicker', []) 2 | 3 | angular.module('web.colorpicker').directive('webColorpicker', function() { 4 | return { 5 | template: '
' + 6 | '
' + 7 | '
' + 8 | '
' + 9 | '
' + 10 | '
' + 11 | '
Transparent
', 12 | restrict: 'EA', 13 | scope: { 14 | dabModel: '=', 15 | dabHeight: '@', 16 | dabWidth: '@', 17 | dabRadius: '@', 18 | dabVertical: '@', 19 | dabRotate: '@', 20 | showGrayscale: '@' 21 | }, 22 | link: function(scope, ele, attrs) { 23 | 24 | scope.selectColor = function(color) { 25 | scope.dabModel = color 26 | } 27 | 28 | scope.rows = [ 29 | { offset: 3, colors: ['#003366', '#336699', '#3366CC', '#003399', '#000099', '#0000CC', '#000066'] }, 30 | { offset: 2.5, colors: ['#006666', '#006699', '#0099CC', '#0066CC', '#0033CC', '#0000FF', '#3333FF', '#333399'] }, 31 | { offset: 2, colors: ['#669999', '#009999', '#33CCCC', '#00CCFF', '#0099FF', '#0066FF', '#3366FF', '#3333CC', '#666699'] }, 32 | { offset: 1.5, colors: ['#339966', '#00CC99', '#00FFCC', '#00FFFF', '#33CCFF', '#3399FF', '#6699FF', '#6666FF', '#6600FF', '#6600CC'] }, 33 | { offset: 1, colors: ['#339933', '#00CC66', '#00FF99', '#66FFCC', '#66FFFF', '#66CCFF', '#99CCFF', '#9999FF', '#9966FF', '#9933FF', '#9900FF'] }, 34 | { offset: 0.5, colors: ['#006600', '#00CC00', '#00FF00', '#66FF99', '#99FFCC', '#CCFFFF', '#CCCCFF', '#CC99FF', '#CC66FF', '#CC33FF', '#CC00FF', '#9900CC'] }, 35 | { offset: 0, colors: ['#003300', '#009933', '#33CC33', '#66FF66', '#99FF99', '#CCFFCC', '#FFFFFF', '#FFCCFF', '#FF99FF', '#FF66FF', '#FF00FF', '#CC00CC', '#660066'] }, 36 | { offset: 0.5, colors: ['#336600', '#009900', '#66FF33', '#99FF66', '#CCFF99', '#FFFFCC', '#FFCCCC', '#FF99CC', '#FF66CC', '#FF33CC', '#CC0099', '#993399'] }, 37 | { offset: 1, colors: ['#333300', '#669900', '#99FF33', '#CCFF66', '#FFFF99', '#FFCC99', '#FF9999', '#FF6699', '#FF3399', '#CC3399', '#990099'] }, 38 | { offset: 1.5, colors: ['#666633', '#99CC00', '#CCFF33', '#FFFF66', '#FFCC66', '#FF9966', '#FF6666', '#FF0066', '#CC6699', '#993366'] }, 39 | { offset: 2, colors: ['#999966', '#CCCC00', '#FFFF00', '#FFCC00', '#FF9933', '#FF6600', '#FF5050', '#CC0066', '#660033'] }, 40 | { offset: 2.5, colors: ['#996633', '#CC9900', '#FF9900', '#CC6600', '#FF3300', '#FF0000', '#CC0000', '#990033'] }, 41 | { offset: 3, colors: ['#663300', '#996600', '#CC3300', '#993300', '#990000', '#800000', '#993333'] } 42 | ]; 43 | 44 | if (scope.showGrayscale) { 45 | scope.rows.push({ colors: [] }) 46 | scope.rows.push({ offset: 1.5, colors: ['#E6E6E6', '#CCCCCC', '#B3B3B3', '#999999', '#808080', '#666666', '#4C4C4C', '#333333', '#191919', '#000000'] }) 47 | scope.rows.push({ colors: [] }) 48 | scope.rows.push({ offset: 0, colors: ['transparent'] }) 49 | } 50 | 51 | var area, i, len, ref; 52 | 53 | ref = scope.rows 54 | for (i = 0, len = ref.length; i < len; i++) { 55 | color = ref[i] 56 | for (j = 0, leng = color.length; j < leng; j++) { 57 | if (color === scope.dabModel) { 58 | scope.selectColor(color) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | }) 65 | -------------------------------------------------------------------------------- /web/vendor/cm/addon/dialog/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: absolute; 3 | left: 0; right: 0; 4 | background: inherit; 5 | z-index: 15; 6 | padding: .1em .8em; 7 | overflow: hidden; 8 | color: inherit; 9 | } 10 | 11 | .CodeMirror-dialog-top { 12 | border-bottom: 1px solid #eee; 13 | top: 0; 14 | } 15 | 16 | .CodeMirror-dialog-bottom { 17 | border-top: 1px solid #eee; 18 | bottom: 0; 19 | } 20 | 21 | .CodeMirror-dialog input { 22 | border: none; 23 | outline: none; 24 | background: transparent; 25 | width: 20em; 26 | color: inherit; 27 | font-family: monospace; 28 | } 29 | 30 | .CodeMirror-dialog button { 31 | font-size: 70%; 32 | } 33 | -------------------------------------------------------------------------------- /web/vendor/cm/addon/dialog/dialog.js: -------------------------------------------------------------------------------- 1 | !function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){function o(e,o,n){var t,i=e.getWrapperElement();return t=i.appendChild(document.createElement("div")),t.className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof o?t.innerHTML=o:t.appendChild(o),t}function n(e,o){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=o}e.defineExtension("openDialog",function(t,i,r){function u(e){if("string"==typeof e)s.value=e;else{if(f)return;f=!0,c.parentNode.removeChild(c),a.focus(),r.onClose&&r.onClose(c)}}r||(r={}),n(this,null);var l,c=o(this,t,r.bottom),f=!1,a=this,s=c.getElementsByTagName("input")[0];return s?(s.focus(),r.value&&(s.value=r.value,!1!==r.selectValueOnOpen&&s.select()),r.onInput&&e.on(s,"input",function(e){r.onInput(e,s.value,u)}),r.onKeyUp&&e.on(s,"keyup",function(e){r.onKeyUp(e,s.value,u)}),e.on(s,"keydown",function(o){r&&r.onKeyDown&&r.onKeyDown(o,s.value,u)||((27==o.keyCode||!1!==r.closeOnEnter&&13==o.keyCode)&&(s.blur(),e.e_stop(o),u()),13==o.keyCode&&i(s.value,o))}),!1!==r.closeOnBlur&&e.on(s,"blur",u)):(l=c.getElementsByTagName("button")[0])&&(e.on(l,"click",function(){u(),a.focus()}),!1!==r.closeOnBlur&&e.on(l,"blur",u),l.focus()),u}),e.defineExtension("openConfirm",function(t,i,r){function u(){f||(f=!0,l.parentNode.removeChild(l),a.focus())}n(this,null);var l=o(this,t,r&&r.bottom),c=l.getElementsByTagName("button"),f=!1,a=this,s=1;c[0].focus();for(var d=0;d=0;s--){var f=o[s].head;r.replaceRange("",u(f.line,f.ch-1),u(f.line,f.ch+1),"+delete")}}function i(r){var i=n(r),a=i&&t(i,"explode");if(!a||r.getOption("disableInput"))return e.Pass;for(var o=r.listSelections(),s=0;s0;return{anchor:new u(t.anchor.line,t.anchor.ch+(n?-1:1)),head:new u(t.head.line,t.head.ch+(n?1:-1))}}function o(r,i){var o=n(r);if(!o||r.getOption("disableInput"))return e.Pass;var c=t(o,"pairs"),h=c.indexOf(i);if(-1==h)return e.Pass;for(var d,g=t(o,"triples"),p=c.charAt(h+1)==i,v=r.listSelections(),m=h%2==0,b=0;b1&&g.indexOf(i)>=0&&r.getRange(u(k.line,k.ch-2),k)==i+i&&(k.ch<=2||r.getRange(u(k.line,k.ch-3),u(k.line,k.ch-2))!=i))x="addFour";else if(p){if(e.isWordChar(P)||!l(r,k,i))return e.Pass;x="both"}else{if(!m||r.getLine(k.line).length!=k.ch&&!s(P,c)&&!/\s/.test(P))return e.Pass;x="both"}else x=p&&f(r,k)?"both":g.indexOf(i)>=0&&r.getRange(k,u(k.line,k.ch+3))==i+i+i?"skipThree":"skip";if(d){if(d!=x)return e.Pass}else d=x}var S=h%2?c.charAt(h-1):i,y=h%2?i:c.charAt(h+1);r.operation(function(){if("skip"==d)r.execCommand("goCharRight");else if("skipThree"==d)for(var e=0;e<3;e++)r.execCommand("goCharRight");else if("surround"==d){for(var t=r.getSelections(),e=0;e-1&&n%2==1}function c(e,t){var n=e.getRange(u(t.line,t.ch-1),u(t.line,t.ch+1));return 2==n.length?n:null}function l(t,n,r){var i=t.getLine(n.line),a=t.getTokenAt(n);if(/\bstring2?\b/.test(a.type)||f(t,n))return!1;var o=new e.StringStream(i.slice(0,n.ch)+r+i.slice(n.ch),4);for(o.pos=o.start=a.start;;){var s=t.getMode().token(o,a.state);if(o.pos>=n.ch+1)return/\bstring2?\b/.test(s);o.start=o.pos}}function f(e,t){var n=e.getTokenAt(u(t.line,t.ch+1));return/\bstring/.test(n.type)&&n.start==t.ch}var h={pairs:"()[]{}''\"\"",triples:"",explode:"[]{}"},u=e.Pos;e.defineOption("autoCloseBrackets",!1,function(t,n,r){r&&r!=e.Init&&(t.removeKeyMap(g),t.state.closeBrackets=null),n&&(t.state.closeBrackets=n,t.addKeyMap(g))});for(var d=h.pairs+"`",g={Backspace:r,Enter:i},p=0;pc.ch&&(v=v.slice(0,v.length-d.end+c.ch));var b=v.toLowerCase();if(!v||"string"==d.type&&(d.end!=c.ch||!/[\"\']/.test(d.string.charAt(d.string.length-1))||1==d.string.length)||"tag"==d.type&&"closeTag"==g.type||d.string.indexOf("/")==d.string.length-1||h&&r(h,b)>-1||a(t,v,c,g,!0))return e.Pass;var y=p&&r(p,b)>-1;o[l]={indent:y,text:">"+(y?"\n\n":"")+"",newPos:y?e.Pos(c.line+1,0):e.Pos(c.line,c.ch+1)}}for(var l=n.length-1;l>=0;l--){var x=o[l];t.replaceRange(x.text,n[l].head,n[l].anchor,"+insert");var P=t.listSelections().slice(0);P[l]={head:x.newPos,anchor:x.newPos},t.setSelections(P),x.indent&&(t.indentLine(x.newPos.line,null,!0),t.indentLine(x.newPos.line+1,null,!0))}}function n(t,n){for(var o=t.listSelections(),r=[],i=n?"/":""!=t.getLine(l.line).charAt(c.end)&&(g+=">"),r[s]=g}t.replaceSelections(r),o=t.listSelections();for(var s=0;s'"]=function(e){return t(e)}),n.addKeyMap(i)}});var i=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],s=["applet","blockquote","body","button","div","dl","fieldset","form","frameset","h1","h2","h3","h4","h5","h6","head","html","iframe","layer","legend","object","ol","p","select","table","ul"];e.commands.closeTag=function(e){return n(e)}}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/edit/matchbrackets.js: -------------------------------------------------------------------------------- 1 | !function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){function t(e,t,r){var i=e.getLineHandle(t.line),o=t.ch-1,l=r&&r.afterCursor;null==l&&(l=/(^| )cm-fat-cursor($| )/.test(e.getWrapperElement().className));var f=!l&&o>=0&&c[i.text.charAt(o)]||c[i.text.charAt(++o)];if(!f)return null;var s=">"==f.charAt(1)?1:-1;if(r&&r.strict&&s>0!=(o==t.ch))return null;var u=e.getTokenTypeAt(a(t.line,o+1)),h=n(e,a(t.line,o+(s>0?1:0)),s,u||null,r);return null==h?null:{from:a(t.line,o),to:h&&h.pos,match:h&&h.ch==f.charAt(0),forward:s>0}}function n(e,t,n,r,i){for(var o=i&&i.maxScanLineLength||1e4,l=i&&i.maxScanLines||1e3,f=[],s=i&&i.bracketRegex?i.bracketRegex:/[(){}[\]]/,u=n>0?Math.min(t.line+l,e.lastLine()+1):Math.max(e.firstLine()-1,t.line-l),h=t.line;h!=u;h+=n){var m=e.getLine(h);if(m){var d=n>0?0:m.length-1,g=n>0?m.length:-1;if(!(m.length>o))for(h==t.line&&(d=t.ch-(n<0?1:0));d!=g;d+=n){var p=m.charAt(d);if(s.test(p)&&(void 0===r||e.getTokenTypeAt(a(h,d+1))==r)){var v=c[p];if(">"==v.charAt(1)==n>0)f.push(p);else{if(!f.length)return{pos:a(h,d),ch:p};f.pop()}}}}}return h-n!=(n>0?e.lastLine():e.firstLine())&&null}function r(e,n,r){for(var i=e.state.matchBrackets.maxHighlightLineLength||1e3,c=[],l=e.listSelections(),f=0;f",")":"(<","[":"]>","]":"[<","{":"}>","}":"{<"},l=null;e.defineOption("matchBrackets",!1,function(t,n,r){r&&r!=e.Init&&(t.off("cursorActivity",i),l&&(l(),l=null)),n&&(t.state.matchBrackets="object"==typeof n?n:{},t.on("cursorActivity",i))}),e.defineExtension("matchBrackets",function(){r(this,!0)}),e.defineExtension("findMatchingBracket",function(e,n,r){return(r||"boolean"==typeof n)&&(r?(r.strict=n,n=r):n=n?{strict:!0}:null),t(this,e,n)}),e.defineExtension("scanForBracket",function(e,t,r,i){return n(this,e,t,r,i)})}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/edit/matchtags.js: -------------------------------------------------------------------------------- 1 | !function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../fold/xml-fold")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../fold/xml-fold"],t):t(CodeMirror)}(function(t){"use strict";function e(t){t.state.tagHit&&t.state.tagHit.clear(),t.state.tagOther&&t.state.tagOther.clear(),t.state.tagHit=t.state.tagOther=null}function a(a){a.state.failedTagMatch=!1,a.operation(function(){if(e(a),!a.somethingSelected()){var o=a.getCursor(),i=a.getViewport();i.from=Math.min(i.from,o.line),i.to=Math.max(o.line+1,i.to);var r=t.findMatchingTag(a,o,i);if(r){if(a.state.matchBothTags){var n="open"==r.at?r.open:r.close;n&&(a.state.tagHit=a.markText(n.from,n.to,{className:"CodeMirror-matchingtag"}))}var c="close"==r.at?r.open:r.close;c?a.state.tagOther=a.markText(c.from,c.to,{className:"CodeMirror-matchingtag"}):a.state.failedTagMatch=!0}}})}function o(t){t.state.failedTagMatch&&a(t)}t.defineOption("matchTags",!1,function(i,r,n){n&&n!=t.Init&&(i.off("cursorActivity",a),i.off("viewportChange",o),e(i)),r&&(i.state.matchBothTags="object"==typeof r&&r.bothTags,i.on("cursorActivity",a),i.on("viewportChange",o),a(i))}),t.commands.toMatchingTag=function(e){var a=t.findMatchingTag(e,e.getCursor());if(a){var o="close"==a.at?a.open:a.close;o&&e.extendSelection(o.to,o.from)}}}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/edit/xml.js: -------------------------------------------------------------------------------- 1 | !function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";var e={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},n={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,caseFold:!1};t.defineMode("xml",function(r,o){function a(t,e){function n(n){return e.tokenize=n,n(t,e)}var r=t.next();if("<"==r)return t.eat("!")?t.eat("[")?t.match("CDATA[")?n(u("atom","]]>")):null:t.match("--")?n(u("comment","--\x3e")):t.match("DOCTYPE",!0,!0)?(t.eatWhile(/[\w\._\-]/),n(d(1))):null:t.eat("?")?(t.eatWhile(/[\w\._\-]/),e.tokenize=u("meta","?>"),"meta"):(C=t.eat("/")?"closeTag":"openTag",e.tokenize=i,"tag bracket");if("&"==r){var o;return o=t.eat("#")?t.eat("x")?t.eatWhile(/[a-fA-F\d]/)&&t.eat(";"):t.eatWhile(/[\d]/)&&t.eat(";"):t.eatWhile(/[\w\.\-:]/)&&t.eat(";"),o?"atom":"error"}return t.eatWhile(/[^&<]/),null}function i(t,e){var n=t.next();if(">"==n||"/"==n&&t.eat(">"))return e.tokenize=a,C=">"==n?"endTag":"selfcloseTag","tag bracket";if("="==n)return C="equals",null;if("<"==n){e.tokenize=a,e.state=m,e.tagName=e.tagStart=null;var r=e.tokenize(t,e);return r?r+" tag error":"tag error"}return/[\'\"]/.test(n)?(e.tokenize=l(n),e.stringStartCol=t.column(),e.tokenize(t,e)):(t.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function l(t){var e=function(e,n){for(;!e.eol();)if(e.next()==t){n.tokenize=i;break}return"string"};return e.isInAttribute=!0,e}function u(t,e){return function(n,r){for(;!n.eol();){if(n.match(e)){r.tokenize=a;break}n.next()}return t}}function d(t){return function(e,n){for(var r;null!=(r=e.next());){if("<"==r)return n.tokenize=d(t+1),n.tokenize(e,n);if(">"==r){if(1==t){n.tokenize=a;break}return n.tokenize=d(t-1),n.tokenize(e,n)}}return"meta"}}function c(t,e,n){this.prev=t.context,this.tagName=e,this.indent=t.indented,this.startOfLine=n,(z.doNotIndent.hasOwnProperty(e)||t.context&&t.context.noIndent)&&(this.noIndent=!0)}function f(t){t.context&&(t.context=t.context.prev)}function s(t,e){for(var n;;){if(!t.context)return;if(n=t.context.tagName,!z.contextGrabbers.hasOwnProperty(n)||!z.contextGrabbers[n].hasOwnProperty(e))return;f(t)}}function m(t,e,n){return"openTag"==t?(n.tagStart=e.column(),g):"closeTag"==t?p:m}function g(t,e,n){return"word"==t?(n.tagName=e.current(),I="tag",b):(I="error",g)}function p(t,e,n){if("word"==t){var r=e.current();return n.context&&n.context.tagName!=r&&z.implicitlyClosed.hasOwnProperty(n.context.tagName)&&f(n),n.context&&n.context.tagName==r||!1===z.matchClosing?(I="tag",h):(I="tag error",x)}return I="error",x}function h(t,e,n){return"endTag"!=t?(I="error",h):(f(n),m)}function x(t,e,n){return I="error",h(t,e,n)}function b(t,e,n){if("word"==t)return I="attribute",k;if("endTag"==t||"selfcloseTag"==t){var r=n.tagName,o=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==t||z.autoSelfClosers.hasOwnProperty(r)?s(n,r):(s(n,r),n.context=new c(n,r,o==n.indented)),m}return I="error",b}function k(t,e,n){return"equals"==t?v:(z.allowMissing||(I="error"),b(t,e,n))}function v(t,e,n){return"string"==t?w:"word"==t&&z.allowUnquoted?(I="string",b):(I="error",b(t,e,n))}function w(t,e,n){return"string"==t?w:b(t,e,n)}var y=r.indentUnit,z={},N=o.htmlMode?e:n;for(var T in N)z[T]=N[T];for(var T in o)z[T]=o[T];var C,I;return a.isInText=!0,{startState:function(t){var e={tokenize:a,state:m,indented:t||0,tagName:null,tagStart:null,context:null};return null!=t&&(e.baseIndent=t),e},token:function(t,e){if(!e.tagName&&t.sol()&&(e.indented=t.indentation()),t.eatSpace())return null;C=null;var n=e.tokenize(t,e);return(n||C)&&"comment"!=n&&(I=null,e.state=e.state(C||n,t,e),I&&(n="error"==I?n+" error":I)),n},indent:function(e,n,r){var o=e.context;if(e.tokenize.isInAttribute)return e.tagStart==e.indented?e.stringStartCol+1:e.indented+y;if(o&&o.noIndent)return t.Pass;if(e.tokenize!=i&&e.tokenize!=a)return r?r.match(/^(\s*)/)[0].length:0;if(e.tagName)return!1!==z.multilineTagIndentPastTag?e.tagStart+e.tagName.length+2:e.tagStart+y*(z.multilineTagIndentFactor||1);if(z.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:z.htmlMode?"html":"xml",helperType:z.htmlMode?"html":"xml",skipAttribute:function(t){t.state==v&&(t.state=b)}}}),t.defineMIME("text/xml","xml"),t.defineMIME("application/xml","xml"),t.mimeModes.hasOwnProperty("text/html")||t.defineMIME("text/html",{name:"xml",htmlMode:!0})}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/fold/xml-fold.js: -------------------------------------------------------------------------------- 1 | !function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function n(e,n){return e.line-n.line||e.ch-n.ch}function t(e,n,t,i){this.line=n,this.ch=t,this.cm=e,this.text=e.getLine(n),this.min=i?Math.max(i.from,e.firstLine()):e.firstLine(),this.max=i?Math.min(i.to-1,e.lastLine()):e.lastLine()}function i(e,n){var t=e.cm.getTokenTypeAt(h(e.line,n));return t&&/\btag\b/.test(t)}function r(e){if(!(e.line>=e.max))return e.ch=0,e.text=e.cm.getLine(++e.line),!0}function u(e){if(!(e.line<=e.min))return e.text=e.cm.getLine(--e.line),e.ch=e.text.length,!0}function f(e){for(;;){var n=e.text.indexOf(">",e.ch);if(-1==n){if(r(e))continue;return}{if(i(e,n+1)){var t=e.text.lastIndexOf("/",n),u=t>-1&&!/\S/.test(e.text.slice(t+1,n));return e.ch=n+1,u?"selfClose":"regular"}e.ch=n+1}}}function o(e){for(;;){var n=e.ch?e.text.lastIndexOf("<",e.ch-1):-1;if(-1==n){if(u(e))continue;return}if(i(e,n+1)){x.lastIndex=n,e.ch=n;var t=x.exec(e.text);if(t&&t.index==n)return t}else e.ch=n}}function l(e){for(;;){x.lastIndex=e.ch;var n=x.exec(e.text);if(!n){if(r(e))continue;return}{if(i(e,n.index+1))return e.ch=n.index+n[0].length,n;e.ch=n.index+1}}}function c(e){for(;;){var n=e.ch?e.text.lastIndexOf(">",e.ch-1):-1;if(-1==n){if(u(e))continue;return}{if(i(e,n+1)){var t=e.text.lastIndexOf("/",n),r=t>-1&&!/\S/.test(e.text.slice(t+1,n));return e.ch=n+1,r?"selfClose":"regular"}e.ch=n}}}function a(e,n){for(var t=[];;){var i,r=l(e),u=e.line,o=e.ch-(r?r[0].length:0);if(!r||!(i=f(e)))return;if("selfClose"!=i)if(r[1]){for(var c=t.length-1;c>=0;--c)if(t[c]==r[2]){t.length=c;break}if(c<0&&(!n||n==r[2]))return{tag:r[2],from:h(u,o),to:h(e.line,e.ch)}}else t.push(r[2])}}function s(e,n){for(var t=[];;){var i=c(e);if(!i)return;if("selfClose"!=i){var r=e.line,u=e.ch,f=o(e);if(!f)return;if(f[1])t.push(f[2]);else{for(var l=t.length-1;l>=0;--l)if(t[l]==f[2]){t.length=l;break}if(l<0&&(!n||n==f[2]))return{tag:f[2],from:h(e.line,e.ch),to:h(r,u)}}}else o(e)}}var h=e.Pos,F="A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",x=new RegExp("<(/?)(["+F+"][A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD-:.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*)","g");e.registerHelper("fold","xml",function(e,n){for(var i=new t(e,n.line,0);;){var r,u=l(i);if(!u||i.line!=n.line||!(r=f(i)))return;if(!u[1]&&"selfClose"!=r){var o=h(i.line,i.ch),c=a(i,u[2]);return c&&{from:o,to:c.from}}}}),e.findMatchingTag=function(e,i,r){var u=new t(e,i.line,i.ch,r);if(-1!=u.text.indexOf(">")||-1!=u.text.indexOf("<")){var l=f(u),c=l&&h(u.line,u.ch),F=l&&o(u);if(l&&F&&!(n(u,i)>0)){var x={from:h(u.line,u.ch),to:c,tag:F[2]};return"selfClose"==l?{open:x,close:null,at:"open"}:F[1]?{open:s(u,F[2]),close:x,at:"close"}:(u=new t(e,c.line,c.ch,r),{open:x,close:a(u,F[2]),at:"open"})}}},e.findEnclosingTag=function(e,n,i,r){for(var u=new t(e,n.line,n.ch,i);;){var f=s(u,r);if(!f)break;var o=new t(e,n.line,n.ch,i),l=a(o,f.tag);if(l)return{open:f,close:l}}},e.scanForClosingTag=function(e,n,i,r){return a(new t(e,n.line,n.ch,r?{from:0,to:r}:null),i)}}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/hint/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 11 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 12 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 13 | border-radius: 3px; 14 | border: 1px solid silver; 15 | 16 | background: white; 17 | font-size: 90%; 18 | font-family: monospace; 19 | 20 | max-height: 20em; 21 | overflow-y: auto; 22 | } 23 | 24 | .CodeMirror-hint { 25 | margin: 0; 26 | padding: 0 4px; 27 | border-radius: 2px; 28 | white-space: pre; 29 | color: black; 30 | cursor: pointer; 31 | } 32 | 33 | li.CodeMirror-hint-active { 34 | background: #08f; 35 | color: white; 36 | } 37 | -------------------------------------------------------------------------------- /web/vendor/cm/addon/hint/show-hint.js: -------------------------------------------------------------------------------- 1 | !function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";function i(t,i){this.cm=t,this.options=i,this.widget=null,this.debounce=0,this.tick=0,this.startPos=this.cm.getCursor("start"),this.startLen=this.cm.getLine(this.startPos.line).length-this.cm.getSelection().length;var e=this;t.on("cursorActivity",this.activityFunc=function(){e.cursorActivity()})}function e(i,e){return t.cmpPos(e.from,i.from)>0&&i.to.ch-i.from.ch!=e.to.ch-e.from.ch}function n(t,i,e){var n=t.options.hintOptions,o={};for(var s in m)o[s]=m[s];if(n)for(var s in n)void 0!==n[s]&&(o[s]=n[s]);if(e)for(var s in e)void 0!==e[s]&&(o[s]=e[s]);return o.hint.resolve&&(o.hint=o.hint.resolve(t,i)),o}function o(t){return"string"==typeof t?t:t.text}function s(t,i){function e(t,e){var o;o="string"!=typeof e?function(t){return e(t,i)}:n.hasOwnProperty(e)?n[e]:e,s[t]=o}var n={Up:function(){i.moveFocus(-1)},Down:function(){i.moveFocus(1)},PageUp:function(){i.moveFocus(1-i.menuSize(),!0)},PageDown:function(){i.moveFocus(i.menuSize()-1,!0)},Home:function(){i.setFocus(0)},End:function(){i.setFocus(i.length-1)},Enter:i.pick,Tab:i.pick,Esc:i.close},o=t.options.customKeys,s=o?{}:n;if(o)for(var c in o)o.hasOwnProperty(c)&&e(c,o[c]);var r=t.options.extraKeys;if(r)for(var c in r)r.hasOwnProperty(c)&&e(c,r[c]);return s}function c(t,i){for(;i&&i!=t;){if("LI"===i.nodeName.toUpperCase()&&i.parentNode==t)return i;i=i.parentNode}}function r(i,e){this.completion=i,this.data=e,this.picked=!1;var n=this,r=i.cm,h=this.hints=document.createElement("ul");h.className="CodeMirror-hints",this.selectedHint=e.selectedHint||0;for(var l=e.list,a=0;ah.clientHeight+1,A=r.getScrollInfo();if(b>0){var S=C.bottom-C.top;if(g.top-(g.bottom-C.top)-S>0)h.style.top=(y=g.top-S)+"px",w=!1;else if(S>k){h.style.height=k-5+"px",h.style.top=(y=g.bottom-C.top)+"px";var T=r.getCursor();e.from.ch!=T.ch&&(g=r.cursorCoords(T),h.style.left=(v=g.left)+"px",C=h.getBoundingClientRect())}}var M=C.right-H;if(M>0&&(C.right-C.left>H&&(h.style.width=H-5+"px",M-=C.right-C.left-H),h.style.left=(v=g.left-M)+"px"),x)for(var F=h.firstChild;F;F=F.nextSibling)F.style.paddingRight=r.display.nativeBarWidth+"px";if(r.addKeyMap(this.keyMap=s(i,{moveFocus:function(t,i){n.changeActive(n.selectedHint+t,i)},setFocus:function(t){n.changeActive(t)},menuSize:function(){return n.screenAmount()},length:l.length,close:function(){i.close()},pick:function(){n.pick()},data:e})),i.options.closeOnUnfocus){var N;r.on("blur",this.onBlur=function(){N=setTimeout(function(){i.close()},100)}),r.on("focus",this.onFocus=function(){clearTimeout(N)})}return r.on("scroll",this.onScroll=function(){var t=r.getScrollInfo(),e=r.getWrapperElement().getBoundingClientRect(),n=y+A.top-t.top,o=n-(window.pageYOffset||(document.documentElement||document.body).scrollTop);if(w||(o+=h.offsetHeight),o<=e.top||o>=e.bottom)return i.close();h.style.top=n+"px",h.style.left=v+A.left-t.left+"px"}),t.on(h,"dblclick",function(t){var i=c(h,t.target||t.srcElement);i&&null!=i.hintId&&(n.changeActive(i.hintId),n.pick())}),t.on(h,"click",function(t){var e=c(h,t.target||t.srcElement);e&&null!=e.hintId&&(n.changeActive(e.hintId),i.options.completeOnSingleClick&&n.pick())}),t.on(h,"mousedown",function(){setTimeout(function(){r.focus()},20)}),t.signal(e,"select",l[0],h.firstChild),!0}function h(t,i){if(!t.somethingSelected())return i;for(var e=[],n=0;n0?i(t):n(o+1)})}var s=h(t,o);n(0)};return s.async=!0,s.supportsSelection=!0,s}return(n=i.getHelper(i.getCursor(),"hintWords"))?function(i){return t.hint.fromList(i,{words:n})}:t.hint.anyword?function(i,e){return t.hint.anyword(i,e)}:function(){}}var u="CodeMirror-hint",f="CodeMirror-hint-active";t.showHint=function(t,i,e){if(!i)return t.showHint(e);e&&e.async&&(i.async=!0);var n={hint:i};if(e)for(var o in e)n[o]=e[o];return t.showHint(n)},t.defineExtension("showHint",function(e){e=n(this,this.getCursor("start"),e);var o=this.listSelections();if(!(o.length>1)){if(this.somethingSelected()){if(!e.hint.supportsSelection)return;for(var s=0;s=this.data.list.length?i=e?this.data.list.length-1:0:i<0&&(i=e?0:this.data.list.length-1),this.selectedHint!=i){var n=this.hints.childNodes[this.selectedHint];n.className=n.className.replace(" "+f,""),n=this.hints.childNodes[this.selectedHint=i],n.className+=" "+f,n.offsetTopthis.hints.scrollTop+this.hints.clientHeight&&(this.hints.scrollTop=n.offsetTop+n.offsetHeight-this.hints.clientHeight+3),t.signal(this.data,"select",this.data.list[this.selectedHint],n)}},screenAmount:function(){return Math.floor(this.hints.clientHeight/this.hints.firstChild.offsetHeight)||1}},t.registerHelper("hint","auto",{resolve:a}),t.registerHelper("hint","fromList",function(i,e){var n=i.getCursor(),o=i.getTokenAt(n),s=t.Pos(n.line,o.end);if(o.string&&/\w/.test(o.string[o.string.length-1]))var c=o.string,r=t.Pos(n.line,o.start);else var c="",r=s;for(var h=[],l=0;l,]/,closeOnUnfocus:!0,completeOnSingleClick:!0,container:null,customKeys:null,extraKeys:null};t.defineOption("hintOptions",null)}); -------------------------------------------------------------------------------- /web/vendor/cm/addon/mode/overlay.js: -------------------------------------------------------------------------------- 1 | !function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.overlayMode=function(o,r,a){return{startState:function(){return{base:e.startState(o),overlay:e.startState(r),basePos:0,baseCur:null,overlayPos:0,overlayCur:null,streamSeen:null}},copyState:function(a){return{base:e.copyState(o,a.base),overlay:e.copyState(r,a.overlay),basePos:a.basePos,baseCur:null,overlayPos:a.overlayPos,overlayCur:null}},token:function(e,n){return(e!=n.streamSeen||Math.min(n.basePos,n.overlayPos)=0&&(e.cachedArgHints=null);var a=i.changed;null==a&&(i.changed=a={from:o.from.line,to:o.from.line});var c=o.from.line+(o.text.length-1);o.from.line=a.to&&(a.to=c+1),a.from>o.from.line&&(a.from=o.from.line),t.lineCount()>L&&o.to-a.from>100&&setTimeout(function(){i.changed&&i.changed.to-i.changed.from>100&&r(e,i)},200)}function r(e,t){e.server.request({files:[{type:"full",name:t.name,text:S(e,t)}]},function(e){e?window.console.error(e):t.changed=null})}function s(t,n,o){t.request(n,{type:"completions",types:!0,docs:!0,urls:!0},function(i,r){if(i)return q(t,n,i);var s=[],c="",l=r.start,u=r.end;'["'==n.getRange(M(l.line,l.ch-2),l)&&'"]'!=n.getRange(u,M(u.line,u.ch+2))&&(c='"]');for(var f=0;f=d;--l){for(var h=n.getLine(l),g=0,m=0;;){var v=h.indexOf("\t",m);if(-1==v)break;g+=c-(v+g)%c-1,m=v+1}if(s=r.column-g,"("==h.charAt(s)){p=!0;break}}if(p){var y=M(l,s),C=t.cachedArgHints;if(C&&C.doc==n.getDoc()&&0==R(y,C.start))return u(t,n,a);t.request(n,{type:"type",preferFunction:!0,end:y},function(e,o){!e&&o.type&&/^fn\(/.test(o.type)&&(t.cachedArgHints={start:y,type:f(o.type),name:o.exprName||o.name||"fn",guess:o.guess,doc:n.getDoc()},u(t,n,a))})}}}}}function u(e,t,n){H(e);for(var o=e.cachedArgHints,i=o.type,r=w("span",o.guess?j+"fhint-guess":null,w("span",j+"fname",o.name),"("),s=0;s ":")")),i.rettype&&r.appendChild(w("span",j+"type",i.rettype));var c=t.cursorCoords(null,"page");e.activeArgHints=D(c.right+1,c.bottom,r)}function f(e){var t=[],n=3;if(")"!=e.charAt(n))for(;;){var o=e.slice(n).match(/^([^, \(\[\{]+): /);if(o&&(n+=o[0].length,o=o[1]),t.push({name:o,type:function(t){for(var o=0,i=n;;){var r=e.charAt(n);if(t.test(r)&&!o)return e.slice(i,n);/[{\[\(]/.test(r)?++o:/[}\]\)]/.test(r)&&--o,++n}}(/[\),]/)}),")"==e.charAt(n))break;n+=2}var i=e.slice(n).match(/^\) -> (.*)$/);return{args:t,rettype:i&&i[1]}}function d(e,t){function o(o){var i={type:"definition",variable:o||null},r=n(e,t.getDoc());e.server.request(x(e,r,i),function(n,o){if(n)return q(e,t,n);if(!o.file&&o.url)return void window.open(o.url);if(o.file){var i,s=e.docs[o.file];if(s&&(i=g(s.doc,o)))return e.jumpStack.push({file:r.name,start:t.getCursor("from"),end:t.getCursor("to")}),void h(e,r,s,i.start,i.end)}q(e,t,"Could not find a definition.")})}m(t)?o():T(t,"Jump to variable",function(e){e&&o(e)})}function p(e,t){var o=e.jumpStack.pop(),i=o&&e.docs[o.file];i&&h(e,n(e,t.getDoc()),i,o.start,o.end)}function h(e,t,n,o,i){n.doc.setSelection(o,i),t!=n&&e.options.switchToDoc&&(H(e),e.options.switchToDoc(n.name,n.doc))}function g(e,t){for(var n=t.context.slice(0,t.contextOffset).split("\n"),o=t.start.line-(n.length-1),i=M(o,(1==n.length?t.start.ch:e.getLine(o).length)-n[0].length),r=e.getLine(o).slice(i.ch),s=o+1;s=0&&R(a,l.end)<=0&&(s=r.length-1))}t.setSelections(r,s)})}function C(e,t){for(var n=Object.create(null),o=0;oL&&!1!==s&&t.changed.to-t.changed.from<100&&t.changed.from<=a.line&&t.changed.to>n.end.line){i.push(b(t,a,n.end)),n.file="#0";var r=i[0].offsetLines;null!=n.start&&(n.start=M(n.start.line- -r,n.start.ch)),n.end=M(n.end.line-r,n.end.ch)}else i.push({type:"full",name:t.name,text:S(e,t)}),n.file=t.name,t.changed=null;else n.file=t.name;for(var c in e.docs){var l=e.docs[c];l.changed&&l!=t&&(i.push({type:"full",name:l.name,text:S(e,l)}),l.changed=null)}return{query:n,files:i}}function b(t,n,o){for(var i,r=t.doc,s=null,a=null,c=n.line-1,l=Math.max(0,c-50);c>=l;--c){var u=r.getLine(c);if(!(u.search(/\bfunction\b/)<0)){var f=e.countColumn(u,null,4);null!=s&&s<=f||(s=f,a=c)}}null==a&&(a=l);var d=Math.min(r.lastLine(),o.line+20);if(null==s||s==e.countColumn(r.getLine(n.line),null,4))i=d;else for(i=o.line+1;i",n):n(prompt(t,""))}function k(t,n,o){function i(){l=!0,c||r()}function r(){t.state.ternTooltip=null,a.parentNode&&(t.off("cursorActivity",r),t.off("blur",r),t.off("scroll",r),N(a))}t.state.ternTooltip&&A(t.state.ternTooltip);var s=t.cursorCoords(),a=t.state.ternTooltip=D(s.right+1,s.bottom,n),c=!1,l=!1;e.on(a,"mousemove",function(){c=!0}),e.on(a,"mouseout",function(t){e.contains(a,t.relatedTarget||t.toElement)||(l?r():c=!1)}),setTimeout(i,o.options.hintDelay?o.options.hintDelay:1700),t.on("cursorActivity",r),t.on("blur",r),t.on("scroll",r)}function D(e,t,n){var o=w("div",j+"tooltip",n);return o.style.left=e+"px",o.style.top=t+"px",document.body.appendChild(o),o}function A(e){var t=e&&e.parentNode;t&&t.removeChild(e)}function N(e){e.style.opacity="0",setTimeout(function(){A(e)},1100)}function q(e,t,n){e.options.showError?e.options.showError(t,n):k(t,String(n),e)}function H(e){e.activeArgHints&&(A(e.activeArgHints),e.activeArgHints=null)}function S(e,t){var n=t.doc.getValue();return e.options.fileFilter&&(n=e.options.fileFilter(n,t.name,t.doc)),n}function F(e){function n(e,t){t&&(e.id=++i,r[i]=t),o.postMessage(e)}var o=e.worker=new Worker(e.options.workerScript);o.postMessage({type:"init",defs:e.options.defs,plugins:e.options.plugins,scripts:e.options.workerDeps});var i=0,r={};o.onmessage=function(o){var i=o.data;"getFile"==i.type?t(e,i.name,function(e,t){n({type:"getFile",err:String(e),text:t,id:i.id})}):"debug"==i.type?window.console.log(i.message):i.id&&r[i.id]&&(r[i.id](i.err,i.body),delete r[i.id])},o.onerror=function(e){for(var t in r)r[t](e);r={}},this.addFile=function(e,t){n({type:"add",name:e,text:t})},this.delFile=function(e){n({type:"del",name:e})},this.request=function(e,t){n({type:"req",body:e},t)}}e.TernServer=function(e){var n=this;this.options=e||{};var o=this.options.plugins||(this.options.plugins={});o.doc_comment||(o.doc_comment=!0),this.docs=Object.create(null),this.options.useWorker?this.server=new F(this):this.server=new tern.Server({getFile:function(e,o){return t(n,e,o)},async:!0,defs:this.options.defs||[],plugins:o}),this.trackChange=function(e,t){i(n,e,t)},this.cachedArgHints=null,this.activeArgHints=null,this.jumpStack=[],this.getHint=function(e,t){return s(n,e,t)},this.getHint.async=!0},e.TernServer.prototype={addDoc:function(t,n){var o={doc:n,name:t,changed:null};return this.server.addFile(t,S(this,o)),e.on(n,"change",this.trackChange),this.docs[t]=o},delDoc:function(t){var n=o(this,t);n&&(e.off(n.doc,"change",this.trackChange),delete this.docs[n.name],this.server.delFile(n.name))},hideDoc:function(e){H(this);var t=o(this,e);t&&t.changed&&r(this,t)},complete:function(e){e.showHint({hint:this.getHint})},showType:function(e,t,n){c(this,e,t,"type",n)},showDocs:function(e,t,n){c(this,e,t,"documentation",n)},updateArgHints:function(e){l(this,e)},jumpToDef:function(e){d(this,e)},jumpBack:function(e){p(this,e)},rename:function(e){v(this,e)},selectName:function(e){y(this,e)},request:function(e,t,o,i){var r=this,s=n(this,e.getDoc()),a=x(this,s,t,i),c=a.query&&this.options.queryOptions&&this.options.queryOptions[a.query.type];if(c)for(var l in c)a.query[l]=c[l];this.server.request(a,function(e,n){!e&&r.options.responseFilter&&(n=r.options.responseFilter(s,t,a,e,n)),o(e,n)})},destroy:function(){H(this),this.worker&&(this.worker.terminate(),this.worker=null)}};var M=e.Pos,j="CodeMirror-Tern-",L=250,O=0,R=e.cmpPos}); -------------------------------------------------------------------------------- /web/vendor/cm/lib/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | border-right: none; 47 | width: 0; 48 | } 49 | /* Shown when moving in bi-directional text */ 50 | .CodeMirror div.CodeMirror-secondarycursor { 51 | border-left: 1px solid silver; 52 | } 53 | .cm-fat-cursor .CodeMirror-cursor { 54 | width: auto; 55 | border: 0 !important; 56 | background: #7e7; 57 | } 58 | .cm-fat-cursor div.CodeMirror-cursors { 59 | z-index: 1; 60 | } 61 | 62 | .cm-animate-fat-cursor { 63 | width: auto; 64 | border: 0; 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | background-color: #7e7; 69 | } 70 | @-moz-keyframes blink { 71 | 0% {} 72 | 50% { background-color: transparent; } 73 | 100% {} 74 | } 75 | @-webkit-keyframes blink { 76 | 0% {} 77 | 50% { background-color: transparent; } 78 | 100% {} 79 | } 80 | @keyframes blink { 81 | 0% {} 82 | 50% { background-color: transparent; } 83 | 100% {} 84 | } 85 | 86 | /* Can style cursor different in overwrite (non-insert) mode */ 87 | .CodeMirror-overwrite .CodeMirror-cursor {} 88 | 89 | .cm-tab { display: inline-block; text-decoration: inherit; } 90 | 91 | .CodeMirror-rulers { 92 | position: absolute; 93 | left: 0; right: 0; top: -50px; bottom: -20px; 94 | overflow: hidden; 95 | } 96 | .CodeMirror-ruler { 97 | border-left: 1px solid #ccc; 98 | top: 0; bottom: 0; 99 | position: absolute; 100 | } 101 | 102 | /* DEFAULT THEME */ 103 | 104 | .cm-s-default .cm-header {color: blue;} 105 | .cm-s-default .cm-quote {color: #090;} 106 | .cm-negative {color: #d44;} 107 | .cm-positive {color: #292;} 108 | .cm-header, .cm-strong {font-weight: bold;} 109 | .cm-em {font-style: italic;} 110 | .cm-link {text-decoration: underline;} 111 | .cm-strikethrough {text-decoration: line-through;} 112 | 113 | .cm-s-default .cm-keyword {color: #708;} 114 | .cm-s-default .cm-atom {color: #219;} 115 | .cm-s-default .cm-number {color: #164;} 116 | .cm-s-default .cm-def {color: #00f;} 117 | .cm-s-default .cm-variable, 118 | .cm-s-default .cm-punctuation, 119 | .cm-s-default .cm-property, 120 | .cm-s-default .cm-operator {} 121 | .cm-s-default .cm-variable-2 {color: #05a;} 122 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 123 | .cm-s-default .cm-comment {color: #a50;} 124 | .cm-s-default .cm-string {color: #a11;} 125 | .cm-s-default .cm-string-2 {color: #f50;} 126 | .cm-s-default .cm-meta {color: #555;} 127 | .cm-s-default .cm-qualifier {color: #555;} 128 | .cm-s-default .cm-builtin {color: #30a;} 129 | .cm-s-default .cm-bracket {color: #997;} 130 | .cm-s-default .cm-tag {color: #170;} 131 | .cm-s-default .cm-attribute {color: #00c;} 132 | .cm-s-default .cm-hr {color: #999;} 133 | .cm-s-default .cm-link {color: #00c;} 134 | 135 | .cm-s-default .cm-error {color: #f00;} 136 | .cm-invalidchar {color: #f00;} 137 | 138 | .CodeMirror-composing { border-bottom: 2px solid; } 139 | 140 | /* Default styles for common addons */ 141 | 142 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 143 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 144 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 145 | .CodeMirror-activeline-background {background: #e8f2ff;} 146 | 147 | /* STOP */ 148 | 149 | /* The rest of this file contains styles related to the mechanics of 150 | the editor. You probably shouldn't touch them. */ 151 | 152 | .CodeMirror { 153 | position: relative; 154 | overflow: hidden; 155 | background: white; 156 | } 157 | 158 | .CodeMirror-scroll { 159 | overflow: scroll !important; /* Things will break if this is overridden */ 160 | /* 30px is the magic margin used to hide the element's real scrollbars */ 161 | /* See overflow: hidden in .CodeMirror */ 162 | margin-bottom: -30px; margin-right: -30px; 163 | padding-bottom: 30px; 164 | height: 100%; 165 | outline: none; /* Prevent dragging from highlighting the element */ 166 | position: relative; 167 | } 168 | .CodeMirror-sizer { 169 | position: relative; 170 | border-right: 30px solid transparent; 171 | } 172 | 173 | /* The fake, visible scrollbars. Used to force redraw during scrolling 174 | before actual scrolling happens, thus preventing shaking and 175 | flickering artifacts. */ 176 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 177 | position: absolute; 178 | z-index: 6; 179 | display: none; 180 | } 181 | .CodeMirror-vscrollbar { 182 | right: 0; top: 0; 183 | overflow-x: hidden; 184 | overflow-y: scroll; 185 | } 186 | .CodeMirror-hscrollbar { 187 | bottom: 0; left: 0; 188 | overflow-y: hidden; 189 | overflow-x: scroll; 190 | } 191 | .CodeMirror-scrollbar-filler { 192 | right: 0; bottom: 0; 193 | } 194 | .CodeMirror-gutter-filler { 195 | left: 0; bottom: 0; 196 | } 197 | 198 | .CodeMirror-gutters { 199 | position: absolute; left: 0; top: 0; 200 | min-height: 100%; 201 | z-index: 3; 202 | } 203 | .CodeMirror-gutter { 204 | white-space: normal; 205 | height: 100%; 206 | display: inline-block; 207 | vertical-align: top; 208 | margin-bottom: -30px; 209 | } 210 | .CodeMirror-gutter-wrapper { 211 | position: absolute; 212 | z-index: 4; 213 | background: none !important; 214 | border: none !important; 215 | } 216 | .CodeMirror-gutter-background { 217 | position: absolute; 218 | top: 0; bottom: 0; 219 | z-index: 4; 220 | } 221 | .CodeMirror-gutter-elt { 222 | position: absolute; 223 | cursor: default; 224 | z-index: 4; 225 | } 226 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 227 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 228 | 229 | .CodeMirror-lines { 230 | cursor: text; 231 | min-height: 1px; /* prevents collapsing before first draw */ 232 | } 233 | .CodeMirror pre { 234 | /* Reset some styles that the rest of the page might have set */ 235 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 236 | border-width: 0; 237 | background: transparent; 238 | font-family: inherit; 239 | font-size: inherit; 240 | margin: 0; 241 | white-space: pre; 242 | word-wrap: normal; 243 | line-height: inherit; 244 | color: inherit; 245 | z-index: 2; 246 | position: relative; 247 | overflow: visible; 248 | -webkit-tap-highlight-color: transparent; 249 | -webkit-font-variant-ligatures: contextual; 250 | font-variant-ligatures: contextual; 251 | } 252 | .CodeMirror-wrap pre { 253 | word-wrap: break-word; 254 | white-space: pre-wrap; 255 | word-break: normal; 256 | } 257 | 258 | .CodeMirror-linebackground { 259 | position: absolute; 260 | left: 0; right: 0; top: 0; bottom: 0; 261 | z-index: 0; 262 | } 263 | 264 | .CodeMirror-linewidget { 265 | position: relative; 266 | z-index: 2; 267 | overflow: auto; 268 | } 269 | 270 | .CodeMirror-widget {} 271 | 272 | .CodeMirror-rtl pre { direction: rtl; } 273 | 274 | .CodeMirror-code { 275 | outline: none; 276 | } 277 | 278 | /* Force content-box sizing for the elements where we expect it */ 279 | .CodeMirror-scroll, 280 | .CodeMirror-sizer, 281 | .CodeMirror-gutter, 282 | .CodeMirror-gutters, 283 | .CodeMirror-linenumber { 284 | -moz-box-sizing: content-box; 285 | box-sizing: content-box; 286 | } 287 | 288 | .CodeMirror-measure { 289 | position: absolute; 290 | width: 100%; 291 | height: 0; 292 | overflow: hidden; 293 | visibility: hidden; 294 | } 295 | 296 | .CodeMirror-cursor { 297 | position: absolute; 298 | pointer-events: none; 299 | } 300 | .CodeMirror-measure pre { position: static; } 301 | 302 | div.CodeMirror-cursors { 303 | visibility: hidden; 304 | position: relative; 305 | z-index: 3; 306 | } 307 | div.CodeMirror-dragcursors { 308 | visibility: visible; 309 | } 310 | 311 | .CodeMirror-focused div.CodeMirror-cursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-selected { background: #d9d9d9; } 316 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 317 | .CodeMirror-crosshair { cursor: crosshair; } 318 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 319 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 320 | 321 | .cm-searching { 322 | background: #ffa; 323 | background: rgba(255, 255, 0, .4); 324 | } 325 | 326 | /* Used to force a border model for a node */ 327 | .cm-force-border { padding-right: .1px; } 328 | 329 | @media print { 330 | /* Hide the cursor when printing */ 331 | .CodeMirror div.CodeMirror-cursors { 332 | visibility: hidden; 333 | } 334 | } 335 | 336 | /* See issue #2901 */ 337 | .cm-tab-wrap-hack:after { content: ''; } 338 | 339 | /* Help users use markselection to safely style text background */ 340 | span.CodeMirror-selectedtext { background: none; } 341 | -------------------------------------------------------------------------------- /web/vendor/cm/mode/xml/xml.js: -------------------------------------------------------------------------------- 1 | !function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],t):t(CodeMirror)}(function(t){"use strict";var e={autoSelfClosers:{area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},implicitlyClosed:{dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},contextGrabbers:{dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}},doNotIndent:{pre:!0},allowUnquoted:!0,allowMissing:!0,caseFold:!0},n={autoSelfClosers:{},implicitlyClosed:{},contextGrabbers:{},doNotIndent:{},allowUnquoted:!1,allowMissing:!1,caseFold:!1};t.defineMode("xml",function(r,o){function a(t,e){function n(n){return e.tokenize=n,n(t,e)}var r=t.next();if("<"==r)return t.eat("!")?t.eat("[")?t.match("CDATA[")?n(u("atom","]]>")):null:t.match("--")?n(u("comment","--\x3e")):t.match("DOCTYPE",!0,!0)?(t.eatWhile(/[\w\._\-]/),n(d(1))):null:t.eat("?")?(t.eatWhile(/[\w\._\-]/),e.tokenize=u("meta","?>"),"meta"):(C=t.eat("/")?"closeTag":"openTag",e.tokenize=i,"tag bracket");if("&"==r){var o;return o=t.eat("#")?t.eat("x")?t.eatWhile(/[a-fA-F\d]/)&&t.eat(";"):t.eatWhile(/[\d]/)&&t.eat(";"):t.eatWhile(/[\w\.\-:]/)&&t.eat(";"),o?"atom":"error"}return t.eatWhile(/[^&<]/),null}function i(t,e){var n=t.next();if(">"==n||"/"==n&&t.eat(">"))return e.tokenize=a,C=">"==n?"endTag":"selfcloseTag","tag bracket";if("="==n)return C="equals",null;if("<"==n){e.tokenize=a,e.state=m,e.tagName=e.tagStart=null;var r=e.tokenize(t,e);return r?r+" tag error":"tag error"}return/[\'\"]/.test(n)?(e.tokenize=l(n),e.stringStartCol=t.column(),e.tokenize(t,e)):(t.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function l(t){var e=function(e,n){for(;!e.eol();)if(e.next()==t){n.tokenize=i;break}return"string"};return e.isInAttribute=!0,e}function u(t,e){return function(n,r){for(;!n.eol();){if(n.match(e)){r.tokenize=a;break}n.next()}return t}}function d(t){return function(e,n){for(var r;null!=(r=e.next());){if("<"==r)return n.tokenize=d(t+1),n.tokenize(e,n);if(">"==r){if(1==t){n.tokenize=a;break}return n.tokenize=d(t-1),n.tokenize(e,n)}}return"meta"}}function c(t,e,n){this.prev=t.context,this.tagName=e,this.indent=t.indented,this.startOfLine=n,(z.doNotIndent.hasOwnProperty(e)||t.context&&t.context.noIndent)&&(this.noIndent=!0)}function f(t){t.context&&(t.context=t.context.prev)}function s(t,e){for(var n;;){if(!t.context)return;if(n=t.context.tagName,!z.contextGrabbers.hasOwnProperty(n)||!z.contextGrabbers[n].hasOwnProperty(e))return;f(t)}}function m(t,e,n){return"openTag"==t?(n.tagStart=e.column(),g):"closeTag"==t?p:m}function g(t,e,n){return"word"==t?(n.tagName=e.current(),I="tag",b):(I="error",g)}function p(t,e,n){if("word"==t){var r=e.current();return n.context&&n.context.tagName!=r&&z.implicitlyClosed.hasOwnProperty(n.context.tagName)&&f(n),n.context&&n.context.tagName==r||!1===z.matchClosing?(I="tag",h):(I="tag error",x)}return I="error",x}function h(t,e,n){return"endTag"!=t?(I="error",h):(f(n),m)}function x(t,e,n){return I="error",h(t,e,n)}function b(t,e,n){if("word"==t)return I="attribute",k;if("endTag"==t||"selfcloseTag"==t){var r=n.tagName,o=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==t||z.autoSelfClosers.hasOwnProperty(r)?s(n,r):(s(n,r),n.context=new c(n,r,o==n.indented)),m}return I="error",b}function k(t,e,n){return"equals"==t?v:(z.allowMissing||(I="error"),b(t,e,n))}function v(t,e,n){return"string"==t?w:"word"==t&&z.allowUnquoted?(I="string",b):(I="error",b(t,e,n))}function w(t,e,n){return"string"==t?w:b(t,e,n)}var y=r.indentUnit,z={},N=o.htmlMode?e:n;for(var T in N)z[T]=N[T];for(var T in o)z[T]=o[T];var C,I;return a.isInText=!0,{startState:function(t){var e={tokenize:a,state:m,indented:t||0,tagName:null,tagStart:null,context:null};return null!=t&&(e.baseIndent=t),e},token:function(t,e){if(!e.tagName&&t.sol()&&(e.indented=t.indentation()),t.eatSpace())return null;C=null;var n=e.tokenize(t,e);return(n||C)&&"comment"!=n&&(I=null,e.state=e.state(C||n,t,e),I&&(n="error"==I?n+" error":I)),n},indent:function(e,n,r){var o=e.context;if(e.tokenize.isInAttribute)return e.tagStart==e.indented?e.stringStartCol+1:e.indented+y;if(o&&o.noIndent)return t.Pass;if(e.tokenize!=i&&e.tokenize!=a)return r?r.match(/^(\s*)/)[0].length:0;if(e.tagName)return!1!==z.multilineTagIndentPastTag?e.tagStart+e.tagName.length+2:e.tagStart+y*(z.multilineTagIndentFactor||1);if(z.alignCDATA&&/$/,blockCommentStart:"\x3c!--",blockCommentEnd:"--\x3e",configuration:z.htmlMode?"html":"xml",helperType:z.htmlMode?"html":"xml",skipAttribute:function(t){t.state==v&&(t.state=b)}}}),t.defineMIME("text/xml","xml"),t.defineMIME("application/xml","xml"),t.mimeModes.hasOwnProperty("text/html")||t.defineMIME("text/html",{name:"xml",htmlMode:!0})}); -------------------------------------------------------------------------------- /web/vendor/cm/theme/rubyblue.css: -------------------------------------------------------------------------------- 1 | .cm-s-rubyblue.CodeMirror { background: #112435; color: white; } 2 | .cm-s-rubyblue div.CodeMirror-selected { background: #38566F; } 3 | .cm-s-rubyblue .CodeMirror-line::selection, .cm-s-rubyblue .CodeMirror-line > span::selection, .cm-s-rubyblue .CodeMirror-line > span > span::selection { background: rgba(56, 86, 111, 0.99); } 4 | .cm-s-rubyblue .CodeMirror-line::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span::-moz-selection, .cm-s-rubyblue .CodeMirror-line > span > span::-moz-selection { background: rgba(56, 86, 111, 0.99); } 5 | .cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } 6 | .cm-s-rubyblue .CodeMirror-guttermarker { color: white; } 7 | .cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087; } 8 | .cm-s-rubyblue .CodeMirror-linenumber { color: white; } 9 | .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white; } 10 | 11 | .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; } 12 | .cm-s-rubyblue span.cm-atom { color: #F4C20B; } 13 | .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; } 14 | .cm-s-rubyblue span.cm-keyword { color: #F0F; } 15 | .cm-s-rubyblue span.cm-string { color: #F08047; } 16 | .cm-s-rubyblue span.cm-meta { color: #F0F; } 17 | .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; } 18 | .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def, .cm-s-rubyblue span.cm-type { color: white; } 19 | .cm-s-rubyblue span.cm-bracket { color: #F0F; } 20 | .cm-s-rubyblue span.cm-link { color: #F4C20B; } 21 | .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; } 22 | .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; } 23 | .cm-s-rubyblue span.cm-error { color: #AF2018; } 24 | 25 | .cm-s-rubyblue .CodeMirror-activeline-background { background: #173047; } 26 | -------------------------------------------------------------------------------- /web/vendor/styles.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Roboto; 3 | src: url("../fonts/roboto/Roboto-Regular.eot"); 4 | src: local("Roboto Regular"), local("Roboto-Regular"), url("../fonts/roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"), url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("../fonts/roboto/Roboto-Regular.woff") format("woff"), url("../fonts/roboto/Roboto-Regular.ttf") format("truetype"), url("../fonts/roboto/Roboto-Regular.svg#Roboto") format("svg"); 5 | font-weight: 400; 6 | font-style: normal; 7 | } 8 | 9 | @import "../bower_components/selectize/dist/css/selectize.default"; 10 | @import "../bower_components/angular-ui-select/dist/select"; 11 | @import "../assets/styles/reset.scss"; 12 | @import "../bower_components/bootstrap-sass/assets/stylesheets/bootstrap.scss"; 13 | @import "../assets/styles/[^reset]*.scss"; 14 | --------------------------------------------------------------------------------