├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.coffee ├── INSTALL.md ├── LICENSE ├── README.md ├── WELCOME.md ├── bower.json ├── deploy-s3.sh ├── deploy-to-appengine.sh ├── droneshare.sublime-project ├── megadroneshare.sublime-project ├── middleware.coffee ├── package.json ├── protractor.conf.coffee ├── protractor.saucelabs.conf.coffee ├── routes.coffee ├── src ├── helpers │ ├── clickjacking.html │ ├── facebook-share.html │ ├── google-plus.html │ └── twitter-share.html ├── images │ ├── 3drobotics.png │ ├── droneshare.png │ ├── fb-share.jpg │ ├── google-plus-share.jpg │ ├── icon.png │ ├── octocat-drone.jpg │ ├── screenshot.png │ ├── twitter-share.jpg │ ├── vehicle-marker-active.png │ └── vehicle-marker-inactive.png ├── index.html ├── scripts │ ├── backend │ │ ├── gitHubBackend.coffee.disabled │ │ ├── peopleBackend.coffee.disabled │ │ └── viewsBackend.coffee.disabled │ ├── controllers │ │ ├── aboutController.coffee │ │ ├── adminController.coffee │ │ ├── dapiControllers.coffee │ │ ├── detailControllers.coffee │ │ └── mapController.coffee │ ├── custom │ │ ├── angular-atmosphere.js │ │ ├── angular-leaflet-directive.js │ │ └── atmosphere.js │ ├── directives │ │ ├── accessCodeDropdown.coffee │ │ ├── liveMap.coffee │ │ ├── mapboxStaticMap.coffee │ │ ├── missionList.coffee │ │ ├── missionPlot.coffee │ │ ├── missionSummary.coffee │ │ ├── navbarDirective.coffee │ │ ├── tab.coffee │ │ ├── tabs.coffee │ │ ├── uploadMission.coffee │ │ ├── userSummary.coffee │ │ └── vehicleSummary.coffee │ ├── filters │ │ ├── capitalize.coffee │ │ ├── duration.coffee │ │ ├── toMapIcon.coffee │ │ └── twitterfy.coffee │ ├── interceptors │ │ └── dispatcher.coffee │ ├── libs │ │ ├── ladda.js │ │ ├── mapbox.js │ │ ├── spin.js │ │ ├── ui-bootstrap-0.11.0.js │ │ └── ui-bootstrap-tpls-0.11.0.js │ ├── loadingIndicator.coffee │ ├── routes.coffee │ └── services │ │ └── dapiServices.coffee ├── styles │ ├── ladda-themeless.css │ ├── mapbox.css │ └── styles.less ├── template │ ├── accordion │ │ ├── accordion-group.html │ │ └── accordion.html │ ├── alert │ │ └── alert.html │ ├── carousel │ │ ├── carousel.html │ │ └── slide.html │ ├── datepicker │ │ ├── datepicker.html │ │ ├── day.html │ │ ├── month.html │ │ ├── popup.html │ │ └── year.html │ ├── modal │ │ ├── backdrop.html │ │ └── window.html │ ├── pagination │ │ ├── pager.html │ │ └── pagination.html │ ├── popover │ │ └── popover.html │ ├── progressbar │ │ ├── bar.html │ │ ├── progress.html │ │ └── progressbar.html │ ├── rating │ │ └── rating.html │ ├── tabs │ │ ├── tab.html │ │ └── tabset.html │ ├── timepicker │ │ └── timepicker.html │ ├── tooltip │ │ ├── tooltip-html-unsafe-popup.html │ │ └── tooltip-popup.html │ └── typeahead │ │ ├── typeahead-match.html │ │ └── typeahead-popup.html └── views │ ├── about.html │ ├── admin-screen.html │ ├── body.html │ ├── directives │ ├── access-code-dropdown.html │ ├── alert-modal.html │ ├── featured-flights.html │ ├── live-map.html │ ├── mission-list.html │ ├── mission-plot.html │ ├── mission-summary.html │ ├── navbar.html │ ├── recent-flights.html │ ├── tab.html │ ├── tabs.html │ ├── upload-mission.html │ ├── user-summary.html │ └── vehicle-summary.html │ ├── footer.html │ ├── login │ ├── create-snippet.html │ ├── email-confirm.html │ ├── login-snippet.html │ ├── login-window.html │ ├── logout.html │ ├── password-reset-confirm.html │ ├── password-reset.html │ └── user-create.html │ ├── mission │ ├── analysis-modal.html │ ├── analysis-snippet.html │ ├── analysis-window.html │ ├── detail-window.html │ ├── doarama-modal.html │ ├── doarama-snippet.html │ ├── doarama-window.html │ ├── list-window.html │ ├── parameters-modal.html │ ├── parameters-snippet.html │ ├── parameters-window.html │ └── plot-window.html │ ├── search-history.html │ ├── site.html │ ├── snippets │ └── alert-header.html │ ├── user │ ├── detail.html │ ├── list-all.html │ ├── vehicle-list-modal.html │ └── vehicle-modal.html │ ├── vehicle-detail.html │ └── vehicle-list.html └── test ├── e2e └── layout │ └── header.coffee ├── fixtures ├── atmosphere.att.json ├── atmosphere.delete.json ├── atmosphere.mystery.json ├── atmosphere.start.json ├── atmosphere.stop.json ├── atmosphere.update.json ├── dseries.json ├── messages.geo.json ├── mission.json ├── missions.json ├── parameters.json ├── staticmap.json ├── user.json └── vehicle.json └── units ├── controllers ├── liveMapController.coffee ├── mapController.coffee ├── missionDetailController.coffee ├── missionPlotController.coffee ├── userDetailController.coffee └── vehicleDetailController.coffee ├── filters └── twitterfySpec.coffee └── services ├── authService.coffee └── missionService.coffee /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": ".temp/bower_components" 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | tab_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.coffee] 14 | indent_size = 2 15 | 16 | [*.js] 17 | indent_size = 2 18 | 19 | [*.json] 20 | indent_size = 2 21 | 22 | # [*.md] 23 | # trim_trailing_whitespace = false 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.sln text eol=crlf 11 | 12 | # Denote all files that are truly binary and should not be modified. 13 | *.png binary 14 | *.jpg binary 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .temp/ 3 | bower_components/ 4 | dist/ 5 | node_modules/ 6 | npm-debug.log 7 | test-results.xml 8 | *.sublime-workspace 9 | .vimrc 10 | s3bucket-policy.json 11 | s3website.json 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | notifications: 5 | email: 6 | on_success: always -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Droneshare 2 | We **<3 pull requests**, this is a short guide on how to write a succesful pull request for this project. 3 | 4 | ### Fork the project, then clone the repo 5 | 6 | ``` 7 | git clone git@github.com:YOUR-USERNAME/droneshare.git 8 | ``` 9 | 10 | make sure your environment is properly setup and navigate to the folder where you just cloned the repo 11 | 12 | ### Branch out of master 13 | 14 | Always branch out of master unless you are targetting a specific branch for a release 15 | 16 | ``` 17 | git checkout -b descriptive_branch_name 18 | ``` 19 | 20 | and make sure you use a descriptive branch name 21 | 22 | ### Commit your changes 23 | 24 | Always write descriptive commit messages and add a fixes or relates note to them with an issue number 25 | 26 | **Example:** 27 | 28 | ``` 29 | Change how the main controller works 30 | 31 | - changed service calls 32 | - added new views for specific actions 33 | - removed directive only code 34 | 35 | Fixes #1 36 | ``` 37 | 38 | ### Push your changes 39 | 40 | Push changes to your repo and send a [pull request](https://github.com/diydrones/droneshare/compare/) 41 | 42 | From here it's on us, we will reply with suggestions changes or improvements, we will try to reply as fast as we can. -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing Droneshare 2 | 3 | This document is mostly assuming you are installing to the 3DR version of droneshare. If you are forking droneshare for your own purposes, then some of these commands may need your personal credentials. 4 | 5 | Droneshare is (currently) an entirely Angular client side application. So deployment is mostly a matter of finding a suitable static file server. 6 | 7 | # Deploying 8 | 9 | ## to Appengine 10 | 11 | See deploy-to-appengine.sh for example. 12 | 13 | ## to Amazon S3 14 | 15 | * ```pip install awscli``` 16 | * ```aws configure``` - and then enter your AWS credentials 17 | * [Configure aws](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) 18 | * complete -C aws_completer aws 19 | * ```deploy-to-s3.sh``` 20 | 21 | # Running 22 | 23 | 24 | ## Requirements 25 | 26 | - [Nodejs](http://nodejs.org/) 27 | - [npm](https://www.npmjs.org/) 28 | - [grunt](http://gruntjs.com/) (Install globaly: ```npm install -g grunt-cli```) 29 | 30 | ## Installing 31 | 32 | To install the app you need to install the packages required for this project, the app has Node package requirements and Bower library packages 33 | 34 | ``` 35 | npm install 36 | bower install 37 | grunt bower:install 38 | ``` 39 | 40 | ## Running the app 41 | 42 | Configure your web server to the ```dist``` folder on the app and run the **build**, **prod** or **dev** tasks 43 | 44 | ``` 45 | grunt build 46 | ``` 47 | 48 | this will generate the files your web server needs to launch the app 49 | 50 | 51 | ## Tests 52 | 53 | To run tests you need to run the test grunt task which builds the app then runs the tests 54 | 55 | 56 | ``` 57 | grunt test 58 | ``` 59 | 60 | If you are planning on debugging tests we recommend you use either the **test** or **karma** grunt tasks with the **--watch** option 61 | 62 | ``` 63 | grunt test --watch 64 | // or 65 | grunt karma --watch 66 | 67 | ``` 68 | 69 | this will leave a process running watching for changes on the test files, which speeds up testing. 70 | 71 | A pro-tip is to launch your browser at the url specified when running the tests 72 | 73 | Here is an example excerpt output from ```grunt karma``` 74 | 75 | ``` 76 | Running "karma:unit" (karma) task 77 | INFO [karma]: Karma v0.12.16 server started at http://localhost:9876/ 78 | INFO [launcher]: Starting browser PhantomJS 79 | INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket tIC2TRlp_tzgnpb0U1mB with id 25791127 80 | ``` 81 | 82 | notice the url from Karma **http://localhost:9876/** run that on your browser so you can set debug breakpoints to help you debug your tests. 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Droneshare 2 | 3 | Build Status: 4 | 5 | [![Codeship Status for diydrones/droneshare](https://codeship.io/projects/1d6b0730-e382-0131-36da-0e6774a12e5d/status)](https://codeship.io/projects/25456) 6 | 7 | The new version of droneshare - built upon the [DroneKit](http://www.dronekit.io/). Please see our [welcome letter](WELCOME.md). 8 | 9 | ## Developers guide 10 | 11 | This application is built using AngularJS and [coffeescript](http://coffeescript.org/). 12 | 13 | 14 | ### Requirements 15 | 16 | Before we continue make sure your system is running with the latest stable versions of the following packages 17 | 18 | - [Nodejs](http://nodejs.org/) 19 | - [npm](https://www.npmjs.org/) 20 | - [grunt](http://gruntjs.com/) (Install globaly: ```npm install -g grunt-cli```) 21 | 22 | ### Getting the source 23 | you either want to download the latest version or use git to get either our own or your fork of the project. 24 | 25 | to get our repository 26 | 27 | ``` 28 | git clone https://github.com/diydrones/droneshare.git 29 | ``` 30 | 31 | to get your fork 32 | 33 | ``` 34 | git clone git@github.com:YOUR-USERNAME/droneshare.git 35 | ``` 36 | 37 | or finally theres a zip download of the latest version available. 38 | 39 | [Download Zip](https://github.com/diydrones/droneshare/archive/master.zip) 40 | 41 | 42 | 43 | ### Installing 44 | 45 | To install the app you need to install the packages required for this project, the app has Node package requirements and Bower library packages. 46 | 47 | You can find the list of npm packages inside the [packages.js](https://github.com/diydrones/droneshare/blob/master/package.json) file. 48 | 49 | The bower list can be found inside the [bower.json](https://github.com/diydrones/droneshare/blob/master/bower.json) 50 | 51 | to install just do 52 | 53 | ``` 54 | npm install 55 | bower install 56 | grunt bower:install 57 | ``` 58 | 59 | ### Running the app 60 | 61 | Configure your web server to the ```dist``` folder on the app and run the **build**, **prod** or **dev** tasks 62 | 63 | ``` 64 | grunt build 65 | ``` 66 | 67 | this will generate the files your web server needs to launch the app 68 | 69 | 70 | ### Tests 71 | 72 | To run tests you need to run the test grunt task which builds the app then runs the tests 73 | 74 | 75 | ``` 76 | grunt test 77 | ``` 78 | 79 | If you are planning on debugging tests we recommend you use either the **test** or **karma** grunt tasks with the **--watch** option 80 | 81 | ``` 82 | grunt test --watch 83 | grunt karma --watch 84 | 85 | ``` 86 | 87 | this will leave a process running watching for changes on the test files, which speeds up testing. 88 | 89 | A pro-tip is to launch your browser at the url specified when running the tests 90 | 91 | Here is an example excerpt output from ```grunt karma``` 92 | 93 | ``` 94 | Running "karma:unit" (karma) task 95 | INFO [karma]: Karma v0.12.16 server started at http://localhost:9876/ 96 | INFO [launcher]: Starting browser PhantomJS 97 | INFO [PhantomJS 1.9.7 (Mac OS X)]: Connected on socket tIC2TRlp_tzgnpb0U1mB with id 25791127 98 | ``` 99 | 100 | Notice the url from Karma **http://localhost:9876/** open that on your browser so you can set debug breakpoints to help you debug your tests. 101 | 102 | 103 | ### Compiling 104 | 105 | There are 3 ways of compiling the app depending on your needs 106 | 107 | 1. `grunt build` - will compile the app preserving individual files (when run, files will be loaded on-demand) 108 | 2. `grunt` or `grunt dev` - same as `grunt` but will watch for file changes and recompile on-the-fly 109 | 3. `grunt prod` - will compile using optimizations. This will create one JavaScript file and one CSS file to demonstrate the power of [r.js](http://requirejs.org/docs/optimization.html), the build optimization tool for RequireJS. And take a look at the index.html file. Yep - it's minified too. 110 | 4. `grunt test` - will compile the app and run all unit tests 111 | 112 | 113 | ### What is Coffeescript? 114 | 115 | Coffeescript is like javascript but with much less boilerplate code. It compiles down to javascript (trivially). If you've never used coffeescript, 116 | please see this [five page user guide](http://arcturo.github.io/library/coffeescript/). If you _still_ prefer javascript: We've got ya covered. 117 | Simply run the following grunt task. 118 | 119 | `grunt jslove` - will transpile all of the CoffeeScript files to JavaScript and throw out the Coffee. 120 | 121 | 122 | ### A note on tabs, spaces and line-endings 123 | 124 | This project uses a [http://editorconfig.org/](.editorconfig) file to specify source formatting conventions. We encourage you to install a suitable 125 | plug-in into your text-editor of choice. 126 | 127 | -------------------------------------------------------------------------------- /WELCOME.md: -------------------------------------------------------------------------------- 1 | # This is Droneshare - please join us 2 | 3 | * This application is in 100% JavaScript and quite easy to extend. 4 | * We happily accept pull-requests. 5 | * Also if you'd like to fork this app and change the innards into something else that is fine also. 6 | * For license information see [LICENSE](LICENSE) 7 | * For instructions on building see [README.md](README.md) 8 | * Questions? Just ask on our [discussion group](https://groups.google.com/forum/#!aboutgroup/drone-platform) 9 | * If you are making a new application (i.e. forking Droneshare or starting from scratch). Please register 10 | your developer key [here](https://developer.3drobotics.com). 11 | 12 | # Contributors 13 | 14 | The following developers have contributed substantial work in making this application better. We would like to thank them for their efforts. 15 | 16 | * [Ramon Roche](https://github.com/mrpollo) - Many fixes to make this application work better 17 | * [Jason Short](https://github.com/jason4short) - UX guidance and fixes 18 | * [Kevin Hester](https://github.com/geeksville) - Primary author (thus far) 19 | 20 | # Supporting projects 21 | 22 | We would like to thank the following open-source projects and contributors: 23 | 24 | * [AngularFun](https://github.com/CaryLandholt/AngularFun) - a great template project for starting AngularJS based applications. 25 | * [Angular-JS](https://angularjs.org/) - The web framework from Google that this application is built upon 26 | * [Angular-UI](http://angular-ui.github.io/) - Various look & feel enhancements for AngularJS 27 | * [Bootstrap CSS](http://getbootstrap.com/) - The ubiquitous CSS library 28 | * [Flot](http://www.flotcharts.org/) - Used for the data plot views 29 | * [Glyphicons](http://glyphicons.com/) - Provided some of the icons in this application 30 | * [Leaflet](http://leafletjs.com/) - An open source mapping framework 31 | * [Mapbox](https://www.mapbox.com) - A commercial provider of Leaflet extensions and mapping services 32 | 33 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "droneshare", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "angular": "1.3.0-beta.5", 6 | "angular-animate": "1.3.0-beta.5", 7 | "angular-mocks": "1.3.0-beta.5", 8 | "angular-route": "1.3.0-beta.5", 9 | "bootstrap": "3.1.1", 10 | "html5shiv": "3.7.0", 11 | "json3": "3.3.0", 12 | "requirejs": "2.1.11", 13 | "angulartics": "~0.15.17", 14 | "angular-flot": "0.0.3", 15 | "flot": "~0.8.3", 16 | "angular-social": "~0.1.1", 17 | "ng-file-upload": "~1.3.2", 18 | "jasmine-jquery": "~2.0.5", 19 | "ngInfiniteScroll": "~1.1.2", 20 | "ngprogress-lite": "1.0.5", 21 | "ng-ladda-bootstrap": "*", 22 | "spin.js": "~1.3.2", 23 | "highcharts-ng": "~0.0.7" 24 | }, 25 | "exportsOverride": { 26 | "angular": { 27 | "scripts/libs": "angular.min.js{,.map}" 28 | }, 29 | "angular-animate": { 30 | "scripts/libs": "angular-animate.min.js{,.map}" 31 | }, 32 | "angular-mocks": { 33 | "scripts/libs": "angular-mocks.js" 34 | }, 35 | "angular-route": { 36 | "scripts/libs": "angular-route.min.js{,.map}" 37 | }, 38 | "bootstrap": { 39 | "fonts": "dist/fonts/*.{eot,svg,ttf,woff}", 40 | "styles": "less/*.less" 41 | }, 42 | "html5shiv": { 43 | "scripts/libs": "dist/html5shiv-printshiv.js" 44 | }, 45 | "flot": { 46 | "scripts/libs": [ 47 | "jquery.js", 48 | "jquery.flot.js", 49 | "jquery.flot.time.js" 50 | ] 51 | }, 52 | "json3": { 53 | "scripts/libs": "lib/json3.min.js" 54 | }, 55 | "requirejs": { 56 | "scripts/libs": "require.js" 57 | }, 58 | "angulartics": { 59 | "scripts/libs": [ 60 | "dist/angulartics-ga.min.js", 61 | "dist/angulartics.min.js" 62 | ] 63 | }, 64 | "angular-flot": { 65 | "scripts/libs": "angular-flot.js" 66 | }, 67 | "angular-social": { 68 | "scripts/libs": "angular-social.js", 69 | "styles": "angular-social.css" 70 | }, 71 | "ng-file-upload": { 72 | "scripts/libs": [ 73 | "angular-file-upload.min.js", 74 | "angular-file-upload-shim.min.js", 75 | "FileAPI.flash.swf", 76 | "FileAPI.min.js" 77 | ] 78 | }, 79 | "jasmine-jquery": { 80 | "scripts/libs": "lib/jasmine-jquery.js" 81 | }, 82 | "ngInfiniteScroll": { 83 | "scripts/libs": "build/ng-infinite-scroll.js" 84 | }, 85 | "ngprogress-lite": { 86 | "scripts/libs": "ngprogress-lite.js", 87 | "styles": "ngprogress-lite.css" 88 | }, 89 | "ng-ladda-bootstrap": { 90 | "scripts/libs": "dist/ng-ladda-bootstrap.js" 91 | }, 92 | "highcharts-ng": { 93 | "scripts/libs": "dist/highcharts-ng.js" 94 | }, 95 | "highcharts": { 96 | "scripts/libs": "highcharts.js" 97 | } 98 | }, 99 | "resolutions": { 100 | "angular": "1.3.0-beta.5" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /deploy-s3.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | #### 4 | ## 5 | ## USAGE DETAILS 6 | ## 7 | #### 8 | 9 | SERVERNAME=$1 10 | 11 | if [ -z "$SERVERNAME" ]; then 12 | echo "Usage: deploy-s3 " 13 | exit -1 14 | fi 15 | 16 | if [ "$SERVERNAME" != "alpha" -a "$SERVERNAME" != "beta" -a "$SERVERNAME" != "production" ]; then 17 | echo "Error, invalid server name" 18 | exit -1 19 | fi 20 | 21 | if [ $SERVERNAME = production ]; then 22 | read -p "Are you REALLY REALLY SURE? " yn 23 | case $yn in 24 | [Yy]* ) echo "deploying to production";; 25 | * ) exit -1;; 26 | esac 27 | fi 28 | 29 | #### 30 | ## 31 | ## BUILD PROJECT 32 | ## 33 | #### 34 | 35 | # grunt might not always be running, avoid deploy fail if not 36 | if `pgrep grunt >/dev/null 2>&1` 37 | then 38 | # Kill any running dev server, then start a prod build 39 | skill grunt 40 | fi 41 | grunt test 42 | grunt prod 43 | 44 | #### 45 | ## 46 | ## PUSH TO AWS - US-WEST-2 47 | ## 48 | #### 49 | 50 | BUCKETNAME=$SERVERNAME.droneshare.com 51 | export AWS_DEFAULT_PROFILE=3dr 52 | aws --region us-west-2 s3 mb s3://$BUCKETNAME || true 53 | 54 | # For now we limit cache time to 1hr 55 | aws --region us-west-2 s3 sync --delete --cache-control="max-age=3600" dist s3://$BUCKETNAME 56 | 57 | # AngularJS applications prefer to get back index.html for any bad links 58 | cat > s3website.json < s3bucket-policy.json < 2 | express = require 'express' 3 | routes = require './routes' 4 | cors = require 'express-cors' 5 | app = express() 6 | 7 | app.configure -> 8 | app.use cors 9 | allowedOrigins: [ '*' ] 10 | app.use express.logger 'dev' 11 | app.use express.bodyParser() 12 | app.use express.methodOverride() 13 | app.use express.errorHandler() 14 | app.use express.static String(options.base) 15 | app.use app.router 16 | routes app, options 17 | 18 | app.get '*', (req, res) -> 19 | res.sendfile('dist/index.html') 20 | 21 | [connect(app)] 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Droneshare", 3 | "description": "Open-source drone sharing and control", 4 | "version": "1.0.0", 5 | "author": { 6 | "name": "Kevin Hester", 7 | "email": "kevin@3drobotics.com", 8 | "url": "https://github.com/geeksville" 9 | }, 10 | "contributors": [ 11 | { 12 | "name": "Ramon Roche", 13 | "email": "ramon@3drobotics.com", 14 | "url": "https://github.com/mrpollo" 15 | }, 16 | { 17 | "name": "Jason Short", 18 | "email": "jason@3drobotics.com", 19 | "url": "https://github.com/jason4short" 20 | } 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/diydrones/droneshare.git" 25 | }, 26 | "dependencies": { 27 | "coffee-script": "~1.7.1", 28 | "express": "~3.5.1", 29 | "express-cors": "0.0.3", 30 | "grunt": "~0.4.4", 31 | "grunt-bower-task": "~0.3.4", 32 | "grunt-coffeelint": "~0.0.8", 33 | "grunt-contrib-clean": "~0.5.0", 34 | "grunt-contrib-coffee": "~0.10.1", 35 | "grunt-contrib-connect": "~0.7.1", 36 | "grunt-contrib-copy": "~0.5.0", 37 | "grunt-contrib-imagemin": "^0.7.1", 38 | "grunt-contrib-less": "~0.11.0", 39 | "grunt-contrib-requirejs": "~0.4.3", 40 | "grunt-contrib-uglify": "~0.4.0", 41 | "grunt-contrib-watch": "~0.6.1", 42 | "grunt-hustler": "~4.0.6", 43 | "grunt-karma": "~0.8.2", 44 | "grunt-prompt": "~1.1.0", 45 | "grunt-sauceconnect": "^0.1.0", 46 | "karma-junit-reporter": "~0.2.1", 47 | "karma-requirejs": "^0.2.2", 48 | "load-grunt-tasks": "~0.4.0", 49 | "optipng-bin": "^0.3.8", 50 | "time-grunt": "~0.3.1" 51 | }, 52 | "engines": { 53 | "node": "0.8.x", 54 | "npm": "1.1.x" 55 | }, 56 | "devDependencies": { 57 | "bower": "^1.3.12", 58 | "grunt-contrib-jasmine": "^0.6.5", 59 | "grunt-protractor-runner": "^1.1.4", 60 | "grunt-protractor-webdriver": "^0.1.9", 61 | "karma-coffee-preprocessor": "^0.2.1", 62 | "karma-jasmine": "^0.2.2", 63 | "karma-phantomjs-launcher": "^0.1.4", 64 | "protractor": "^1.3.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /protractor.conf.coffee: -------------------------------------------------------------------------------- 1 | exports.config = 2 | 3 | allScriptsTimeout: 99999 4 | 5 | #// Capabilities to be passed to the webdriver instance. 6 | multiCapabilities: [{ 7 | 'browserName': 'firefox' 8 | }, { 9 | 'browserName': 'chrome' 10 | }] 11 | 12 | baseUrl: 'http://localhost:9001/' 13 | 14 | framework: 'jasmine' 15 | 16 | onPrepare: -> 17 | global.findBy = protractor.By 18 | 19 | #// Spec patterns are relative to the current working directly when 20 | #// protractor is called. 21 | specs: ['test/e2e/**/*.coffee'] 22 | 23 | #// Options to be passed to Jasmine-node. 24 | jasmineNodeOpts: 25 | showColors: true 26 | defaultTimeoutInterval: 30000 27 | isVerbose : true 28 | includeStackTrace : true 29 | -------------------------------------------------------------------------------- /protractor.saucelabs.conf.coffee: -------------------------------------------------------------------------------- 1 | exports.config = 2 | allScriptsTimeout: 99999 3 | 4 | sauceUser: process.env.SAUCE_USERNAME, 5 | sauceKey: process.env.SAUCE_ACCESS_KEY, 6 | 7 | multiCapabilities: [{ 8 | browserName: 'internet explorer' 9 | platform : 'Windows 7' 10 | version : '9' 11 | },{ 12 | browserName: 'internet explorer' 13 | platform : 'Windows 8' 14 | version : '10' 15 | }, { 16 | browserName: 'internet explorer' 17 | platform : 'Windows 8.1' 18 | version : '11' 19 | }, { 20 | browserName: 'chrome' 21 | platform : 'Windows 8.1' 22 | version : '' 23 | }, { 24 | browserName: 'firefox' 25 | platform : 'Windows 8.1' 26 | version : '33' 27 | }, { 28 | browserName: 'safari' 29 | platform : 'OS X 10.9' 30 | version : '7' 31 | }, { 32 | browserName: 'safari' 33 | platform : 'OS X 10.9' 34 | version : '7' 35 | }] 36 | baseUrl: 'http://localhost:9001/' 37 | 38 | framework: 'jasmine' 39 | 40 | onPrepare: -> 41 | global.findBy = protractor.By 42 | 43 | #// Spec patterns are relative to the current working directly when 44 | #// protractor is called. 45 | specs: ['test/e2e/**/*.coffee'] 46 | 47 | #// Options to be passed to Jasmine-node. 48 | jasmineNodeOpts: 49 | showColors: true 50 | defaultTimeoutInterval: 30000 51 | isVerbose : true 52 | includeStackTrace : true 53 | -------------------------------------------------------------------------------- /routes.coffee: -------------------------------------------------------------------------------- 1 | # GRUNT SERVER ROUTES 2 | module.exports = (app, options) -> 3 | app.get '/', (req, res) -> 4 | res.render "#{options.base}/index.html" 5 | -------------------------------------------------------------------------------- /src/helpers/clickjacking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/helpers/facebook-share.html: -------------------------------------------------------------------------------- 1 |
2 | 19 | -------------------------------------------------------------------------------- /src/helpers/google-plus.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/helpers/twitter-share.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/images/3drobotics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/3drobotics.png -------------------------------------------------------------------------------- /src/images/droneshare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/droneshare.png -------------------------------------------------------------------------------- /src/images/fb-share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/fb-share.jpg -------------------------------------------------------------------------------- /src/images/google-plus-share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/google-plus-share.jpg -------------------------------------------------------------------------------- /src/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/icon.png -------------------------------------------------------------------------------- /src/images/octocat-drone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/octocat-drone.jpg -------------------------------------------------------------------------------- /src/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/screenshot.png -------------------------------------------------------------------------------- /src/images/twitter-share.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/twitter-share.jpg -------------------------------------------------------------------------------- /src/images/vehicle-marker-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/vehicle-marker-active.png -------------------------------------------------------------------------------- /src/images/vehicle-marker-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dronekit/droneshare/4852652029793044f225e0356cb725ebf8745f51/src/images/vehicle-marker-inactive.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Droneshare 10 | 11 | 12 | 13 | 14 | <% if (config.environment && config.environment === 'prod') { %> 15 | 16 | <% } else { %> 17 | 18 | <% } %> 19 | 20 | 21 | <% if (config.environment && config.environment === 'prod') { %> 22 | 23 | 24 | 34 | <% } else { %> 35 | 36 | 37 | 47 | <% } %> 48 | 49 | 53 | 54 | <% if (config.environment && config.environment === 'prod') { %> 55 | 56 | <% } else { %> 57 | 58 | <% } %> 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | 67 |
68 |
69 |
70 | 71 |
72 | 73 | <% if (config.environment && config.environment === 'prod') { %> 74 | 75 | 83 | 104 | <% } else { %> 105 | 106 | <% } %> 107 |
108 |
109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /src/scripts/backend/gitHubBackend.coffee.disabled: -------------------------------------------------------------------------------- 1 | class Run 2 | constructor: (@$log, @$httpBackend) -> 3 | @$httpBackend.whenJSONP(/api.github.com/).passThrough() 4 | 5 | angular.module('app').run ['$log', '$httpBackend', Run] -------------------------------------------------------------------------------- /src/scripts/backend/peopleBackend.coffee.disabled: -------------------------------------------------------------------------------- 1 | class Run 2 | constructor: (@$log, @$httpBackend) -> 3 | nextId = 0 4 | 5 | people = [ 6 | {id: nextId++, name: 'Saasha', age: 6} 7 | {id: nextId++, name: 'Planet', age: 8} 8 | ] 9 | 10 | @$httpBackend.whenGET('/people').respond people 11 | 12 | @$httpBackend.whenPOST('/people').respond (method, url, data) -> 13 | person = angular.fromJson data 14 | name = person.name 15 | isUnique = (name for p in people when p.name is name).length is 0 16 | 17 | return if not isUnique 18 | message = 19 | title: 'Duplicate!' 20 | message: "#{name} is a duplicate. Please enter a new name." 21 | 22 | [403, message] 23 | 24 | person.id = nextId++ 25 | 26 | people.push person 27 | 28 | [200, person] 29 | 30 | angular.module('app').run ['$log', '$httpBackend', Run] -------------------------------------------------------------------------------- /src/scripts/backend/viewsBackend.coffee.disabled: -------------------------------------------------------------------------------- 1 | class Run 2 | constructor: (@$log, @$httpBackend) -> 3 | @$httpBackend.whenGET(/^.*\.(html|htm)$/).passThrough() 4 | 5 | angular.module('app').run ['$log', '$httpBackend', Run] -------------------------------------------------------------------------------- /src/scripts/controllers/aboutController.coffee: -------------------------------------------------------------------------------- 1 | class AboutController 2 | @$inject: ['$scope', '$window'] 3 | constructor: ($scope, $window) -> 4 | $scope.octocat = $window.logos.octocat 5 | 6 | angular.module('app').controller 'aboutController', AboutController 7 | -------------------------------------------------------------------------------- /src/scripts/controllers/adminController.coffee: -------------------------------------------------------------------------------- 1 | class Controller 2 | @$inject: ['$sce', '$scope', '$log', 'adminService'] 3 | constructor: (@sce, @scope, @log, @adminService) -> 4 | @simType = "std/4/600" 5 | @lines = [] 6 | @debugInfo = "(waiting for server...)" 7 | 8 | @log.debug("Starting log viewing") 9 | adminService.atmosphere.on("log", @onLog) 10 | 11 | # Async fetch of debugging info 12 | @adminService.getDebugInfo().then (results) => 13 | @log.debug("Setting debug info") 14 | @debugInfo = results 15 | 16 | @startSim = () => 17 | @log.debug("Running sim " + @simType) 18 | @adminService.startSim(@simType) 19 | 20 | @importOld = (count) => 21 | @adminService.importOld(count) 22 | 23 | @runCommand = (cmd) => 24 | @adminService.postId(cmd) 25 | 26 | onLog: (data) => 27 | @log.info("Logmsg: " + data) 28 | # Keep the last 10 log entries 29 | @scope.$apply(() => 30 | @lines.push(data.toString()) 31 | @lines = @lines[-10..] 32 | ) 33 | 34 | 35 | 36 | #Controller.$inject = ['$scope', '$log', 'adminService'] 37 | 38 | angular.module('app').controller 'adminController', Controller 39 | -------------------------------------------------------------------------------- /src/scripts/controllers/detailControllers.coffee: -------------------------------------------------------------------------------- 1 | # Temporarily moved to dapiControllers.coffee 2 | -------------------------------------------------------------------------------- /src/scripts/custom/angular-atmosphere.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // From https://github.com/bertramdev/angular-atmosphere/blob/master/app/scripts/services/angular-atmosphere.js 4 | 5 | function NoAtmospherePluginError(message) { 6 | this.prototype.name = 'NoAtmospherePluginError'; 7 | this.message = (message || 'The Atmosphere plugin for jQuery was not found'); 8 | } 9 | 10 | NoAtmospherePluginError.prototype = new Error(); 11 | 12 | angular.module('ngAtmosphere', []) 13 | .factory('atmosphere', [function () { 14 | 15 | if (!window.atmosphere) { 16 | throw new NoAtmospherePluginError(); 17 | } 18 | 19 | var debug = false; 20 | var listeners = {}; 21 | var listenerIndex = {}; 22 | 23 | var connection; 24 | 25 | function handleResponse(response) { 26 | var data = response.responseBody; 27 | if (typeof data === 'string'){ 28 | data = angular.fromJson(data); 29 | } 30 | if (debug){ 31 | console.log('ngAtmosphere DEBUG: received response from server', data.type, JSON.stringify(data)); 32 | } 33 | 34 | var callByKey = function (key) { 35 | if (listeners.hasOwnProperty(key)) 36 | angular.forEach(listeners[key], function (listener) { 37 | listener.fn.call(this, data.data); 38 | }); 39 | }; 40 | 41 | callByKey(data.type); 42 | callByKey(null); 43 | } 44 | 45 | // Public API here 46 | return { 47 | init: function (requestObj) { 48 | if (!connection) { 49 | var request = requestObj; 50 | request.onMessage = handleResponse; 51 | 52 | connection = window.atmosphere.subscribe(request); 53 | if (debug) { 54 | console.log('ngAtmosphere DEBUG: connection made to: ' + connection.getUrl()); 55 | } 56 | } 57 | }, 58 | close: function () { 59 | if(connection){ 60 | if (debug) { 61 | console.log('ngAtmosphere DEBUG: unsubscribing to ' + connection.getUrl()); 62 | } 63 | window.atmosphere.unsubscribeUrl(connection.getUrl()); 64 | connection = null; 65 | } 66 | }, 67 | 68 | // type is either a string to match or null to receive all msgs 69 | on: function (type, callbackFn) { 70 | 71 | var id = Math.random(); 72 | 73 | if (!listeners.hasOwnProperty(type)) { 74 | listeners[type] = []; 75 | } 76 | listenerIndex[id] = type; 77 | listeners[type].push({id: id, fn: callbackFn}); 78 | 79 | if (debug) { 80 | console.log('ngAtmosphere DEBUG: added callback to ' + type + ' and given the id of ' + id); 81 | } 82 | 83 | return id; 84 | }, 85 | off: function (id) { 86 | var type = listenerIndex[id]; 87 | var typeListeners = listeners[type]; 88 | var removed = false; 89 | 90 | for (var i = 0; i < typeListeners.length; i++) { 91 | if (typeListeners[i].id === id) { 92 | typeListeners.splice(i, 1); 93 | delete listenerIndex[id]; 94 | 95 | removed = true; 96 | break; 97 | } 98 | } 99 | if (debug) { 100 | console.log('ngAtmosphere DEBUG: removed callback from ' + type + ' with the id of: ' + id); 101 | } 102 | 103 | return removed; 104 | }, 105 | emit: function (type, data) { 106 | if (debug) { 107 | console.log('ngAtmosphere DEBUG: sending data with type: ' + type, connection, data); 108 | } 109 | connection.push(angular.toJson({type: type, data: data})); 110 | }, 111 | debug: function(enable){ 112 | debug = enable; 113 | } 114 | }; 115 | }]); 116 | -------------------------------------------------------------------------------- /src/scripts/directives/accessCodeDropdown.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'accessCodeDropdown', ['$window', ($window) -> 2 | restrict: 'E' 3 | templateUrl: '/views/directives/access-code-dropdown.html' 4 | scope: 5 | code: '=' 6 | fieldName: '=field' 7 | ] 8 | -------------------------------------------------------------------------------- /src/scripts/directives/liveMap.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'liveMap', ['$window', ($window) -> return { 2 | restrict: 'A' 3 | transclude: true 4 | controller: 'liveMapController' 5 | templateUrl: '/views/directives/live-map.html' 6 | link: ($scope, $element, attrs) -> 7 | sameRouteSameParams = (config) -> $scope.urlBase == config.url 8 | 9 | $scope.$on 'loading-started', (event, config) -> 10 | $scope.recordsLoaded(false, config) if sameRouteSameParams(config) 11 | $scope.$on 'loading-complete', (event, config) -> 12 | $scope.recordsLoaded(true, config) if sameRouteSameParams(config) 13 | 14 | $scope.uiBounds = $('#main-navigation').height() + $('#footer').height() 15 | $scope.initializeWindowSize = -> 16 | $scope.windowHeight = $window.innerHeight - ( $scope.uiBounds ) 17 | 18 | $scope.leafletData.getMap().then (map) -> 19 | $(map._container).css 20 | height: "#{$scope.windowHeight}px" 21 | width: "100%" 22 | map.invalidateSize(false) 23 | 24 | angular.element($window).bind 'resize', -> 25 | $scope.initializeWindowSize() 26 | $scope.$apply() 27 | 28 | # apply window resize on initialize 29 | $scope.initializeWindowSize() 30 | }] 31 | -------------------------------------------------------------------------------- /src/scripts/directives/mapboxStaticMap.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'mapboxStaticMap', () -> 2 | restrict: 'A' 3 | template: '' 4 | scope: 5 | latitude: '=' 6 | longitude: '=' 7 | width: '=' 8 | height: '=' 9 | zoom: '=?' 10 | icon: '=?' # A mapbox icon name string 11 | color: '=?' # html hex color string 12 | controller: [ "$scope", (scope) -> 13 | apikey = 'kevin3dr.hokdl9ko' # FIXME - move this someplace better 14 | 15 | longitude = scope.longitude 16 | latitude = scope.latitude 17 | zoom = scope.zoom ? "8" 18 | latlonstr = "#{longitude},#{latitude},#{zoom}" 19 | markerstr = if scope.icon? 20 | color = scope.color ? "f44" # default to redish 21 | "pin-s-#{scope.icon}+#{color}(#{latlonstr})/" 22 | else 23 | "" 24 | scope.url = "http://api.tiles.mapbox.com/v3/#{apikey}/#{markerstr}#{latlonstr}/#{scope.width}x#{scope.height}.png" 25 | ] 26 | -------------------------------------------------------------------------------- /src/scripts/directives/missionList.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'missionList', ['$log', ($log) -> return { 2 | restrict: 'A' 3 | controllerAs: 'controller' 4 | templateUrl: '/views/directives/mission-list.html' 5 | scope: 6 | 'pageSize': '=' 7 | 'noInfiniteScroll': '=' 8 | 'preFetched': '=' 9 | 'records': '=' 10 | controller: ['$scope', '$sce', 'missionService', 'authService', (@scope, @sce, @service, @authService) -> 11 | @currentUser = @authService.getUser() 12 | 13 | if @scope.preFetched 14 | @scope.busy = false 15 | else 16 | @scope.busy = true 17 | @allMissions().then (results) => @scope.busy = false 18 | 19 | @createdAt = 'desc' 20 | @scope.filterInProgress = false 21 | @scope.filtersActive = false 22 | @scope.loaded = if @scope.preFetched then true else false 23 | @scope.missionScopeTitle = 'All' 24 | @scope.missionDataSet = 'all' 25 | @scope.missionDataSetOptions = [ 26 | {value: 'all', label: 'All Missions'}, 27 | {value: 'mine', label: 'My Missions'} 28 | ] 29 | @scope.vehicleType = '' 30 | @scope.dropDownIsOpen = 31 | field: false 32 | opt: false 33 | 34 | @getFetchParams = => 35 | fetchParams = 36 | order_by: 'createdAt' 37 | order_dir: @createdAt 38 | page_offset: 0 39 | page_size: 12 40 | 41 | @setFetchParamsFilter = (value) => 42 | @fetchParams = @getFetchParams() 43 | @fetchParams["#{@scope.filters.field.field}[#{@scope.filters.opt.opt}]"] = value 44 | @fetchParams 45 | 46 | @createFilterField = (title, field, units) -> 47 | filter = 48 | title: title 49 | field: field 50 | units: units 51 | 52 | @createFilterOpt = (title, opt, humanize) => 53 | filter = 54 | title: @sce.trustAsHtml(title) 55 | opt: opt 56 | humanize: humanize 57 | 58 | @fetchParams = @getFetchParams() 59 | 60 | @filterFields = [ 61 | @createFilterField('Max Groundspeed', 'field_maxGroundspeed', 'm/s') 62 | @createFilterField('Duration', 'field_flightDuration', 'min') 63 | @createFilterField('Max Airspeed', 'field_maxAirspeed', 'm/s') 64 | @createFilterField('Max Altitude', 'field_maxAlt', 'm') 65 | ] 66 | 67 | @filterOpts = [ 68 | @createFilterOpt('Greater than', 'GT', 'greater than') 69 | @createFilterOpt('Greater or Equal to', 'GE', 'greater or equal to') 70 | @createFilterOpt('Equal to', 'EQ', 'equal to') 71 | @createFilterOpt('Lower than', 'LT', 'lower than') 72 | @createFilterOpt('Lower or Equal to', 'LE', 'equal or lower than') 73 | @createFilterOpt('Different than', 'NE', 'different than') 74 | ] 75 | 76 | @scope.filters = 77 | field: @filterFields[0] 78 | opt: @filterOpts[0] 79 | input: '' 80 | dataset: '' 81 | 82 | @vehicleTypes = [ 83 | "quadcopter" 84 | "tricopter" 85 | "coaxial" 86 | "hexarotor" 87 | "octorotor" 88 | "fixed-wing" 89 | "ground-rover" 90 | "submarine" 91 | "airship" 92 | "flapping-wing" 93 | "boat" 94 | "free-balloon" 95 | "antenna-tracker" 96 | "generic" 97 | "rocket" 98 | "helicopter" 99 | ] 100 | 101 | @allMissions = => 102 | @scope.missionScopeTitle = "All" 103 | # reset fetchParams 104 | @fetchParams = @getFetchParams() 105 | @service.getAllMissions().then @assignRecords 106 | 107 | @userMissions = (filterParams = false) => 108 | @scope.missionScopeTitle = "My" 109 | # reset fetchParams 110 | @fetchParams = @getFetchParams() 111 | # then add user login limitation 112 | @fetchParams['field_userName'] = @currentUser.login 113 | @service.getUserMissions(@currentUser.login, filterParams).then @assignRecords 114 | 115 | @getVehicleTypeMissions = (vehicleType) => 116 | @service.getVehicleTypeMissions(vehicleType).then @assignRecords 117 | 118 | @getDurationMissions = (duration, opt) => 119 | @service.getDurationMissions(duration, opt).then @assignRecords 120 | 121 | @getMaxAltMissions = (maxAlt, opt) => 122 | @service.getMaxAltMissions(maxAlt, opt).then @assignRecords 123 | 124 | @getMaxGroundSpeedMissions = (speed, opt) => 125 | @service.getMaxGroundSpeedMissions(speed, opt).then @assignRecords 126 | 127 | @getMaxAirSpeedMissions = (speed, opt = 'GT') => 128 | @service.getMaxAirSpeedMissions(speed, opt).then @assignRecords 129 | 130 | @getLatitudeMissions = (latitude, opt = 'GT') => 131 | @service.getLatitudeMissions(latitude, opt).then @assignRecords 132 | 133 | @getLongitudeMissions = (longitude, opt = 'GT') => 134 | @service.getLongitudeMissions(longitude, opt).then @assignRecords 135 | 136 | @sortCreatedAt = -> 137 | # figure out how to toggle sort by other fields 138 | 139 | @assignRecords = (records) => 140 | @scope.records = records 141 | # check if we need to fetch again from infinite-scroll 142 | @scope.checkIfNextPage() 143 | 144 | @appendRecords = (records) => 145 | @scope.records = @scope.records.concat records 146 | 147 | @chooseDataSet = => 148 | return @allMissions() if @scope.missionDataSet == 'all' 149 | return @userMissions() if @scope.missionDataSet == 'mine' 150 | 151 | @filterDataSet = (value, opt)=> 152 | @scope.filterInProgress = true 153 | # if trying to get duration input from user is in minutes 154 | # API expects seconds, need to convert 155 | value *= 60 if @scope.filters.field.field == 'field_flightDuration' 156 | # set the fetchParams so that we can use them later 157 | @setFetchParamsFilter value 158 | 159 | if @scope.missionDataSet == 'all' 160 | switch @scope.filters.field.field 161 | when 'field_maxGroundspeed' then @getMaxGroundSpeedMissions(value, opt).then @filterClear 162 | when 'field_flightDuration' then @getDurationMissions(value, opt).then @filterClear 163 | when 'field_maxAirspeed' then @getMaxAirSpeedMissions(value, opt).then @filterClear 164 | when 'field_maxAlt' then @getMaxAltMissions(value, opt).then @filterClear 165 | when 'field_latitude' then @getLatitudeMissions(value, opt).then @filterClear 166 | when 'field_longitude' then @getLongitudeMissions(value, opt).then @filterClear 167 | else $log.debug("something is wrong") 168 | else if @scope.missionDataSet == 'mine' 169 | # since we know set the fetchParams before choosing 170 | # which dataSet to work on, we can just tell the service 171 | # to poll the user missions with this filters applied 172 | @userMissions(@fetchParams).then @filterClear 173 | 174 | @filterClear = => 175 | @scope.filterInProgress = false 176 | 177 | @setCreatedAt = => 178 | fetchParams = @service.getFetchParams() 179 | fetchParams.order_dir = @createdAt 180 | @service.createdAt = @createdAt 181 | $log.debug "sortDirection: ", @createdAt, " fetchParams: ", fetchParams 182 | 183 | @nextPage = => 184 | return false if @scope.busy 185 | @scope.busy = true 186 | 187 | offset = @fetchParams.page_offset 188 | offset = 1 if @fetchParams.page_offset == 0 189 | @fetchParams.page_offset = @fetchParams.page_size + offset 190 | 191 | @service.getMissions(@fetchParams).then (records) => 192 | @scope.busy = false 193 | @appendRecords(records) 194 | 195 | return @ 196 | ] 197 | link: ($scope, element, attributes, controller) -> 198 | $scope.$watch 'missionDataSet', (newValue, oldValue) -> 199 | unless newValue == oldValue 200 | #controller.queryFilterHidden = if $scope.missionDataSet == 'mine' then true else false 201 | controller.chooseDataSet() 202 | 203 | $scope.tryFilterField = (index) -> 204 | $scope.dropDownIsOpen.field = false 205 | $scope.filters.field = controller.filterFields[index] 206 | 207 | $scope.tryFilterOp = (index) -> 208 | $log.debug "tryfilterOp: ", index 209 | $scope.dropDownIsOpen.opt = false 210 | $scope.filters.opt = controller.filterOpts[index] 211 | 212 | $scope.tryFilterDataSet = -> 213 | $scope.filtersActive = true 214 | controller.filterDataSet $scope.filters.input, $scope.filters.opt.opt 215 | # humanized filter query 216 | $scope.filters.dataset = "#{$scope.filters.field.title} is #{$scope.filters.opt.humanize} #{$scope.filters.input}" 217 | 218 | $scope.checkIfNextPage = -> 219 | return false if $scope.noInfiniteScroll 220 | controller.nextPage() 221 | 222 | $scope.toggleCreatedAt = -> 223 | controller.createdAt = if controller.createdAt == 'asc' then 'desc' else 'asc' 224 | controller.setCreatedAt() 225 | controller.chooseDataSet() 226 | 227 | ($ '.form-control-input-clear').bind 'click', (event) -> 228 | if $scope.filtersActive 229 | $scope.filtersActive = false 230 | $scope.filterInProgress = true 231 | $scope.filters.input = '' 232 | $scope.filters.dataset = '' 233 | controller.createdAt = 'asc' 234 | controller.chooseDataSet().then controller.filterClear 235 | }] 236 | -------------------------------------------------------------------------------- /src/scripts/directives/missionPlot.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'missionPlot', ['$log', '$window', ($log, $window) -> return { 2 | restrict: 'A' 3 | controllerAs: 'controller' 4 | templateUrl: '/views/directives/mission-plot.html' 5 | scope: 6 | 'series': '=' 7 | controller: ['$scope', (@scope) -> 8 | @getPlotWidthHeight = -> 9 | navHeight = ($ 'header[header-nav]').height() 10 | infoHeight = ($ '.plot-info').height() 11 | plotHeight = ($ window).height() - navHeight - infoHeight - 200 12 | plotWidth = ($ '.highcharts-plot').width() 13 | [plotHeight, plotWidth] 14 | 15 | size = @getPlotWidthHeight() 16 | 17 | @resizeChart = => 18 | size = @getPlotWidthHeight() 19 | plotHeight = size[0] 20 | plotWidth = size[1] 21 | @scope.$apply => 22 | @scope.chartConfig.size.width = plotWidth 23 | @scope.chartConfig.size.height = plotHeight 24 | 25 | @scope.chartConfig = 26 | options: 27 | chart: 28 | type: 'line' 29 | zoomType: 'x' 30 | xAxis: 31 | ordinal: false 32 | type: 'datetime' 33 | tickInterval: 2 * 60 * 1000 34 | labels: 35 | rotation: -45 36 | dateTimeLabelFormats: 37 | day: '%H:%M' 38 | hour: '%I %p' 39 | minute: '%I:%M %p' 40 | 41 | title: 42 | text: 'Param Plot' 43 | series: ({name: option.label, data: option.data} for option in @scope.series) 44 | size: 45 | height: size[0] 46 | width: size[1] 47 | 48 | return @ 49 | ] 50 | link: ($scope, element, attributes, controller) -> 51 | ($ window).resize controller.resizeChart 52 | #controller.resizeChart(false) 53 | }] 54 | -------------------------------------------------------------------------------- /src/scripts/directives/missionSummary.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'missionSummary', ['$window', ($window) -> 2 | restrict: 'A' 3 | templateUrl: '/views/directives/mission-summary.html' 4 | scope: 5 | mission: '=' 6 | ] 7 | -------------------------------------------------------------------------------- /src/scripts/directives/navbarDirective.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'headerNav', [ '$window', ($window) -> return { 2 | restrict: 'A' 3 | transclude: true 4 | templateUrl: '/views/directives/navbar.html' 5 | controller: 'authController' 6 | link: ($scope, element, attrs, authController) -> 7 | $scope.parent_logo = $window.logos.parent 8 | $scope.son_logo = $window.logos.son 9 | $scope.auth = authController 10 | }] 11 | -------------------------------------------------------------------------------- /src/scripts/directives/tab.coffee: -------------------------------------------------------------------------------- 1 | class Directive 2 | constructor: ($log) -> 3 | link = (scope, element, attrs, controller) -> 4 | controller.addTab scope, attrs.tabId 5 | 6 | return { 7 | link 8 | locals: 9 | transcluded: '@' 10 | replace: true 11 | require: '^appTabs' 12 | restrict: 'E' 13 | scope: 14 | caption: '@' 15 | selected: '@' 16 | templateUrl: '/views/directives/tab.html' 17 | transclude: true 18 | } 19 | 20 | angular.module('app').directive 'appTab', ['$log', Directive] 21 | -------------------------------------------------------------------------------- /src/scripts/directives/tabs.coffee: -------------------------------------------------------------------------------- 1 | class Controller 2 | constructor: ($log) -> 3 | @tabs = [] 4 | 5 | @select = (tab) => 6 | tab.transcluded = true 7 | 8 | return if tab.selected is true 9 | 10 | angular.forEach @tabs, (tab) -> 11 | tab.selected = false 12 | 13 | tab.selected = true 14 | 15 | @addTab = (tab) => 16 | tab.transcluded = true 17 | 18 | @select tab if @tabs.length is 0 19 | @tabs.push tab 20 | 21 | class Directive 22 | constructor: ($log) -> 23 | return { 24 | controller: ['$log', Controller] 25 | controllerAs: 'controller' 26 | replace: true 27 | restrict: 'E' 28 | scope: {} 29 | templateUrl: '/views/directives/tabs.html' 30 | transclude: true 31 | } 32 | 33 | angular.module('app').directive 'appTabs', ['$log', Directive] 34 | -------------------------------------------------------------------------------- /src/scripts/directives/uploadMission.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'uploadMission', -> return { 2 | restrict: 'A' 3 | templateUrl: '/views/directives/upload-mission.html' 4 | controller: 'vehicleController as controller' 5 | scope: 6 | 'user': '=' 7 | link: ($scope, element, attributes, controller) -> 8 | $scope.vehicleDialog = -> 9 | controller.modal.open 10 | templateUrl: '/views/user/vehicle-list-modal.html' 11 | controller: 'alertController as controller' 12 | resolve: 13 | record: -> 14 | $scope.user 15 | modalOptions: -> 16 | options = 17 | title: 'Upload mission to vehicle' 18 | description: '' 19 | action: '' 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /src/scripts/directives/userSummary.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'userSummary', ['$window', ($window) -> 2 | restrict: 'E' 3 | templateUrl: '/views/directives/user-summary.html' 4 | scope: 5 | user: '=' 6 | ] 7 | -------------------------------------------------------------------------------- /src/scripts/directives/vehicleSummary.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').directive 'vehicleSummary', ['$window', ($window) -> 2 | restrict: 'A' 3 | templateUrl: '/views/directives/vehicle-summary.html' 4 | controller: 'vehicleController as controller' 5 | scope: 6 | vehicle: '=' 7 | ] 8 | -------------------------------------------------------------------------------- /src/scripts/filters/capitalize.coffee: -------------------------------------------------------------------------------- 1 | class Capitalize 2 | constructor: (@$filter) -> 3 | return (words) -> 4 | (words.split(' ').map (word) -> word.charAt(0).toUpperCase() + word.slice(1)).join('') 5 | 6 | angular.module('app').filter 'capitalize', ['$filter', Capitalize] 7 | -------------------------------------------------------------------------------- /src/scripts/filters/duration.coffee: -------------------------------------------------------------------------------- 1 | class Duration 2 | constructor: (@$log, @$filter) -> 3 | return (seconds) -> 4 | sec_num = parseInt(seconds, 10) 5 | hours = Math.floor(sec_num / 3600) 6 | minutes = Math.floor((sec_num - (hours * 3600)) / 60) 7 | seconds = sec_num - (hours * 3600) - (minutes * 60) 8 | 9 | hours = "0" + hours if hours < 10 10 | minutes = "0" + minutes if minutes < 10 11 | seconds = "0" + seconds if seconds < 10 12 | 13 | hours + ':' + minutes + ':' + seconds 14 | 15 | angular.module('app').filter 'duration', ['$log', '$filter', Duration] 16 | -------------------------------------------------------------------------------- /src/scripts/filters/toMapIcon.coffee: -------------------------------------------------------------------------------- 1 | 2 | angular.module('app').filter 'toMapIcon', () -> 3 | (vehicleTypeStr) -> 4 | if vehicleTypeStr == "fixed-wing" 5 | "airport" 6 | else 7 | "heliport" 8 | -------------------------------------------------------------------------------- /src/scripts/filters/twitterfy.coffee: -------------------------------------------------------------------------------- 1 | class Filter 2 | constructor: (@$log) -> 3 | return (username) -> 4 | "@#{username}" 5 | 6 | angular.module('app').filter 'twitterfy', ['$log', Filter] 7 | -------------------------------------------------------------------------------- /src/scripts/interceptors/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | class Interceptor 2 | constructor: ($log, $rootScope, $q) -> 3 | return { 4 | response: (response) -> 5 | $rootScope.$broadcast "success:#{response.status}", response 6 | 7 | response 8 | responseError: (response) -> 9 | $rootScope.$broadcast "error:#{response.status}", response 10 | 11 | $q.reject response 12 | } 13 | 14 | class Config 15 | constructor: ($httpProvider) -> 16 | $httpProvider.interceptors.push ['$log', '$rootScope', '$q', Interceptor] 17 | 18 | angular.module('app').config ['$httpProvider', Config] 19 | -------------------------------------------------------------------------------- /src/scripts/libs/ladda.js: -------------------------------------------------------------------------------- 1 | var Ladda = (function() { 2 | "use strict"; 3 | var ALL_INSTANCES = []; 4 | function create(button) { 5 | if (typeof button === "undefined") { 6 | console.warn("Ladda button target must be defined."); 7 | return; 8 | } 9 | if (!button.querySelector(".ladda-label")) { 10 | button.innerHTML = '' + button.innerHTML + ""; 11 | } 12 | var spinner = createSpinner(button); 13 | var spinnerWrapper = document.createElement("span"); 14 | spinnerWrapper.className = "ladda-spinner"; 15 | button.appendChild(spinnerWrapper); 16 | var timer; 17 | var instance = { 18 | start: function() { 19 | button.setAttribute("disabled", ""); 20 | button.setAttribute("data-loading", ""); 21 | clearTimeout(timer); 22 | spinner.spin(spinnerWrapper); 23 | this.setProgress(0); 24 | return this; 25 | }, 26 | startAfter: function(delay) { 27 | clearTimeout(timer); 28 | timer = setTimeout(function() { 29 | instance.start(); 30 | }, delay); 31 | return this; 32 | }, 33 | stop: function() { 34 | button.removeAttribute("disabled"); 35 | button.removeAttribute("data-loading"); 36 | clearTimeout(timer); 37 | timer = setTimeout(function() { 38 | spinner.stop(); 39 | }, 1e3); 40 | return this; 41 | }, 42 | toggle: function() { 43 | if (this.isLoading()) { 44 | this.stop(); 45 | } else { 46 | this.start(); 47 | } 48 | return this; 49 | }, 50 | setProgress: function(progress) { 51 | progress = Math.max(Math.min(progress, 1), 0); 52 | var progressElement = button.querySelector(".ladda-progress"); 53 | if (progress === 0 && progressElement && progressElement.parentNode) { 54 | progressElement.parentNode.removeChild(progressElement); 55 | } else { 56 | if (!progressElement) { 57 | progressElement = document.createElement("div"); 58 | progressElement.className = "ladda-progress"; 59 | button.appendChild(progressElement); 60 | } 61 | progressElement.style.width = (progress || 0) * button.offsetWidth + "px"; 62 | } 63 | }, 64 | enable: function() { 65 | this.stop(); 66 | return this; 67 | }, 68 | disable: function() { 69 | this.stop(); 70 | button.setAttribute("disabled", ""); 71 | return this; 72 | }, 73 | isLoading: function() { 74 | return button.hasAttribute("data-loading"); 75 | } 76 | }; 77 | ALL_INSTANCES.push(instance); 78 | return instance; 79 | } 80 | function bind(target, options) { 81 | options = options || {}; 82 | var targets = []; 83 | if (typeof target === "string") { 84 | targets = toArray(document.querySelectorAll(target)); 85 | } else if (typeof target === "object" && typeof target.nodeName === "string") { 86 | targets = [ target ]; 87 | } 88 | for (var i = 0, len = targets.length; i < len; i++) { 89 | (function() { 90 | var element = targets[i]; 91 | if (typeof element.addEventListener === "function") { 92 | var instance = create(element); 93 | var timeout = -1; 94 | element.addEventListener("click", function() { 95 | instance.startAfter(1); 96 | if (typeof options.timeout === "number") { 97 | clearTimeout(timeout); 98 | timeout = setTimeout(instance.stop, options.timeout); 99 | } 100 | if (typeof options.callback === "function") { 101 | options.callback.apply(null, [ instance ]); 102 | } 103 | }, false); 104 | } 105 | })(); 106 | } 107 | } 108 | function stopAll() { 109 | for (var i = 0, len = ALL_INSTANCES.length; i < len; i++) { 110 | ALL_INSTANCES[i].stop(); 111 | } 112 | } 113 | function createSpinner(button) { 114 | var height = button.offsetHeight, spinnerColor; 115 | if (height > 32) { 116 | height *= .8; 117 | } 118 | if (button.hasAttribute("data-spinner-size")) { 119 | height = parseInt(button.getAttribute("data-spinner-size"), 10); 120 | } 121 | if (button.hasAttribute("data-spinner-color")) { 122 | spinnerColor = button.getAttribute("data-spinner-color"); 123 | } 124 | var lines = 12, radius = height * .2, length = radius * .6, width = radius < 7 ? 2 : 3; 125 | return new Spinner({ 126 | color: spinnerColor || "#fff", 127 | lines: lines, 128 | radius: radius, 129 | length: length, 130 | width: width, 131 | zIndex: "auto", 132 | top: "auto", 133 | left: "auto", 134 | className: "" 135 | }); 136 | } 137 | function toArray(nodes) { 138 | var a = []; 139 | for (var i = 0; i < nodes.length; i++) { 140 | a.push(nodes[i]); 141 | } 142 | return a; 143 | } 144 | return { 145 | bind: bind, 146 | create: create, 147 | stopAll: stopAll 148 | }; 149 | }()); 150 | -------------------------------------------------------------------------------- /src/scripts/libs/spin.js: -------------------------------------------------------------------------------- 1 | var Spinner = (function() { 2 | "use strict"; 3 | var prefixes = [ "webkit", "Moz", "ms", "O" ], animations = {}, useCssAnimations; 4 | function createEl(tag, prop) { 5 | var el = document.createElement(tag || "div"), n; 6 | for (n in prop) el[n] = prop[n]; 7 | return el; 8 | } 9 | function ins(parent) { 10 | for (var i = 1, n = arguments.length; i < n; i++) parent.appendChild(arguments[i]); 11 | return parent; 12 | } 13 | var sheet = function() { 14 | var el = createEl("style", { 15 | type: "text/css" 16 | }); 17 | ins(document.getElementsByTagName("head")[0], el); 18 | return el.sheet || el.styleSheet; 19 | }(); 20 | function addAnimation(alpha, trail, i, lines) { 21 | var name = [ "opacity", trail, ~~(alpha * 100), i, lines ].join("-"), start = .01 + i / lines * 100, z = Math.max(1 - (1 - alpha) / trail * (100 - start), alpha), prefix = useCssAnimations.substring(0, useCssAnimations.indexOf("Animation")).toLowerCase(), pre = prefix && "-" + prefix + "-" || ""; 22 | if (!animations[name]) { 23 | sheet.insertRule("@" + pre + "keyframes " + name + "{" + "0%{opacity:" + z + "}" + start + "%{opacity:" + alpha + "}" + (start + .01) + "%{opacity:1}" + (start + trail) % 100 + "%{opacity:" + alpha + "}" + "100%{opacity:" + z + "}" + "}", sheet.cssRules.length); 24 | animations[name] = 1; 25 | } 26 | return name; 27 | } 28 | function vendor(el, prop) { 29 | var s = el.style, pp, i; 30 | if (s[prop] !== undefined) return prop; 31 | prop = prop.charAt(0).toUpperCase() + prop.slice(1); 32 | for (i = 0; i < prefixes.length; i++) { 33 | pp = prefixes[i] + prop; 34 | if (s[pp] !== undefined) return pp; 35 | } 36 | } 37 | function css(el, prop) { 38 | for (var n in prop) el.style[vendor(el, n) || n] = prop[n]; 39 | return el; 40 | } 41 | function merge(obj) { 42 | for (var i = 1; i < arguments.length; i++) { 43 | var def = arguments[i]; 44 | for (var n in def) if (obj[n] === undefined) obj[n] = def[n]; 45 | } 46 | return obj; 47 | } 48 | function pos(el) { 49 | var o = { 50 | x: el.offsetLeft, 51 | y: el.offsetTop 52 | }; 53 | while (el = el.offsetParent) o.x += el.offsetLeft, o.y += el.offsetTop; 54 | return o; 55 | } 56 | var defaults = { 57 | lines: 12, 58 | length: 7, 59 | width: 5, 60 | radius: 10, 61 | rotate: 0, 62 | corners: 1, 63 | color: "#000", 64 | direction: 1, 65 | speed: 1, 66 | trail: 100, 67 | opacity: 1 / 4, 68 | fps: 20, 69 | zIndex: 2e9, 70 | className: "spinner", 71 | top: "auto", 72 | left: "auto", 73 | position: "relative" 74 | }; 75 | function Spinner(o) { 76 | if (typeof this == "undefined") return new Spinner(o); 77 | this.opts = merge(o || {}, Spinner.defaults, defaults); 78 | } 79 | Spinner.defaults = {}; 80 | merge(Spinner.prototype, { 81 | spin: function(target) { 82 | this.stop(); 83 | var self = this, o = self.opts, el = self.el = css(createEl(0, { 84 | className: o.className 85 | }), { 86 | position: o.position, 87 | width: 0, 88 | zIndex: o.zIndex 89 | }), mid = o.radius + o.length + o.width, ep, tp; 90 | if (target) { 91 | target.insertBefore(el, target.firstChild || null); 92 | tp = pos(target); 93 | ep = pos(el); 94 | css(el, { 95 | left: (o.left == "auto" ? tp.x - ep.x + (target.offsetWidth >> 1) : parseInt(o.left, 10) + mid) + "px", 96 | top: (o.top == "auto" ? tp.y - ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + "px" 97 | }); 98 | } 99 | el.setAttribute("role", "progressbar"); 100 | self.lines(el, self.opts); 101 | if (!useCssAnimations) { 102 | var i = 0, start = (o.lines - 1) * (1 - o.direction) / 2, alpha, fps = o.fps, f = fps / o.speed, ostep = (1 - o.opacity) / (f * o.trail / 100), astep = f / o.lines; 103 | (function anim() { 104 | i++; 105 | for (var j = 0; j < o.lines; j++) { 106 | alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity); 107 | self.opacity(el, j * o.direction + start, alpha, o); 108 | } 109 | self.timeout = self.el && setTimeout(anim, ~~(1e3 / fps)); 110 | })(); 111 | } 112 | return self; 113 | }, 114 | stop: function() { 115 | var el = this.el; 116 | if (el) { 117 | clearTimeout(this.timeout); 118 | if (el.parentNode) el.parentNode.removeChild(el); 119 | this.el = undefined; 120 | } 121 | return this; 122 | }, 123 | lines: function(el, o) { 124 | var i = 0, start = (o.lines - 1) * (1 - o.direction) / 2, seg; 125 | function fill(color, shadow) { 126 | return css(createEl(), { 127 | position: "absolute", 128 | width: o.length + o.width + "px", 129 | height: o.width + "px", 130 | background: color, 131 | boxShadow: shadow, 132 | transformOrigin: "left", 133 | transform: "rotate(" + ~~(360 / o.lines * i + o.rotate) + "deg) translate(" + o.radius + "px" + ",0)", 134 | borderRadius: (o.corners * o.width >> 1) + "px" 135 | }); 136 | } 137 | for (;i < o.lines; i++) { 138 | seg = css(createEl(), { 139 | position: "absolute", 140 | top: 1 + ~(o.width / 2) + "px", 141 | transform: o.hwaccel ? "translate3d(0,0,0)" : "", 142 | opacity: o.opacity, 143 | animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + " " + 1 / o.speed + "s linear infinite" 144 | }); 145 | if (o.shadow) ins(seg, css(fill("#000", "0 0 4px " + "#000"), { 146 | top: 2 + "px" 147 | })); 148 | ins(el, ins(seg, fill(o.color, "0 0 1px rgba(0,0,0,.1)"))); 149 | } 150 | return el; 151 | }, 152 | opacity: function(el, i, val) { 153 | if (i < el.childNodes.length) el.childNodes[i].style.opacity = val; 154 | } 155 | }); 156 | function initVML() { 157 | function vml(tag, attr) { 158 | return createEl("<" + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr); 159 | } 160 | sheet.addRule(".spin-vml", "behavior:url(#default#VML)"); 161 | Spinner.prototype.lines = function(el, o) { 162 | var r = o.length + o.width, s = 2 * r; 163 | function grp() { 164 | return css(vml("group", { 165 | coordsize: s + " " + s, 166 | coordorigin: -r + " " + -r 167 | }), { 168 | width: s, 169 | height: s 170 | }); 171 | } 172 | var margin = -(o.width + o.length) * 2 + "px", g = css(grp(), { 173 | position: "absolute", 174 | top: margin, 175 | left: margin 176 | }), i; 177 | function seg(i, dx, filter) { 178 | ins(g, ins(css(grp(), { 179 | rotation: 360 / o.lines * i + "deg", 180 | left: ~~dx 181 | }), ins(css(vml("roundrect", { 182 | arcsize: o.corners 183 | }), { 184 | width: r, 185 | height: o.width, 186 | left: o.radius, 187 | top: -o.width >> 1, 188 | filter: filter 189 | }), vml("fill", { 190 | color: o.color, 191 | opacity: o.opacity 192 | }), vml("stroke", { 193 | opacity: 0 194 | })))); 195 | } 196 | if (o.shadow) for (i = 1; i <= o.lines; i++) seg(i, -2, "progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)"); 197 | for (i = 1; i <= o.lines; i++) seg(i); 198 | return ins(el, g); 199 | }; 200 | Spinner.prototype.opacity = function(el, i, val, o) { 201 | var c = el.firstChild; 202 | o = o.shadow && o.lines || 0; 203 | if (c && i + o < c.childNodes.length) { 204 | c = c.childNodes[i + o]; 205 | c = c && c.firstChild; 206 | c = c && c.firstChild; 207 | if (c) c.opacity = val; 208 | } 209 | }; 210 | } 211 | var probe = css(createEl("group"), { 212 | behavior: "url(#default#VML)" 213 | }); 214 | if (!vendor(probe, "transform") && probe.adj) initVML(); else useCssAnimations = vendor(probe, "animation"); 215 | return Spinner; 216 | }()); 217 | -------------------------------------------------------------------------------- /src/scripts/loadingIndicator.coffee: -------------------------------------------------------------------------------- 1 | angular.module('app').config ['$httpProvider', ($httpProvider) -> 2 | $httpProvider.interceptors.push ['$q', '$rootScope', ($q, $rootScope) -> 3 | 'request': (config) -> 4 | $rootScope.$broadcast('loading-started', config) unless /html/.test(config.url) 5 | return config || $q.when(config) 6 | 'response': (response) -> 7 | $rootScope.$broadcast('loading-complete', response.config) unless /html/.test(response.config.url) 8 | return response || $q.when(response) 9 | ] 10 | ] 11 | -------------------------------------------------------------------------------- /src/scripts/routes.coffee: -------------------------------------------------------------------------------- 1 | class Config 2 | constructor: ($routeProvider, $locationProvider) -> 3 | $locationProvider.html5Mode(true) 4 | 5 | $routeProvider 6 | .when '/create', 7 | templateUrl: '/views/login/user-create.html' 8 | .when '/confirm/:id/:verification', 9 | templateUrl: '/views/login/email-confirm.html' 10 | .when '/reset/:id/:verification', 11 | templateUrl: '/views/login/password-reset-confirm.html' 12 | .when '/reset', 13 | templateUrl: '/views/login/password-reset.html' 14 | .when '/logout', 15 | templateUrl: '/views/login/logout.html' 16 | .when '/login', 17 | templateUrl: '/views/login/login-window.html' 18 | .when '/user', 19 | templateUrl: '/views/user/list-all.html' 20 | .when '/user/:id', 21 | controller: 'userDetailController as controller' 22 | templateUrl: '/views/user/detail.html' 23 | resolve: 24 | resolvedUser: ['$route', 'userService', ($route, userService) -> 25 | userService.getId($route.current.params.id) 26 | ] 27 | .when '/vehicle', 28 | templateUrl: '/views/vehicle-list.html' 29 | .when '/vehicle/:id', 30 | templateUrl: '/views/vehicle-detail.html' 31 | controller: 'vehicleDetailController as controller' 32 | resolve: 33 | resolvedVehicle: ['$route', 'vehicleService', ($route, vehicleService) -> 34 | vehicleService.getId($route.current.params.id) 35 | ] 36 | .when '/mission', 37 | title: 'Recent' 38 | templateUrl: '/views/mission/list-window.html' 39 | controller: 'missionController as controller' 40 | resolve: 41 | preFetchedMissions: ['$route', 'missionService', ($route, missionService) -> 42 | missionService.getAllMissions({order_by: "createdAt", order_dir: "desc", page_size: 12}) 43 | ] 44 | .when '/mission/:id', 45 | controller: 'missionDetailController as controller' 46 | title: 'Detail' 47 | templateUrl: '/views/mission/detail-window.html' 48 | .when '/parameters/:id', 49 | templateUrl: '/views/mission/parameters-window.html' 50 | .when '/analysis/:id', 51 | templateUrl: '/views/mission/analysis-window.html' 52 | .when '/doarama/:id', 53 | templateUrl: '/views/mission/doarama-window.html' 54 | .when '/mission/:id/plot', 55 | title: 'Plot' 56 | templateUrl: '/views/mission/plot-window.html' 57 | controller: 'missionPlotController as controller' 58 | resolve: 59 | missionData: ['$route', 'missionService', ($route, missionService) -> 60 | missionService.getId($route.current.params.id).then missionService.fixMissionRecord 61 | ] 62 | plotData: ['$route', 'missionService', ($route, missionService) -> 63 | missionService.get_plotdata($route.current.params.id).then (response) -> 64 | response.data 65 | ] 66 | .when '/github/:id', 67 | controller: 'gitHubController' 68 | .when '/admin', 69 | templateUrl: '/views/admin-screen.html' 70 | .when '/about', 71 | controller: 'aboutController' 72 | templateUrl: '/views/about.html' 73 | .when '/', 74 | title: 'World' 75 | templateUrl: '/views/site.html' 76 | .otherwise 77 | redirectTo: '/' 78 | 79 | angular.module('app').config ['$routeProvider', '$locationProvider', Config] 80 | angular.module('app').config ['$logProvider', ($logProvider) -> $logProvider.debugEnabled(window.debugEnabled)] 81 | 82 | angular.module('app').run ['$location', '$rootScope', (location, rootScope) -> 83 | rootScope.$on '$routeChangeSuccess', (event, current, previous) -> 84 | rootScope.title = if current?$$route?title? then " - " + current.$$route.title else "" 85 | ] 86 | -------------------------------------------------------------------------------- /src/styles/ladda-themeless.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Ladda 3 | * http://lab.hakim.se/ladda 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2013 Hakim El Hattab, http://hakim.se 7 | */ 8 | /************************************* 9 | * CONFIG 10 | */ 11 | /************************************* 12 | * MIXINS 13 | */ 14 | /************************************* 15 | * BUTTON BASE 16 | */ 17 | .ladda-button { 18 | position: relative; } 19 | 20 | /* Spinner animation */ 21 | .ladda-button .ladda-spinner { 22 | position: absolute; 23 | z-index: 2; 24 | display: inline-block; 25 | width: 32px; 26 | height: 32px; 27 | top: 50%; 28 | margin-top: -16px; 29 | opacity: 0; 30 | pointer-events: none; } 31 | 32 | /* Button label */ 33 | .ladda-button .ladda-label { 34 | position: relative; 35 | z-index: 3; } 36 | 37 | /* Progress bar */ 38 | .ladda-button .ladda-progress { 39 | position: absolute; 40 | width: 0; 41 | height: 100%; 42 | left: 0; 43 | top: 0; 44 | background: rgba(0, 0, 0, 0.2); 45 | visibility: hidden; 46 | opacity: 0; 47 | -webkit-transition: 0.1s linear all !important; 48 | -moz-transition: 0.1s linear all !important; 49 | -ms-transition: 0.1s linear all !important; 50 | -o-transition: 0.1s linear all !important; 51 | transition: 0.1s linear all !important; } 52 | 53 | .ladda-button[data-loading] .ladda-progress { 54 | opacity: 1; 55 | visibility: visible; } 56 | 57 | /************************************* 58 | * EASING 59 | */ 60 | .ladda-button, 61 | .ladda-button .ladda-spinner, 62 | .ladda-button .ladda-label { 63 | -webkit-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important; 64 | -moz-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important; 65 | -ms-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important; 66 | -o-transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important; 67 | transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important; } 68 | 69 | .ladda-button[data-style=zoom-in], 70 | .ladda-button[data-style=zoom-in] .ladda-spinner, 71 | .ladda-button[data-style=zoom-in] .ladda-label, 72 | .ladda-button[data-style=zoom-out], 73 | .ladda-button[data-style=zoom-out] .ladda-spinner, 74 | .ladda-button[data-style=zoom-out] .ladda-label { 75 | -webkit-transition: 0.3s ease all !important; 76 | -moz-transition: 0.3s ease all !important; 77 | -ms-transition: 0.3s ease all !important; 78 | -o-transition: 0.3s ease all !important; 79 | transition: 0.3s ease all !important; } 80 | 81 | /************************************* 82 | * EXPAND LEFT 83 | */ 84 | .ladda-button[data-style=expand-right] .ladda-spinner { 85 | right: 14px; } 86 | .ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner, .ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner { 87 | right: 4px; } 88 | .ladda-button[data-style=expand-right][data-loading] { 89 | padding-right: 56px; } 90 | .ladda-button[data-style=expand-right][data-loading] .ladda-spinner { 91 | opacity: 1; } 92 | .ladda-button[data-style=expand-right][data-loading][data-size="s"], .ladda-button[data-style=expand-right][data-loading][data-size="xs"] { 93 | padding-right: 40px; } 94 | 95 | /************************************* 96 | * EXPAND RIGHT 97 | */ 98 | .ladda-button[data-style=expand-left] .ladda-spinner { 99 | left: 14px; } 100 | .ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner, .ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner { 101 | left: 4px; } 102 | .ladda-button[data-style=expand-left][data-loading] { 103 | padding-left: 56px; } 104 | .ladda-button[data-style=expand-left][data-loading] .ladda-spinner { 105 | opacity: 1; } 106 | .ladda-button[data-style=expand-left][data-loading][data-size="s"], .ladda-button[data-style=expand-left][data-loading][data-size="xs"] { 107 | padding-left: 40px; } 108 | 109 | /************************************* 110 | * EXPAND UP 111 | */ 112 | .ladda-button[data-style=expand-up] { 113 | overflow: hidden; } 114 | .ladda-button[data-style=expand-up] .ladda-spinner { 115 | top: -32px; 116 | left: 50%; 117 | margin-left: -16px; } 118 | .ladda-button[data-style=expand-up][data-loading] { 119 | padding-top: 54px; } 120 | .ladda-button[data-style=expand-up][data-loading] .ladda-spinner { 121 | opacity: 1; 122 | top: 14px; 123 | margin-top: 0; } 124 | .ladda-button[data-style=expand-up][data-loading][data-size="s"], .ladda-button[data-style=expand-up][data-loading][data-size="xs"] { 125 | padding-top: 32px; } 126 | .ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner, .ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner { 127 | top: 4px; } 128 | 129 | /************************************* 130 | * EXPAND DOWN 131 | */ 132 | .ladda-button[data-style=expand-down] { 133 | overflow: hidden; } 134 | .ladda-button[data-style=expand-down] .ladda-spinner { 135 | top: 62px; 136 | left: 50%; 137 | margin-left: -16px; } 138 | .ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner, .ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner { 139 | top: 40px; } 140 | .ladda-button[data-style=expand-down][data-loading] { 141 | padding-bottom: 54px; } 142 | .ladda-button[data-style=expand-down][data-loading] .ladda-spinner { 143 | opacity: 1; } 144 | .ladda-button[data-style=expand-down][data-loading][data-size="s"], .ladda-button[data-style=expand-down][data-loading][data-size="xs"] { 145 | padding-bottom: 32px; } 146 | 147 | /************************************* 148 | * SLIDE LEFT 149 | */ 150 | .ladda-button[data-style=slide-left] { 151 | overflow: hidden; } 152 | .ladda-button[data-style=slide-left] .ladda-label { 153 | position: relative; } 154 | .ladda-button[data-style=slide-left] .ladda-spinner { 155 | left: 100%; 156 | margin-left: -16px; } 157 | .ladda-button[data-style=slide-left][data-loading] .ladda-label { 158 | opacity: 0; 159 | left: -100%; } 160 | .ladda-button[data-style=slide-left][data-loading] .ladda-spinner { 161 | opacity: 1; 162 | left: 50%; } 163 | 164 | /************************************* 165 | * SLIDE RIGHT 166 | */ 167 | .ladda-button[data-style=slide-right] { 168 | overflow: hidden; } 169 | .ladda-button[data-style=slide-right] .ladda-label { 170 | position: relative; } 171 | .ladda-button[data-style=slide-right] .ladda-spinner { 172 | right: 100%; 173 | margin-left: -16px; } 174 | .ladda-button[data-style=slide-right][data-loading] .ladda-label { 175 | opacity: 0; 176 | left: 100%; } 177 | .ladda-button[data-style=slide-right][data-loading] .ladda-spinner { 178 | opacity: 1; 179 | left: 50%; } 180 | 181 | /************************************* 182 | * SLIDE UP 183 | */ 184 | .ladda-button[data-style=slide-up] { 185 | overflow: hidden; } 186 | .ladda-button[data-style=slide-up] .ladda-label { 187 | position: relative; } 188 | .ladda-button[data-style=slide-up] .ladda-spinner { 189 | left: 50%; 190 | margin-left: -16px; 191 | margin-top: 1em; } 192 | .ladda-button[data-style=slide-up][data-loading] .ladda-label { 193 | opacity: 0; 194 | top: -1em; } 195 | .ladda-button[data-style=slide-up][data-loading] .ladda-spinner { 196 | opacity: 1; 197 | margin-top: -16px; } 198 | 199 | /************************************* 200 | * SLIDE DOWN 201 | */ 202 | .ladda-button[data-style=slide-down] { 203 | overflow: hidden; } 204 | .ladda-button[data-style=slide-down] .ladda-label { 205 | position: relative; } 206 | .ladda-button[data-style=slide-down] .ladda-spinner { 207 | left: 50%; 208 | margin-left: -16px; 209 | margin-top: -2em; } 210 | .ladda-button[data-style=slide-down][data-loading] .ladda-label { 211 | opacity: 0; 212 | top: 1em; } 213 | .ladda-button[data-style=slide-down][data-loading] .ladda-spinner { 214 | opacity: 1; 215 | margin-top: -16px; } 216 | 217 | /************************************* 218 | * ZOOM-OUT 219 | */ 220 | .ladda-button[data-style=zoom-out] { 221 | overflow: hidden; } 222 | 223 | .ladda-button[data-style=zoom-out] .ladda-spinner { 224 | left: 50%; 225 | margin-left: -16px; 226 | -webkit-transform: scale(2.5); 227 | -moz-transform: scale(2.5); 228 | -ms-transform: scale(2.5); 229 | -o-transform: scale(2.5); 230 | transform: scale(2.5); } 231 | 232 | .ladda-button[data-style=zoom-out] .ladda-label { 233 | position: relative; 234 | display: inline-block; } 235 | 236 | .ladda-button[data-style=zoom-out][data-loading] .ladda-label { 237 | opacity: 0; 238 | -webkit-transform: scale(0.5); 239 | -moz-transform: scale(0.5); 240 | -ms-transform: scale(0.5); 241 | -o-transform: scale(0.5); 242 | transform: scale(0.5); } 243 | 244 | .ladda-button[data-style=zoom-out][data-loading] .ladda-spinner { 245 | opacity: 1; 246 | -webkit-transform: none; 247 | -moz-transform: none; 248 | -ms-transform: none; 249 | -o-transform: none; 250 | transform: none; } 251 | 252 | /************************************* 253 | * ZOOM-IN 254 | */ 255 | .ladda-button[data-style=zoom-in] { 256 | overflow: hidden; } 257 | 258 | .ladda-button[data-style=zoom-in] .ladda-spinner { 259 | left: 50%; 260 | margin-left: -16px; 261 | -webkit-transform: scale(0.2); 262 | -moz-transform: scale(0.2); 263 | -ms-transform: scale(0.2); 264 | -o-transform: scale(0.2); 265 | transform: scale(0.2); } 266 | 267 | .ladda-button[data-style=zoom-in] .ladda-label { 268 | position: relative; 269 | display: inline-block; } 270 | 271 | .ladda-button[data-style=zoom-in][data-loading] .ladda-label { 272 | opacity: 0; 273 | -webkit-transform: scale(2.2); 274 | -moz-transform: scale(2.2); 275 | -ms-transform: scale(2.2); 276 | -o-transform: scale(2.2); 277 | transform: scale(2.2); } 278 | 279 | .ladda-button[data-style=zoom-in][data-loading] .ladda-spinner { 280 | opacity: 1; 281 | -webkit-transform: none; 282 | -moz-transform: none; 283 | -ms-transform: none; 284 | -o-transform: none; 285 | transform: none; } 286 | 287 | /************************************* 288 | * CONTRACT 289 | */ 290 | .ladda-button[data-style=contract] { 291 | overflow: hidden; 292 | width: 100px; } 293 | 294 | .ladda-button[data-style=contract] .ladda-spinner { 295 | left: 50%; 296 | margin-left: -16px; } 297 | 298 | .ladda-button[data-style=contract][data-loading] { 299 | border-radius: 50%; 300 | width: 52px; } 301 | 302 | .ladda-button[data-style=contract][data-loading] .ladda-label { 303 | opacity: 0; } 304 | 305 | .ladda-button[data-style=contract][data-loading] .ladda-spinner { 306 | opacity: 1; } 307 | 308 | /************************************* 309 | * OVERLAY 310 | */ 311 | .ladda-button[data-style=contract-overlay] { 312 | overflow: hidden; 313 | width: 100px; 314 | box-shadow: 0px 0px 0px 3000px rgba(0, 0, 0, 0); } 315 | 316 | .ladda-button[data-style=contract-overlay] .ladda-spinner { 317 | left: 50%; 318 | margin-left: -16px; } 319 | 320 | .ladda-button[data-style=contract-overlay][data-loading] { 321 | border-radius: 50%; 322 | width: 52px; 323 | /*outline: 10000px solid rgba( 0, 0, 0, 0.5 );*/ 324 | box-shadow: 0px 0px 0px 3000px rgba(0, 0, 0, 0.8); } 325 | 326 | .ladda-button[data-style=contract-overlay][data-loading] .ladda-label { 327 | opacity: 0; } 328 | 329 | .ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner { 330 | opacity: 1; } 331 | -------------------------------------------------------------------------------- /src/template/accordion/accordion-group.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{heading}} 5 |

6 |
7 |
8 |
9 |
10 |
-------------------------------------------------------------------------------- /src/template/accordion/accordion.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/template/alert/alert.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/template/carousel/carousel.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/template/carousel/slide.html: -------------------------------------------------------------------------------- 1 |
8 | -------------------------------------------------------------------------------- /src/template/datepicker/datepicker.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /src/template/datepicker/day.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 |
{{label.abbr}}
{{ weekNumbers[$index] }} 17 | 18 |
22 | -------------------------------------------------------------------------------- /src/template/datepicker/month.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
12 | 13 |
17 | -------------------------------------------------------------------------------- /src/template/datepicker/popup.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/template/datepicker/year.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
12 | 13 |
17 | -------------------------------------------------------------------------------- /src/template/modal/backdrop.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/template/modal/window.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/template/pagination/pager.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/template/pagination/pagination.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/template/popover/popover.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/template/progressbar/bar.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/template/progressbar/progress.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/template/progressbar/progressbar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
-------------------------------------------------------------------------------- /src/template/rating/rating.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ({{ $index < value ? '*' : ' ' }}) 4 | 5 | -------------------------------------------------------------------------------- /src/template/tabs/tab.html: -------------------------------------------------------------------------------- 1 |
  • 2 | {{heading}} 3 |
  • 4 | -------------------------------------------------------------------------------- /src/template/tabs/tabset.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 |
    5 |
    9 |
    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /src/template/timepicker/timepicker.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
     
    11 | 12 | : 15 | 16 |
     
    27 | -------------------------------------------------------------------------------- /src/template/tooltip/tooltip-html-unsafe-popup.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | -------------------------------------------------------------------------------- /src/template/tooltip/tooltip-popup.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | -------------------------------------------------------------------------------- /src/template/typeahead/typeahead-match.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/template/typeahead/typeahead-popup.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/about.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    About DroneShare

    4 |

    5 | This is the beta release of Droneshare. Droneshare is a mission viewing and sharing application that works with ground control applications to let you share your mission data. 6 |

    7 |

    8 | This project is open source, if you would like to improve it, it is probably best to start at our github repo. 9 |

    10 |

    Terms of service

    11 |

    12 | Alas, we haven't written one yet, but we should be getting to this soon. But in short: this is a beta and the service might have bugs. 13 |

    14 |

    15 | We also use the really cool Doarama project for our movie views, their terms of service are here. 16 |

    17 |

    18 | If you have questions, please contact Support. 19 |

    20 |

    Maps

    21 |

    22 | Droneshare uses maps from the OpenStreetMap project and our friends at Mapbox. 23 |

    24 |

    25 | In fact, Mapbox has been experimenting with providing maps that show user contributed areas where flying drones is not recommended, If you would like to improve this map please see their project here. 26 |

    27 |

    28 | 29 | Octocat 30 | 31 |

    32 |
    33 |
    34 | -------------------------------------------------------------------------------- /src/views/admin-screen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
    5 |
    6 |
    7 |

    8 | 9 |
    10 | 11 |
    12 | 13 | 14 |
    15 | 16 |
    17 | 18 | 19 |
    20 | 21 |
    22 | 23 | 24 |
    25 | 26 |

    Debug info: {{controller.debugInfo}}

    27 | 30 | 31 |
    32 |
    Server log
    33 |
      34 |
    • {{line}}
    • 35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 | -------------------------------------------------------------------------------- /src/views/body.html: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /src/views/directives/access-code-dropdown.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/views/directives/alert-modal.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/views/directives/featured-flights.html: -------------------------------------------------------------------------------- 1 |
    2 | featured flights stuff goes here 3 |
    4 | 5 | -------------------------------------------------------------------------------- /src/views/directives/live-map.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/views/directives/mission-list.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Missions:

    4 |
    5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 | 12 |
    13 | 14 |
    15 | 16 |
    17 |
    18 | 19 |
    20 | 23 |
    24 | 25 |
    26 |
    {{filters.field.units}}
    27 |
    28 | 29 |
    30 | 31 | 32 |
    33 | 34 | 44 | 45 | 56 | 57 |
    58 | Sort by: Most recent 59 |
    60 | 61 |
    62 |
    63 | 64 | 65 |
    66 |
    67 |
    68 |
    69 |
    70 |
    71 | Loading missions... 72 |
    73 | 76 |
    77 | -------------------------------------------------------------------------------- /src/views/directives/mission-plot.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/views/directives/mission-summary.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/views/directives/navbar.html: -------------------------------------------------------------------------------- 1 | 34 |
    35 |
    36 | 41 |
    42 |
    43 | -------------------------------------------------------------------------------- /src/views/directives/recent-flights.html: -------------------------------------------------------------------------------- 1 |
    2 | recent flights stuff goes here 3 |
    4 | -------------------------------------------------------------------------------- /src/views/directives/tab.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    -------------------------------------------------------------------------------- /src/views/directives/tabs.html: -------------------------------------------------------------------------------- 1 |
    2 | 7 |
    8 |
    -------------------------------------------------------------------------------- /src/views/directives/upload-mission.html: -------------------------------------------------------------------------------- 1 | 2 | Upload Mission 3 | 4 | -------------------------------------------------------------------------------- /src/views/directives/user-summary.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    {{user.login}}

    4 | {{user.email}} 5 | {{user.fullName}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/directives/vehicle-summary.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 8 |
      9 |
    • Type: {{vehicle.vehicleType || "Unknown Type"}}
    • 10 |
    • Autopilot: {{vehicle.autopilotType || "Unknown Type"}}
    • 11 |
    • {{vehicle.missions.length}} Missions Flown
    • 12 |
    • Edit vehicle
    • 13 |
    • Remove vehicle
    • 14 |
    15 |
    16 |
    17 | -------------------------------------------------------------------------------- /src/views/footer.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/views/login/create-snippet.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | {{alert}} 4 | {{alert}} 5 | 6 | 7 |
    8 |

    Create account

    9 | 10 | 11 | 12 |
    13 | 14 | 15 |
    16 |
    17 | 18 | 19 |
    20 | 21 |
    22 | 23 | 24 |
    25 | 26 |
    27 | 28 | 29 |

    Use at least 7 characters and one digit.

    30 |
    31 | 32 |
    33 | Send service emails (feature announcements, etc...) 34 |
    35 | 36 |
    37 | 38 |
    39 | 40 | 41 | 44 |
    45 |
    46 | -------------------------------------------------------------------------------- /src/views/login/email-confirm.html: -------------------------------------------------------------------------------- 1 |

    Email confirmation

    2 | 3 |
    4 | {{alert}} 5 | {{alert}} 6 |
    7 | -------------------------------------------------------------------------------- /src/views/login/login-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 |
    5 |
    6 | 7 | {{alert}} 8 | {{alert}} 9 | 10 |

    Please log-in

    11 | 12 |
    13 | 14 | 15 |
    16 |
    17 | 18 | 19 |
    20 | 21 | 22 |
    23 | Remember me 24 |
    25 |
    26 |
    27 | 28 |
    29 |
    30 | 31 | 34 |
    35 |
    36 | 37 |
    38 | 43 |
    44 |
    45 | 46 | -------------------------------------------------------------------------------- /src/views/login/login-window.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 6 | -------------------------------------------------------------------------------- /src/views/login/logout.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | -------------------------------------------------------------------------------- /src/views/login/password-reset-confirm.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 |
    6 | 7 | {{alert}} 8 | {{alert}} 9 | 10 |

    You are almost there

    11 | 12 |
    13 | 14 | 15 |
    16 | 17 |
    18 |
    19 | 20 |
    21 |
    22 | 23 | 26 |
    27 |
    28 |
    29 |
    30 | -------------------------------------------------------------------------------- /src/views/login/password-reset.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 |
    6 | 7 | {{alert}} 8 | {{alert}} 9 | 10 |

    Forgotten password

    11 | So, you've forgotten your password? No problem, just enter your email address or login name 12 | and we'll send you an email you can use to pick a new password.

    13 | 14 |

    15 | 16 | 17 |
    18 | 19 |
    20 |
    21 | 22 |
    23 |
    24 | 25 | 28 |
    29 |
    30 |
    31 |
    32 | -------------------------------------------------------------------------------- /src/views/login/user-create.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 6 | -------------------------------------------------------------------------------- /src/views/mission/analysis-modal.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 10 | -------------------------------------------------------------------------------- /src/views/mission/analysis-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | This report is autogenerated by the LogAnalyzer tool. This tool is heuristically driven and might make 7 | mistakes or be unable to analyze some missions. If you are interested in improving this tool 8 | we welcome any contributions. For information on analyzing log data, please see our wiki. 9 | 10 | 11 | 12 | {{errorMessage}} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 |
    StatusTestNotes
    26 | 27 | 28 | 29 |
    {{r.status}}
    30 |
    {{r.name}}{{r.message}}
    36 | -------------------------------------------------------------------------------- /src/views/mission/analysis-window.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 | 5 | 6 | 7 |
    8 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /src/views/mission/detail-window.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{alert}} 4 | 5 |
    6 | 7 |
    8 | 9 |
    10 |
    11 |
    12 |
    13 | 14 |
    15 | 16 | 21 | 22 |
    23 | 26 | 29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 |
    36 | 37 |
    38 | 39 |
    40 | 41 |
    42 | {{record.summaryText}} 43 |
    44 | 45 |
    46 | Mission is live! 47 |
    48 |
    49 | {{record.dateString}} 50 |
    51 | 52 |
    53 |
    Mission time:
    54 |
    55 | {{record.flightDuration | duration}} 56 |
    57 |
    58 | 59 |
    Alt:
    60 |
    61 | {{record.maxAlt | number }} m 62 |
    63 |
    Speed:
    64 |
    65 | {{record.maxGroundspeed | number }} m/s 66 |
    67 |
    Airspeed:
    68 |
    69 | {{record.maxAirspeed | number }} m/s 70 |
    71 |
    72 |
    Software version {{record.softwareVersion}}
    73 |
    74 |
    75 |
    76 | 77 |
    78 |
    79 |
    80 | 83 | 86 | 87 | Plot 88 | 89 | 92 |
    93 |
    94 |
    95 | 96 |
    97 |
    98 | 121 |
    122 |
    123 | 124 |
    125 | 133 |
    134 | 135 |
    136 | 137 |
    138 |
    139 |
    140 | -------------------------------------------------------------------------------- /src/views/mission/doarama-modal.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 10 | -------------------------------------------------------------------------------- /src/views/mission/doarama-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/views/mission/doarama-window.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 | 5 | 6 | 7 |
    8 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /src/views/mission/list-window.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | -------------------------------------------------------------------------------- /src/views/mission/parameters-modal.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 10 | -------------------------------------------------------------------------------- /src/views/mission/parameters-snippet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Droneshare has detected parameters that are outside of the recommended 5 | range. The suspect values are marked in red - you should check on DIY Drones to figure out if you 6 | are doing a bad thing. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    IDValueDocumentation
    {{r.id}}{{r.value}}{{r.value}}{{r.doc}}
    34 | -------------------------------------------------------------------------------- /src/views/mission/parameters-window.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 | 5 | 6 | 7 |
    8 | 9 |
    10 | 11 | -------------------------------------------------------------------------------- /src/views/mission/plot-window.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    5 | 6 |
    7 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 | « Back 14 |
    15 | 16 |
    17 | 18 | {{record.userName}}'s 19 | {{record.vehicleText}}
    20 | {{record.dateString}} 21 |
    22 | 23 |
    24 | Location: {{record.summaryText}} 25 |
    26 | 27 |
    28 |
    29 | Alt: {{record.maxAlt | number }} m 30 |
    31 |
    32 | Software: {{record.softwareVersion}} 33 |
    34 |
    35 | 36 |
    37 | Speed: {{record.maxGroundspeed | number }} m/s
    38 | Airspeed: {{record.maxAirspeed | number }} m/s 39 |
    40 | 41 |
    42 |
    43 |
    44 |
    45 |
    46 |
    47 | -------------------------------------------------------------------------------- /src/views/search-history.html: -------------------------------------------------------------------------------- 1 |

    Search History

    2 | 3 |
    4 | 8 | 9 |
      10 |
    1. 11 |
      12 | {{item.source}}/{{item.criteria}} 13 |
      14 |
    2. 15 |
    16 |
    -------------------------------------------------------------------------------- /src/views/site.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 |
    5 | -------------------------------------------------------------------------------- /src/views/snippets/alert-header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{alert}} 5 | {{alert}} 6 | -------------------------------------------------------------------------------- /src/views/user/detail.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 | 8 |
    9 |
    10 |

    {{controller.record.fullName}}

    11 | 15 |
    16 |
    17 |
    18 |
    19 | 20 |
    21 | 78 |
    79 | 80 |
    81 |
    82 |

    {{controller.ownershipPrefix}} Vehicles

    83 | 84 | 87 | 88 |
    89 |
    90 |
    91 | 92 |
    93 |
    94 | 95 |
    96 |
    97 | -------------------------------------------------------------------------------- /src/views/user/list-all.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |
    5 | 6 |

    Users

    7 | 8 |
    9 |

    10 | 11 |
    12 | 13 | 14 | 15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 |
    23 | 24 | -------------------------------------------------------------------------------- /src/views/user/vehicle-list-modal.html: -------------------------------------------------------------------------------- 1 | 4 | 11 | 14 | -------------------------------------------------------------------------------- /src/views/user/vehicle-modal.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | -------------------------------------------------------------------------------- /src/views/vehicle-detail.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |
    6 | 7 |
    8 | 9 | {{alert}} 10 | {{alert}} 11 | 12 |

    Vehicle detail:

    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 |
    46 | 47 |
    48 | 49 |
    50 |
    51 | 52 |
    53 | 54 |
    55 |
    56 |
    57 | 58 |
    59 | Drag and drop missions logs here 60 |
    61 | 62 |
    63 | 64 |
    65 | 66 |
    67 | 68 |
    69 |
    Uploading...
    70 | 71 |
    72 |
    73 | 74 |
    75 | 76 |
    77 |
    78 | 79 | 82 |
    83 |
    84 | 85 |
    86 |
    87 | 88 |
    89 |

    Past Missions

    90 |
    91 |
    92 |
    93 |
    94 | 95 |
    96 |
    97 | -------------------------------------------------------------------------------- /src/views/vehicle-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
    5 |
    6 |
    7 | 8 |
    9 |

    10 | 11 |
    12 | 13 |
    14 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /test/e2e/layout/header.coffee: -------------------------------------------------------------------------------- 1 | describe 'header', -> 2 | 3 | beforeEach -> 4 | browser.get('index.html') 5 | 6 | describe 'menu', -> 7 | it 'should consist of 3 menu items', -> 8 | list = element.all findBy.css '#navigation a' 9 | expect(list.count()).toBe(3) 10 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.att.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "att", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "roll": -1.6392115700223275, 7 | "pitch": 4.108468922563036, 8 | "yaw": 2.7048725186448204 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "delete", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "id": 6671, 7 | "isLive": false, 8 | "viewPrivacy": "DEFAULT", 9 | "vehicleId": 219, 10 | "maxAlt": 0.0, 11 | "maxGroundspeed": 0.0, 12 | "maxAirspeed": 0.0, 13 | "maxG": -1.0, 14 | "createdOn": "2014-06-24T23:57Z", 15 | "updatedOn": "2014-06-24T23:57Z", 16 | "summaryText": "San Diego, California, United States", 17 | "viewURL": "http://www.droneshare.com/mission/6671", 18 | "vehicleText": "quadcopter", 19 | "userName": "mrpollo", 20 | "userAvatarImage": "http://www.gravatar.com/avatar/8c4caaefed2a4b497af546524bc3bf6f.jpg", 21 | "numParameters": 0 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.mystery.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "mystery", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "id": 1 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.start.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "start", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "id": 6671, 7 | "notes": "Imported from Droneshare", 8 | "isLive": false, 9 | "viewPrivacy": "DEFAULT", 10 | "vehicleId": 219, 11 | "maxAlt": 550.7, 12 | "maxGroundspeed": 35.36000061035156, 13 | "maxAirspeed": 28.25760841369629, 14 | "maxG": 0.0, 15 | "flightDuration": 2714.766180038452, 16 | "latitude": 35.2344411, 17 | "longitude": -97.5270715, 18 | "softwareVersion": "V3.0.3", 19 | "softwareGit": "", 20 | "createdOn": "2014-06-22T00:14Z", 21 | "updatedOn": "2014-06-23T23:07Z", 22 | "summaryText": "San Diego, California, United States", 23 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-97.5271,35.2344,2)/-97.5271,35.2344,2/140x100.png", 24 | "viewURL": "http://www.droneshare.com/mission/6671", 25 | "vehicleText": "mystery vehicle", 26 | "userName": "mrpollo", 27 | "numParameters": 321, 28 | "vehicleType": "quadcopter" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.stop.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "stop", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "id": 6671, 7 | "notes": "Imported from Droneshare", 8 | "isLive": false, 9 | "viewPrivacy": "DEFAULT", 10 | "vehicleId": 219, 11 | "maxAlt": 550.7, 12 | "maxGroundspeed": 35.36000061035156, 13 | "maxAirspeed": 28.25760841369629, 14 | "maxG": 0.0, 15 | "flightDuration": 2714.766180038452, 16 | "latitude": 35.2344411, 17 | "longitude": -97.5270715, 18 | "softwareVersion": "V3.0.3", 19 | "softwareGit": "", 20 | "createdOn": "2014-06-22T00:14Z", 21 | "updatedOn": "2014-06-23T23:07Z", 22 | "summaryText": "San Diego, California, United States", 23 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-97.5271,35.2344,2)/-97.5271,35.2344,2/140x100.png", 24 | "viewURL": "http://www.droneshare.com/mission/6671", 25 | "vehicleText": "quadcopter", 26 | "userName": "mrpollo", 27 | "numParameters": 321, 28 | "vehicleType": "quadcopter" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/atmosphere.update.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "update", 3 | "data": { 4 | "missionId": 6671, 5 | "payload": { 6 | "id": 6671, 7 | "isLive": true, 8 | "viewPrivacy": "DEFAULT", 9 | "vehicleId": 219, 10 | "maxAlt": 89.44, 11 | "maxGroundspeed": 0.10999999940395355, 12 | "maxAirspeed": 0.10999999940395355, 13 | "maxG": -1.0, 14 | "latitude": 39.3223914, 15 | "longitude": -76.6463088, 16 | "softwareVersion": "V3.1.5", 17 | "softwareGit": "", 18 | "createdOn": "2014-06-24T23:52Z", 19 | "updatedOn": "2014-06-24T23:53Z", 20 | "summaryText": "San Diego, California, United States", 21 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-76.6463,39.3224,2)/-76.6463,39.3224,2/140x100.png", 22 | "viewURL": "http://www.droneshare.com/mission/6671", 23 | "vehicleText": "quadcopter", 24 | "userName": "mrpollo", 25 | "userAvatarImage": "http://www.gravatar.com/avatar/8c4caaefed2a4b497af546524bc3bf6f.jpg", 26 | "numParameters": 0, 27 | "vehicleType": "quadcopter" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/fixtures/dseries.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "label": "GLOBAL_POSITION_INT.alt", 3 | "data": [ 4 | [ 5 | 1399574989337, 6 | 29.47 7 | ], 8 | [ 9 | 1399574989369, 10 | 29.49 11 | ], 12 | [ 13 | 1399574989726, 14 | 29.5 15 | ], 16 | [ 17 | 1399574990418, 18 | 29.53 19 | ], 20 | [ 21 | 1399574990534, 22 | 29.57 23 | ], 24 | [ 25 | 1399574991251, 26 | 29.63 27 | ], 28 | [ 29 | 1399574991359, 30 | 29.65 31 | ], 32 | [ 33 | 1399574991742, 34 | 29.67 35 | ], 36 | [ 37 | 1399574992357, 38 | 29.72 39 | ], 40 | [ 41 | 1399574992376, 42 | 29.77 43 | ] 44 | ], 45 | "color": 0 46 | }, 47 | { 48 | "label": "VFR_HUD.airspeed", 49 | "data": [ 50 | [ 51 | 1399574989350, 52 | 0.02 53 | ], 54 | [ 55 | 1399574990466, 56 | 0.03 57 | ], 58 | [ 59 | 1399574991275, 60 | 0.02 61 | ] 62 | ], 63 | "color": 1 64 | }, 65 | { 66 | "label": "VFR_HUD.groundspeed", 67 | "data": [ 68 | [ 69 | 1399574989350, 70 | 0.02 71 | ], 72 | [ 73 | 1399574990466, 74 | 0.03 75 | ], 76 | [ 77 | 1399574991275, 78 | 0.02 79 | ] 80 | ], 81 | "color": 2 82 | }, 83 | { 84 | "label": "VFR_HUD.throttle", 85 | "data": [ 86 | [ 87 | 1399574989350, 88 | 0 89 | ] 90 | ], 91 | "color": 3 92 | }] 93 | -------------------------------------------------------------------------------- /test/fixtures/messages.geo.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "FeatureCollection", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [ 12 | -21.9295056, 13 | 64.1300183, 14 | 29.47 15 | ] 16 | }, 17 | "properties": { 18 | "marker-size": "small", 19 | "title": "Mode change", 20 | "description": "STABILIZE", 21 | "marker-color": "#64ff00", 22 | "marker-symbol": "triangle-stroked" 23 | } 24 | } 25 | ] 26 | }, 27 | { 28 | "type": "FeatureCollection", 29 | "features": [ 30 | { 31 | "type": "Feature", 32 | "geometry": { 33 | "type": "Point", 34 | "coordinates": [ 35 | -21.92949104309082, 36 | 64.1300277709961, 37 | 0 38 | ] 39 | }, 40 | "properties": { 41 | "marker-size": "medium", 42 | "title": "Home", 43 | "description": "Home", 44 | "marker-color": "#000099", 45 | "marker-symbol": "building" 46 | } 47 | }, 48 | { 49 | "type": "Feature", 50 | "geometry": { 51 | "type": "Point", 52 | "coordinates": [ 53 | -21.943431854248047, 54 | 64.0523910522461, 55 | 40 56 | ] 57 | }, 58 | "properties": { 59 | "marker-size": "medium", 60 | "title": "Waypoint #1", 61 | "marker-color": "#9999D6", 62 | "marker-symbol": "1" 63 | } 64 | }, 65 | { 66 | "type": "Feature", 67 | "geometry": { 68 | "type": "Point", 69 | "coordinates": [ 70 | -21.941980361938477, 71 | 64.0521240234375, 72 | 40 73 | ] 74 | }, 75 | "properties": { 76 | "marker-size": "medium", 77 | "title": "Waypoint #2", 78 | "marker-color": "#9999D6", 79 | "marker-symbol": "2" 80 | } 81 | }, 82 | { 83 | "type": "Feature", 84 | "geometry": { 85 | "type": "Point", 86 | "coordinates": [ 87 | -21.941375732421875, 88 | 64.0514907836914, 89 | 30 90 | ] 91 | }, 92 | "properties": { 93 | "marker-size": "medium", 94 | "title": "Waypoint #3", 95 | "marker-color": "#9999D6", 96 | "marker-symbol": "3" 97 | } 98 | }, 99 | { 100 | "type": "Feature", 101 | "geometry": { 102 | "type": "Point", 103 | "coordinates": [ 104 | -21.941980361938477, 105 | 64.05085754394531, 106 | 30 107 | ] 108 | }, 109 | "properties": { 110 | "marker-size": "medium", 111 | "title": "Waypoint #4", 112 | "marker-color": "#9999D6", 113 | "marker-symbol": "4" 114 | } 115 | }, 116 | { 117 | "type": "Feature", 118 | "geometry": { 119 | "type": "Point", 120 | "coordinates": [ 121 | -21.943431854248047, 122 | 64.05059051513672, 123 | 30 124 | ] 125 | }, 126 | "properties": { 127 | "marker-size": "medium", 128 | "title": "Waypoint #5", 129 | "marker-color": "#9999D6", 130 | "marker-symbol": "5" 131 | } 132 | }, 133 | { 134 | "type": "Feature", 135 | "geometry": { 136 | "type": "Point", 137 | "coordinates": [ 138 | -21.944883346557617, 139 | 64.05085754394531, 140 | 30 141 | ] 142 | }, 143 | "properties": { 144 | "marker-size": "medium", 145 | "title": "Waypoint #6", 146 | "marker-color": "#9999D6", 147 | "marker-symbol": "6" 148 | } 149 | }, 150 | { 151 | "type": "Feature", 152 | "geometry": { 153 | "type": "Point", 154 | "coordinates": [ 155 | -21.945486068725586, 156 | 64.0514907836914, 157 | 30 158 | ] 159 | }, 160 | "properties": { 161 | "marker-size": "medium", 162 | "title": "Waypoint #7", 163 | "marker-color": "#9999D6", 164 | "marker-symbol": "7" 165 | } 166 | }, 167 | { 168 | "type": "Feature", 169 | "geometry": { 170 | "type": "Point", 171 | "coordinates": [ 172 | -21.944883346557617, 173 | 64.0521240234375, 174 | 40 175 | ] 176 | }, 177 | "properties": { 178 | "marker-size": "medium", 179 | "title": "Waypoint #8", 180 | "marker-color": "#9999D6", 181 | "marker-symbol": "8" 182 | } 183 | }, 184 | { 185 | "type": "Feature", 186 | "geometry": { 187 | "type": "Point", 188 | "coordinates": [ 189 | -21.943431854248047, 190 | 64.0523910522461, 191 | 40 192 | ] 193 | }, 194 | "properties": { 195 | "marker-size": "medium", 196 | "title": "Waypoint #9", 197 | "marker-color": "#9999D6", 198 | "marker-symbol": "9" 199 | } 200 | }, 201 | { 202 | "type": "Feature", 203 | "geometry": { 204 | "type": "Point", 205 | "coordinates": [ 206 | -21.943151473999023, 207 | 64.0513687133789, 208 | 30 209 | ] 210 | }, 211 | "properties": { 212 | "marker-size": "medium", 213 | "title": "Waypoint #10", 214 | "description": "Loiter (forever)", 215 | "marker-color": "#9999D6", 216 | "marker-symbol": "0" 217 | } 218 | }, 219 | { 220 | "type": "Feature", 221 | "geometry": { 222 | "type": "LineString", 223 | "coordinates": [ 224 | [ 225 | -21.943431854248047, 226 | 64.0523910522461, 227 | 40 228 | ], 229 | [ 230 | -21.941980361938477, 231 | 64.0521240234375, 232 | 40 233 | ], 234 | [ 235 | -21.941375732421875, 236 | 64.0514907836914, 237 | 30 238 | ], 239 | [ 240 | -21.941980361938477, 241 | 64.05085754394531, 242 | 30 243 | ], 244 | [ 245 | -21.943431854248047, 246 | 64.05059051513672, 247 | 30 248 | ], 249 | [ 250 | -21.944883346557617, 251 | 64.05085754394531, 252 | 30 253 | ], 254 | [ 255 | -21.945486068725586, 256 | 64.0514907836914, 257 | 30 258 | ], 259 | [ 260 | -21.944883346557617, 261 | 64.0521240234375, 262 | 40 263 | ], 264 | [ 265 | -21.943431854248047, 266 | 64.0523910522461, 267 | 40 268 | ], 269 | [ 270 | -21.943151473999023, 271 | 64.0513687133789, 272 | 30 273 | ] 274 | ] 275 | }, 276 | "properties": { 277 | "stroke": "#9999D6", 278 | "stroke-opacity": 0.5 279 | } 280 | } 281 | ] 282 | }, 283 | { 284 | "type": "FeatureCollection", 285 | "features": [ 286 | { 287 | "type": "Feature", 288 | "geometry": { 289 | "type": "LineString", 290 | "coordinates": [ 291 | [ 292 | -21.9295056, 293 | 64.1300183, 294 | 29.47 295 | ], 296 | [ 297 | -21.9295063, 298 | 64.1300186, 299 | 29.53 300 | ], 301 | [ 302 | -21.9295069, 303 | 64.1300189, 304 | 29.63 305 | ], 306 | [ 307 | -21.9295074, 308 | 64.130019, 309 | 29.72 310 | ] 311 | ] 312 | }, 313 | "properties": { 314 | "stroke": "#444444", 315 | "stroke-width": 4 316 | } 317 | }, 318 | { 319 | "type": "Feature", 320 | "geometry": { 321 | "type": "LineString", 322 | "coordinates": [ 323 | [ 324 | -21.9295056, 325 | 64.1300183, 326 | 29.47 327 | ], 328 | [ 329 | -21.9295063, 330 | 64.1300186, 331 | 29.53 332 | ], 333 | [ 334 | -21.9295069, 335 | 64.1300189, 336 | 29.63 337 | ], 338 | [ 339 | -21.9295074, 340 | 64.130019, 341 | 29.72 342 | ] 343 | ] 344 | }, 345 | "properties": { 346 | "stroke": "#00FF00", 347 | "stroke-width": 2 348 | } 349 | } 350 | ] 351 | } 352 | ], 353 | "bbox": [ 354 | -21.934507399999998, 355 | 64.12501830000001, 356 | 29.47, 357 | -21.9245056, 358 | 64.135019, 359 | 29.72 360 | ] 361 | } 362 | -------------------------------------------------------------------------------- /test/fixtures/mission.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 100, 3 | "notes": "Imported from Droneshare", 4 | "isLive": false, 5 | "viewPrivacy": "DEFAULT", 6 | "vehicleId": 218, 7 | "maxAlt": 29.72, 8 | "maxGroundspeed": 0.029999999329447746, 9 | "maxAirspeed": 0.029999999329447746, 10 | "maxG": 0, 11 | "latitude": 64.130019, 12 | "longitude": -21.9295074, 13 | "softwareVersion": "V3.1.3", 14 | "softwareGit": "", 15 | "createdOn": "2014-05-08T18:49Z", 16 | "updatedOn": "2014-06-17T17:11Z", 17 | "summaryText": "San Diego, California, United States", 18 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-21.9295,64.1300,2)/-21.9295,64.1300,2/140x100.png", 19 | "viewURL": "http://www.droneshare.com/mission/100", 20 | "vehicleText": "quadcopter", 21 | "userName": "mrpollo", 22 | "numParameters": 331, 23 | "vehicleType": "quadcopter" 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/missions.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "id": 6293, 3 | "isLive": false, 4 | "viewPrivacy": "DEFAULT", 5 | "vehicleId": 267, 6 | "maxAlt": 20.67, 7 | "maxGroundspeed": 18.729999542236328, 8 | "maxAirspeed": 18.729999542236328, 9 | "maxG": 0, 10 | "flightDuration": 298.2569999694824, 11 | "latitude": 37.8575624, 12 | "longitude": -122.2928988, 13 | "createdOn": "2014-06-17T20:42Z", 14 | "updatedOn": "2014-06-17T20:58Z", 15 | "summaryText": "Berkeley, California, United States", 16 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-122.2929,37.8576,2)/-122.2929,37.8576,2/140x100.png", 17 | "viewURL": "http://www.droneshare.com/mission/6293", 18 | "vehicleText": "quadcopter", 19 | "userName": "jason", 20 | "userAvatarImage": "http://www.gravatar.com/avatar/674e1a0c118af8a62d8700a2f21b2cfb.jpg", 21 | "numParameters": 0, 22 | "vehicleType": "quadcopter" 23 | }, 24 | { 25 | "id": 6281, 26 | "isLive": false, 27 | "viewPrivacy": "DEFAULT", 28 | "vehicleId": 495, 29 | "maxAlt": 142.95, 30 | "maxGroundspeed": 0.10000000149011612, 31 | "maxAirspeed": 0.10000000149011612, 32 | "maxG": 0, 33 | "latitude": 44.7796271, 34 | "longitude": 20.4211009, 35 | "createdOn": "2014-06-17T13:28Z", 36 | "updatedOn": "2014-06-17T13:30Z", 37 | "summaryText": "Belgrade, Grad Beograd, Serbia", 38 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(20.4211,44.7796,2)/20.4211,44.7796,2/140x100.png", 39 | "viewURL": "http://www.droneshare.com/mission/6281", 40 | "vehicleText": "quadcopter", 41 | "userName": "polimac", 42 | "numParameters": 0, 43 | "vehicleType": "quadcopter" 44 | }, 45 | { 46 | "id": 6292, 47 | "isLive": false, 48 | "viewPrivacy": "DEFAULT", 49 | "vehicleId": 267, 50 | "maxAlt": 25.59, 51 | "maxGroundspeed": 4.960000038146973, 52 | "maxAirspeed": 4.960000038146973, 53 | "maxG": 0, 54 | "flightDuration": 598.3070001602173, 55 | "latitude": 37.8577643, 56 | "longitude": -122.2928795, 57 | "createdOn": "2014-06-17T00:22Z", 58 | "updatedOn": "2014-06-17T20:57Z", 59 | "summaryText": "Berkeley, California, United States", 60 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-122.2929,37.8578,2)/-122.2929,37.8578,2/140x100.png", 61 | "viewURL": "http://www.droneshare.com/mission/6292", 62 | "vehicleText": "quadcopter", 63 | "userName": "jason", 64 | "userAvatarImage": "http://www.gravatar.com/avatar/674e1a0c118af8a62d8700a2f21b2cfb.jpg", 65 | "numParameters": 0, 66 | "vehicleType": "quadcopter" 67 | }, 68 | { 69 | "id": 6256, 70 | "isLive": false, 71 | "viewPrivacy": "DEFAULT", 72 | "vehicleId": 267, 73 | "maxAlt": 21.47, 74 | "maxGroundspeed": 6.769999980926514, 75 | "maxAirspeed": 6.769999980926514, 76 | "maxG": 0, 77 | "flightDuration": 200.78399991989136, 78 | "latitude": 37.8577947, 79 | "longitude": -122.2926807, 80 | "createdOn": "2014-06-16T23:41Z", 81 | "updatedOn": "2014-06-17T00:25Z", 82 | "summaryText": "Berkeley, California, United States", 83 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-122.2927,37.8578,2)/-122.2927,37.8578,2/140x100.png", 84 | "viewURL": "http://www.droneshare.com/mission/6256", 85 | "vehicleText": "quadcopter", 86 | "userName": "jason", 87 | "userAvatarImage": "http://www.gravatar.com/avatar/674e1a0c118af8a62d8700a2f21b2cfb.jpg", 88 | "numParameters": 0, 89 | "vehicleType": "quadcopter" 90 | }, 91 | { 92 | "id": 6255, 93 | "isLive": false, 94 | "viewPrivacy": "DEFAULT", 95 | "vehicleId": 267, 96 | "maxAlt": 29.15, 97 | "maxGroundspeed": 2.0199999809265137, 98 | "maxAirspeed": 2.0199999809265137, 99 | "maxG": 0, 100 | "flightDuration": 4.788000106811523, 101 | "latitude": 37.8578104, 102 | "longitude": -122.2928883, 103 | "createdOn": "2014-06-16T23:34Z", 104 | "updatedOn": "2014-06-17T00:25Z", 105 | "summaryText": "Berkeley, California, United States", 106 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-122.2929,37.8578,2)/-122.2929,37.8578,2/140x100.png", 107 | "viewURL": "http://www.droneshare.com/mission/6255", 108 | "vehicleText": "quadcopter", 109 | "userName": "jason", 110 | "userAvatarImage": "http://www.gravatar.com/avatar/674e1a0c118af8a62d8700a2f21b2cfb.jpg", 111 | "numParameters": 0, 112 | "vehicleType": "quadcopter" 113 | }, 114 | { 115 | "id": 6177, 116 | "isLive": false, 117 | "viewPrivacy": "DEFAULT", 118 | "vehicleId": 232, 119 | "maxAlt": 149.91, 120 | "maxGroundspeed": 4.049999713897705, 121 | "maxAirspeed": 4.049999713897705, 122 | "maxG": 0, 123 | "flightDuration": 3.76200008392334, 124 | "latitude": 43.3540242, 125 | "longitude": -71.4626192, 126 | "createdOn": "2014-06-15T22:19Z", 127 | "updatedOn": "2014-06-15T22:36Z", 128 | "summaryText": "Loudon, New Hampshire, United States", 129 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-71.4626,43.3540,2)/-71.4626,43.3540,2/140x100.png", 130 | "viewURL": "http://www.droneshare.com/mission/6177", 131 | "vehicleText": "Y6", 132 | "userName": "john", 133 | "userAvatarImage": "http://www.gravatar.com/avatar/ca6c744d618d7f30fc6459071dddc42d.jpg", 134 | "numParameters": 0, 135 | "vehicleType": "hexarotor" 136 | }, 137 | { 138 | "id": 6172, 139 | "isLive": false, 140 | "viewPrivacy": "DEFAULT", 141 | "vehicleId": 486, 142 | "maxAlt": 1711.17, 143 | "maxGroundspeed": 5.159999847412109, 144 | "maxAirspeed": 5.159999847412109, 145 | "maxG": 0, 146 | "flightDuration": 288.1099998950958, 147 | "latitude": 39.8964342, 148 | "longitude": -105.0742386, 149 | "createdOn": "2014-06-15T17:19Z", 150 | "updatedOn": "2014-06-15T17:37Z", 151 | "summaryText": "Broomfield, Colorado, United States", 152 | "mapThumbnailURL": "http://api.tiles.mapbox.com/v3/kevin3dr.hokdl9ko/pin-s-heliport+f44(-105.0742,39.8964,2)/-105.0742,39.8964,2/140x100.png", 153 | "viewURL": "http://www.droneshare.com/mission/6172", 154 | "vehicleText": "hexarotor", 155 | "userName": "kraken1960", 156 | "numParameters": 0, 157 | "vehicleType": "hexarotor" 158 | }] 159 | -------------------------------------------------------------------------------- /test/fixtures/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "login": "mrpollo", 3 | "id": 219, 4 | "fullName": "Ramón Roche", 5 | "isAdmin": false, 6 | "avatarImage": "http://www.gravatar.com/avatar/8c4caaefed2a4b497af546524bc3bf6f.jpg", 7 | "profileURL": "http://www.gravatar.com/8c4caaefed2a4b497af546524bc3bf6f", 8 | "emailVerified": false, 9 | "needNewPassword": false, 10 | "defaultViewPrivacy": 0, 11 | "defaultControlPrivacy": 0, 12 | "vehicles": [{ 13 | "uuid": "05bc38ee-fecd-47d9-88b4-7885492dc520", 14 | "name": "quadcopter", 15 | "id": 218, 16 | "userId": 219, 17 | "viewPrivacy": "SHARED", 18 | "controlPrivacy": "DEFAULT", 19 | "missions": [], 20 | "createdOn": "2014-06-04T16:08Z", 21 | "updatedOn": "2014-06-05T14:22Z", 22 | "summaryText": "quadcopter" 23 | }], 24 | "email": "mrpollo@gmail.com", 25 | "wantEmails": true 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/vehicle.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "05bc38ee-fecd-47d9-88b4-7885492dc520", 3 | "name": "quadcopter", 4 | "id": 218, 5 | "userId": 219, 6 | "viewPrivacy": "SHARED", 7 | "controlPrivacy": "DEFAULT", 8 | "missions": [], 9 | "createdOn": "2014-06-04T16:08Z", 10 | "updatedOn": "2014-06-05T14:22Z", 11 | "summaryText": "quadcopter" 12 | } 13 | -------------------------------------------------------------------------------- /test/units/controllers/liveMapController.coffee: -------------------------------------------------------------------------------- 1 | jasmine.getJSONFixtures().fixturesPath = '/base/test/fixtures' 2 | 3 | describe "liveMapController", -> 4 | beforeEach -> 5 | window.logos = 6 | parent: '/images/3drobotics.png' 7 | son: '/images/droneshare.png' 8 | vehicleMarkerActive: '/images/vehicle-marker-active.png' 9 | vehicleMarkerInactive: '/images/vehicle-marker-inactive.png' 10 | 11 | beforeEach module 'app' 12 | beforeEach inject ($rootScope, $controller, _$httpBackend_, _atmosphere_) -> 13 | loadJSONFixtures('user.json') 14 | loadJSONFixtures('staticmap.json') 15 | @scope = $rootScope.$new() 16 | @atmosphere = _atmosphere_ 17 | 18 | @liveMapController = $controller('liveMapController', { '$scope': @scope }) 19 | @urlBase = 'https://api.droneshare.com/api/v1' 20 | @httpBackend = _$httpBackend_ 21 | @httpBackend.expectGET("#{@urlBase}/auth/user").respond 200, getJSONFixture('user.json') 22 | 23 | # FIXME - we should share this init with missionService 24 | @httpBackend.whenGET("#{@urlBase}/mission/staticMap").respond(getJSONFixture('staticmap.json')) 25 | 26 | it 'should set scope.tiles', -> 27 | @scope.$apply() 28 | expect(@scope.tiles).not.toBeUndefined() 29 | 30 | it 'should set scope.layers', -> 31 | @scope.$apply() 32 | expect(@scope.layers).not.toBeUndefined() 33 | 34 | it 'should check if current user is logged in', -> 35 | @scope.$apply() 36 | @httpBackend.flush() 37 | 38 | expect(@scope.auth.user.loggedIn).toBeTruthy() 39 | 40 | it 'should connect to all atmosphere services', -> 41 | spyOn(@liveMapController, 'connectAtmo') 42 | 43 | @scope.$apply() 44 | @httpBackend.flush() 45 | 46 | expect(@liveMapController.connectAtmo).toHaveBeenCalled() 47 | 48 | it 'should disconnect from all atmosphere services on scope destroy', -> 49 | spyOn(@liveMapController, 'disconnectAtmo') 50 | 51 | @scope.$apply() 52 | @httpBackend.flush() 53 | @scope.$destroy() 54 | 55 | expect(@liveMapController.disconnectAtmo).toHaveBeenCalled() 56 | 57 | describe 'atmosphere', -> 58 | beforeEach -> 59 | loadJSONFixtures 'atmosphere.att.json' 60 | loadJSONFixtures 'atmosphere.delete.json' 61 | loadJSONFixtures 'atmosphere.mystery.json' 62 | loadJSONFixtures 'atmosphere.start.json' 63 | loadJSONFixtures 'atmosphere.stop.json' 64 | loadJSONFixtures 'atmosphere.update.json' 65 | 66 | describe 'onMissionUpdate', -> 67 | beforeEach -> 68 | spyOn(@liveMapController, 'onMissionUpdate').and.callThrough() 69 | spyOn(@liveMapController, 'updateVehicle').and.callThrough() 70 | 71 | it 'should handle start frames', -> 72 | fakeMissionStart = getJSONFixture('atmosphere.start.json').data 73 | vehicleKey = @liveMapController.vehicleKey fakeMissionStart.missionId 74 | 75 | expect(@scope.vehicleMarkers[vehicleKey]).toBeUndefined() 76 | 77 | @liveMapController.onMissionUpdate fakeMissionStart 78 | 79 | expect(@liveMapController.updateVehicle).toHaveBeenCalled() 80 | expect(@scope.vehicleMarkers[vehicleKey].payload.id).toEqual fakeMissionStart.missionId 81 | 82 | describe 'update frame messages', -> 83 | beforeEach -> 84 | @fakeMissionUpdate = getJSONFixture('atmosphere.update.json').data 85 | 86 | it 'should create markers if it doesn\'t yet exist', -> 87 | vehicleKey = @liveMapController.vehicleKey @fakeMissionUpdate.missionId 88 | 89 | # make sure it doesn't exist yet 90 | expect(@scope.vehicleMarkers[vehicleKey]).toBeUndefined() 91 | 92 | @liveMapController.onMissionUpdate @fakeMissionUpdate 93 | 94 | # should be defined by now 95 | expect(@scope.vehicleMarkers[vehicleKey].payload.id).toEqual @fakeMissionUpdate.missionId 96 | 97 | it 'should update markers', -> 98 | fakeMissionStart = getJSONFixture('atmosphere.start.json').data 99 | vehicleKey = @liveMapController.vehicleKey fakeMissionStart.missionId 100 | 101 | expect(@fakeMissionUpdate.missionId).toEqual fakeMissionStart.missionId 102 | expect(@scope.vehicleMarkers[vehicleKey]).toBeUndefined() 103 | 104 | # Create new marker 105 | @liveMapController.onMissionUpdate fakeMissionStart 106 | expect(@scope.vehicleMarkers[vehicleKey].payload.id).toEqual fakeMissionStart.missionId 107 | 108 | lat = @scope.vehicleMarkers[vehicleKey].lat 109 | lng = @scope.vehicleMarkers[vehicleKey].lng 110 | 111 | # Update the marker 112 | @liveMapController.onMissionUpdate @fakeMissionUpdate 113 | expect(@scope.vehicleMarkers[vehicleKey].lat).not.toEqual lat 114 | expect(@scope.vehicleMarkers[vehicleKey].lng).not.toEqual lng 115 | 116 | it 'should zoom to the current user vehicle', -> 117 | spyOn(@liveMapController, 'zoomToVehicle') 118 | 119 | @scope.$apply() 120 | @httpBackend.flush() 121 | 122 | me = @liveMapController.authService.getUser() 123 | fakeMissionStart = getJSONFixture('atmosphere.start.json').data 124 | vehicleKey = @liveMapController.vehicleKey fakeMissionStart.missionId 125 | 126 | @liveMapController.onMissionUpdate fakeMissionStart 127 | 128 | expect(@scope.vehicleMarkers[vehicleKey]).not.toBeUndefined() 129 | expect(@liveMapController.zoomToVehicle).toHaveBeenCalled() 130 | 131 | describe 'onMissionDelete', -> 132 | it 'should remove vehicle marker', -> 133 | fakeMissionStop = getJSONFixture('atmosphere.stop.json').data 134 | fakeMissionStart = getJSONFixture('atmosphere.start.json').data 135 | vehicleKey = @liveMapController.vehicleKey fakeMissionStart.missionId 136 | 137 | expect(fakeMissionStop.missionId).toEqual fakeMissionStart.missionId 138 | expect(@scope.vehicleMarkers[vehicleKey]).toBeUndefined() 139 | 140 | @liveMapController.onMissionUpdate fakeMissionStart 141 | expect(@scope.vehicleMarkers[vehicleKey]).not.toBeUndefined() 142 | 143 | @liveMapController.onMissionDelete fakeMissionStop 144 | expect(@scope.vehicleMarkers[vehicleKey]).toBeUndefined() 145 | 146 | describe 'updateVehicleMessage', -> 147 | it 'should update marker popup', -> 148 | loadJSONFixtures 'atmosphere.mystery.json' 149 | 150 | spyOn(@liveMapController, 'updateMarkerPopup') 151 | @liveMapController.updateVehicleMessage getJSONFixture('atmosphere.mystery.json').data 152 | expect(@liveMapController.updateMarkerPopup).toHaveBeenCalled() 153 | 154 | describe 'onAttitude', -> 155 | it 'should update marker angle', -> 156 | loadJSONFixtures 'atmosphere.start.json' 157 | loadJSONFixtures 'atmosphere.att.json' 158 | fakeMissionStart = getJSONFixture('atmosphere.start.json').data 159 | fakeAttitude = getJSONFixture('atmosphere.att.json').data 160 | vehicleKey = @liveMapController.vehicleKey fakeMissionStart.missionId 161 | 162 | @liveMapController.onMissionUpdate fakeMissionStart 163 | expect(@scope.vehicleMarkers[vehicleKey]).not.toBeUndefined() 164 | 165 | @liveMapController.onAttitude fakeAttitude 166 | expect(@scope.vehicleMarkers[vehicleKey].iconAngle).toEqual fakeAttitude.payload.yaw 167 | -------------------------------------------------------------------------------- /test/units/controllers/mapController.coffee: -------------------------------------------------------------------------------- 1 | describe "mapController", -> 2 | beforeEach module 'app' 3 | beforeEach inject ($rootScope, $controller) -> 4 | @scope = $rootScope.$new() 5 | @controller = $controller('mapController', { '$scope': @scope }) 6 | 7 | it 'should initMap and set defaults', -> 8 | expect(@scope.defaults.scrollWheelZoom).toBeTruthy() 9 | expect(@scope.defaults.zoom).toEqual 10 10 | expect(@scope.defaults.minZoom).toEqual 2 11 | 12 | it 'should initMap and set mapbox layers', -> 13 | expect(@scope.layers.baselayers.threedr_satview.url).toMatch /mapbox/ 14 | -------------------------------------------------------------------------------- /test/units/controllers/missionDetailController.coffee: -------------------------------------------------------------------------------- 1 | describe "missionDetailController", -> 2 | beforeEach module 'app' 3 | beforeEach inject ($rootScope, $controller, _$httpBackend_) -> 4 | loadJSONFixtures 'user.json' 5 | loadJSONFixtures 'mission.json' 6 | loadJSONFixtures 'messages.geo.json' 7 | 8 | @scope = $rootScope.$new() 9 | @user = getJSONFixture 'user.json' 10 | @mission = getJSONFixture 'mission.json' 11 | @geojson = getJSONFixture 'messages.geo.json' 12 | 13 | routeParamsStub = jasmine.createSpy('routeParamsStub') 14 | routeParamsStub.id = 218 15 | 16 | @userDetailController = $controller('missionDetailController', { '$scope': @scope, '$routeParams': routeParamsStub }) 17 | @urlBase = 'https://api.droneshare.com/api/v1' 18 | @httpBackend = _$httpBackend_ 19 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 200, @user 20 | @httpBackend.whenGET("#{@urlBase}/mission/#{routeParamsStub.id}").respond 200, @mission 21 | @httpBackend.whenGET("#{@urlBase}/mission/#{routeParamsStub.id}/messages.geo.json").respond 200, @geojson 22 | 23 | it 'gets mission record by params', -> 24 | expect(@scope.record).toBeUndefined() 25 | @scope.$apply() 26 | @httpBackend.flush() 27 | expect(@scope.record).not.toBeUndefined() 28 | 29 | describe 'get_geojson', -> 30 | it 'gets geojson data at init', -> 31 | expect(@scope.geojson).toEqual {} 32 | @scope.$apply() 33 | @httpBackend.flush() 34 | expect(@scope.geojson).not.toEqual {} 35 | 36 | it 'should set bounds', -> 37 | expect(@scope.bounds).toEqual {} 38 | @scope.$apply() 39 | @httpBackend.flush() 40 | 41 | expect(@scope.bounds).not.toEqual {} 42 | expect(@scope.bounds.southWest.lng).toEqual @geojson.bbox[0] 43 | expect(@scope.bounds.southWest.lat).toEqual @geojson.bbox[1] 44 | expect(@scope.bounds.northEast.lng).toEqual @geojson.bbox[3] 45 | expect(@scope.bounds.northEast.lat).toEqual @geojson.bbox[4] 46 | 47 | describe 'handle_fetch_response', -> 48 | it 'gets formats date to present to users', -> 49 | @scope.$apply() 50 | @httpBackend.flush() 51 | createdOn = new Date(@mission.createdOn) 52 | expect(@scope.record.dateString).toEqual "#{createdOn.toDateString()} - #{createdOn.toLocaleTimeString()}" 53 | 54 | it 'prepares data for social sharing', -> 55 | @scope.$apply() 56 | @httpBackend.flush() 57 | expect(@scope.$parent.ogImage).toEqual @mission.mapThumbnailURL 58 | expect(@scope.$parent.ogDescription).toEqual "#{@mission.userName} flew their drone in #{@mission.summaryText} for #{Math.round(@mission.flightDuration / 60)} minutes." 59 | expect(@scope.$parent.ogTitle).toEqual "#{@mission.userName}'s mission" 60 | -------------------------------------------------------------------------------- /test/units/controllers/missionPlotController.coffee: -------------------------------------------------------------------------------- 1 | describe "missionPlotController", -> 2 | beforeEach module 'app' 3 | beforeEach inject ($rootScope, $controller, _$httpBackend_) -> 4 | loadJSONFixtures 'user.json' 5 | loadJSONFixtures 'mission.json' 6 | loadJSONFixtures 'dseries.json' 7 | 8 | @scope = $rootScope.$new() 9 | @user = getJSONFixture 'user.json' 10 | @mission = getJSONFixture 'mission.json' 11 | @dseries = getJSONFixture 'dseries.json' 12 | 13 | routeParamsStub = jasmine.createSpy('routeParamsStub') 14 | routeParamsStub.id = 218 15 | 16 | @missionPlotController = $controller('missionPlotController', { '$scope': @scope, '$routeParams': routeParamsStub, 'missionData': @mission, 'plotData': @dseries }) 17 | @urlBase = 'https://api.droneshare.com/api/v1' 18 | @httpBackend = _$httpBackend_ 19 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 200, @user 20 | @httpBackend.whenGET("#{@urlBase}/mission/#{routeParamsStub.id}").respond 200, @mission 21 | @httpBackend.whenGET("#{@urlBase}/mission/#{routeParamsStub.id}/dseries").respond 200, @dseries 22 | 23 | it 'gets mission record as a resolved instance', -> 24 | expect(@scope.record).toEqual @mission 25 | 26 | it 'gets plot data as a resolved instance', -> 27 | expect(@scope.series).toEqual @dseries 28 | -------------------------------------------------------------------------------- /test/units/controllers/userDetailController.coffee: -------------------------------------------------------------------------------- 1 | describe "userDetailController", -> 2 | beforeEach module 'app' 3 | beforeEach inject ($rootScope, $controller, _$httpBackend_) -> 4 | loadJSONFixtures('user.json') 5 | @scope = $rootScope.$new() 6 | @user = getJSONFixture('user.json') 7 | @authUser = angular.extend({loggedIn: true}, @user) 8 | routeParamsStub = jasmine.createSpy('routeParamsStub') 9 | routeParamsStub.id = 219 10 | 11 | @userDetailController = $controller('userDetailController', { '$scope': @scope, '$routeParams': routeParamsStub, 'resolvedUser': @user }) 12 | @urlBase = 'https://api.droneshare.com/api/v1' 13 | @httpBackend = _$httpBackend_ 14 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 200, @user 15 | @httpBackend.whenGET("#{@urlBase}/user/#{routeParamsStub.id}").respond 200, @user 16 | 17 | it 'expects a resolved user object to be provided', -> 18 | expect(@scope.record).not.toBeUndefined() 19 | 20 | describe 'isMeOrAdmin', -> 21 | it 'knows if auth user is owner of current user record', -> 22 | spyOn(@userDetailController.authService, 'getUser').and.returnValue(@authUser) 23 | 24 | @scope.$apply() 25 | @httpBackend.flush() 26 | 27 | expect(@userDetailController.isMe()).toBeTruthy() 28 | 29 | it 'knows if auth user is owner of current record or an admin', -> 30 | @scope.$apply() 31 | @httpBackend.flush() 32 | 33 | # force check of admin 34 | spyOn(@userDetailController, 'isMe').and.returnValue(false) 35 | expect(@userDetailController.isMeOrAdmin()).not.toBeTruthy() 36 | -------------------------------------------------------------------------------- /test/units/controllers/vehicleDetailController.coffee: -------------------------------------------------------------------------------- 1 | describe "vehicleDetailController", -> 2 | beforeEach module 'app' 3 | beforeEach inject ($rootScope, $controller, _$httpBackend_) -> 4 | loadJSONFixtures 'user.json' 5 | loadJSONFixtures 'vehicle.json' 6 | 7 | @scope = $rootScope.$new() 8 | @user = getJSONFixture('user.json') 9 | @vehicle = getJSONFixture('vehicle.json') 10 | 11 | routeParamsStub = jasmine.createSpy('routeParamsStub') 12 | routeParamsStub.id = 218 13 | 14 | @vehicleDetailController = $controller('vehicleDetailController', { '$scope': @scope, '$routeParams': routeParamsStub, 'resolvedVehicle': @vehicle }) 15 | @urlBase = 'https://api.droneshare.com/api/v1' 16 | @httpBackend = _$httpBackend_ 17 | @httpBackend.expectGET("#{@urlBase}/auth/user").respond 200, @user 18 | @httpBackend.expectGET("#{@urlBase}/vehicle/#{routeParamsStub.id}").respond 200, @vehicle 19 | 20 | it 'expects a resolved vehicle object to be provided', -> 21 | expect(@scope.record).not.toBeUndefined() 22 | -------------------------------------------------------------------------------- /test/units/filters/twitterfySpec.coffee: -------------------------------------------------------------------------------- 1 | describe 'twitterfy', -> 2 | beforeEach module 'app' 3 | 4 | it 'username should be prepended with the @ sign', inject ['twitterfyFilter', (twitterfy) -> 5 | twitterHandle = 'CaryLandholt' 6 | twitterfied = twitterfy twitterHandle 7 | 8 | expect(twitterfied).toEqual("@#{twitterHandle}") 9 | expect(twitterfied).not.toEqual(twitterHandle) 10 | ] -------------------------------------------------------------------------------- /test/units/services/authService.coffee: -------------------------------------------------------------------------------- 1 | jasmine.getJSONFixtures().fixturesPath = '/base/test/fixtures' 2 | 3 | describe "authService", -> 4 | beforeEach module 'app' 5 | 6 | beforeEach inject ($rootScope, $controller, _$httpBackend_, authService) -> 7 | loadJSONFixtures 'user.json' 8 | @user = getJSONFixture 'user.json' 9 | @scope = $rootScope.$new() 10 | @urlBase = 'https://api.droneshare.com/api/v1' 11 | @authService = authService 12 | @httpBackend = _$httpBackend_ 13 | 14 | describe 'initialize', -> 15 | beforeEach -> 16 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 17 | 18 | it 'user is logged out on initialize', -> 19 | expect(@authService.user.loggedIn).not.toBeTruthy() 20 | 21 | @scope.$apply() 22 | @httpBackend.flush() 23 | 24 | expect(@authService.user.loggedIn).toBeTruthy() 25 | 26 | it 'checks if a user is currently logged in', -> 27 | spyOn(@authService, 'setLoggedIn') 28 | 29 | @scope.$apply() 30 | @httpBackend.flush() 31 | 32 | expect(@authService.setLoggedIn).toHaveBeenCalledWith @user 33 | 34 | describe 'logout', -> 35 | beforeEach -> 36 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 37 | @scope.$apply() 38 | @httpBackend.flush() 39 | 40 | it 'logs user out', -> 41 | expect(@authService.user.loggedIn).toBeTruthy() 42 | @authService.logout() 43 | expect(@authService.user.loggedIn).not.toBeTruthy() 44 | 45 | it 'should call the api action /logout', -> 46 | spyOn(@authService, 'postId').and.callThrough() 47 | @authService.logout() 48 | expect(@authService.postId).toHaveBeenCalledWith 'logout' 49 | 50 | describe 'create', -> 51 | it 'should post payload to API /create', -> 52 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 53 | spyOn(@authService, 'postId').and.callThrough() 54 | payload = jasmine.createSpy('payload') 55 | @authService.create(payload) 56 | expect(@authService.postId).toHaveBeenCalledWith 'create', payload 57 | 58 | it 'should login user upon creation', -> 59 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 401, {} 60 | @httpBackend.expectPOST("#{@urlBase}/auth/create").respond @user 61 | 62 | spyOn(@authService, 'setLoggedIn') 63 | payload = jasmine.createSpy('payload') 64 | @authService.create(payload) 65 | 66 | @scope.$apply() 67 | @httpBackend.flush() 68 | 69 | expect(@authService.setLoggedIn).toHaveBeenCalled() 70 | 71 | describe 'login', -> 72 | beforeEach -> 73 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 401, {} 74 | 75 | it 'posts user credentials to API', -> 76 | @httpBackend.expectPOST("#{@urlBase}/auth/login").respond @user 77 | 78 | spyOn(@authService, 'postId').and.callThrough() 79 | @authService.login 'mrpollo', 'password' 80 | 81 | @scope.$apply() 82 | @httpBackend.flush() 83 | 84 | expect(@authService.postId).toHaveBeenCalled() 85 | 86 | # currently there is logic in place to send this headers 87 | # but after careful review its not POSTing data as form-urlendcode 88 | # instead is doing app/js with utf8 89 | xit 'posts user credentials to API with a form content type', -> 90 | @httpBackend.expectPOST("#{@urlBase}/auth/login", {}, (headers) -> 91 | return headers['Content-Type'] == 'application/x-wwww-form-urlencoded' 92 | ).respond @user 93 | 94 | @authService.login 'mrpollo', 'password' 95 | 96 | @scope.$apply() 97 | @httpBackend.flush() 98 | 99 | describe 'password_reset', -> 100 | it 'calls API /pwreset with loginName', -> 101 | spyOn(@authService, 'postId') 102 | @authService.password_reset 'mrpollo' 103 | expect(@authService.postId).toHaveBeenCalled() 104 | 105 | describe 'password_reset_confirm', -> 106 | beforeEach -> 107 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 401, {} 108 | 109 | it 'confirms password reset via token', -> 110 | @httpBackend.expectPOST("#{@urlBase}/auth/pwreset/mrpollo/token").respond @user 111 | spyOn(@authService, 'postId').and.callThrough() 112 | 113 | @authService.password_reset_confirm 'mrpollo', 'token', 'password' 114 | 115 | @scope.$apply() 116 | @httpBackend.flush() 117 | 118 | expect(@authService.postId).toHaveBeenCalledWith "pwreset/mrpollo/token", JSON.stringify('password') 119 | 120 | describe 'email_confirm', -> 121 | beforeEach -> 122 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond 401, {} 123 | 124 | it 'should confirm user email', -> 125 | spyOn(@authService, 'postId') 126 | @authService.email_confirm 'mrpollo', 'token' 127 | expect(@authService.postId).toHaveBeenCalledWith "emailconfirm/mrpollo/token", {} 128 | 129 | describe 'setLoggedIn', -> 130 | beforeEach -> 131 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 132 | 133 | it 'should flag @user.loggedIn as true and return user', -> 134 | spyOn(@authService, 'setLoggedIn').and.callThrough() 135 | expect(@user.loggedIn).toBeUndefined() 136 | @authService.setLoggedIn(@user) 137 | expect(@authService.user.loggedIn).toBeTruthy() 138 | expect(@authService.user.login).toEqual @user.login 139 | 140 | describe 'setLoggedOut', -> 141 | it 'should flag @user.loggedIn as false', -> 142 | spyOn(@authService, 'setLoggedOut').and.callThrough() 143 | @authService.setLoggedIn(@user) 144 | expect(@authService.user.loggedIn).toBeTruthy() 145 | @authService.setLoggedOut() 146 | expect(@authService.user.loggedIn).not.toBeTruthy() 147 | 148 | describe 'getUser', -> 149 | beforeEach -> 150 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 151 | 152 | it 'should return current user object', -> 153 | @authService.user = @user 154 | @scope.$apply() 155 | expect(@authService.getUser()).toEqual @user 156 | 157 | describe 'checkLogin', -> 158 | afterEach -> 159 | @httpBackend.verifyNoOutstandingExpectation() 160 | @httpBackend.verifyNoOutstandingRequest() 161 | 162 | it 'should login is user if succesful', -> 163 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond @user 164 | spyOn(@authService, 'setLoggedIn') 165 | @authService.checkLogin() 166 | 167 | @scope.$apply() 168 | @httpBackend.flush() 169 | 170 | expect(@authService.setLoggedIn).toHaveBeenCalledWith @user 171 | 172 | it 'should call setLoggedOut on error', -> 173 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond(401, '') 174 | spyOn(@authService, 'setLoggedOut') 175 | @authService.checkLogin() 176 | 177 | @scope.$apply() 178 | @httpBackend.flush() 179 | 180 | expect(@authService.setLoggedOut).toHaveBeenCalled() 181 | -------------------------------------------------------------------------------- /test/units/services/missionService.coffee: -------------------------------------------------------------------------------- 1 | jasmine.getJSONFixtures().fixturesPath = '/base/test/fixtures' 2 | 3 | describe "missionService", -> 4 | beforeEach module 'app' 5 | 6 | beforeEach inject ($rootScope, _$httpBackend_, missionService) -> 7 | loadJSONFixtures('dseries.json') 8 | loadJSONFixtures('mission.json') 9 | loadJSONFixtures('parameters.json') 10 | loadJSONFixtures('messages.geo.json') 11 | loadJSONFixtures('staticmap.json') 12 | 13 | @fetchParams = 14 | order_by: "createdAt" 15 | order_dir: "desc" 16 | page_size: "12" 17 | 18 | @urlBase = 'https://api.droneshare.com/api/v1' 19 | @scope = $rootScope.$new() 20 | @missionService = missionService 21 | @httpBackend = _$httpBackend_ 22 | 23 | @httpBackend.whenGET("#{@urlBase}/auth/user").respond({"message":"You are not logged in"}) 24 | @httpBackend.whenGET("#{@urlBase}/mission/staticMap").respond(getJSONFixture('staticmap.json')) 25 | 26 | it 'should get missions', -> 27 | expected = getJSONFixture('missions.json') 28 | notExpected = [{}] 29 | 30 | @httpBackend.expectGET("#{@urlBase}/mission").respond(expected) 31 | 32 | positiveTestSuccess = (results) -> 33 | expect(results).toEqual expected 34 | results 35 | 36 | negativeTestSuccess = (results) -> 37 | expect(results).not.toEqual notExpected 38 | results 39 | 40 | @missionService.get().then(positiveTestSuccess).then(negativeTestSuccess) 41 | 42 | @httpBackend.flush() 43 | 44 | it 'should get a mission by id', -> 45 | expected = getJSONFixture('mission.json') 46 | notExpected = {} 47 | 48 | @httpBackend.expectGET("#{@urlBase}/mission/4750").respond(expected) 49 | 50 | positiveTestSuccess = (results) -> 51 | expect(results).toEqual expected 52 | results 53 | 54 | negativeTestSuccess = (results) -> 55 | expect(results).not.toEqual notExpected 56 | results 57 | 58 | @missionService.getId(4750).then(positiveTestSuccess).then(negativeTestSuccess) 59 | 60 | @httpBackend.flush() 61 | 62 | it 'should get plot data from a mission', -> 63 | expected = getJSONFixture('dseries.json') 64 | notExpected = [] 65 | 66 | @httpBackend.expectGET("#{@urlBase}/mission/4750/dseries").respond(expected) 67 | 68 | positiveTestSuccess = (results) -> 69 | expect(results.data).toEqual expected 70 | results 71 | 72 | negativeTestSuccess = (results) -> 73 | expect(results.data).not.toEqual notExpected 74 | results 75 | 76 | @missionService.get_plotdata(4750).then(positiveTestSuccess).then(negativeTestSuccess) 77 | 78 | @httpBackend.flush() 79 | 80 | it 'should get parameters from a mission', -> 81 | expected = getJSONFixture('parameters.json') 82 | notExpected = [] 83 | 84 | @httpBackend.expectGET("#{@urlBase}/mission/4750/parameters.json").respond(expected) 85 | 86 | positiveTestSuccess = (results) -> 87 | expect(results.data).toEqual expected 88 | results 89 | 90 | negativeTestSuccess = (results) -> 91 | expect(results.data).not.toEqual notExpected 92 | results 93 | 94 | @missionService.get_parameters(4750).then(positiveTestSuccess).then(negativeTestSuccess) 95 | 96 | @httpBackend.flush() 97 | 98 | it 'should get geojson from a mission', -> 99 | expected = getJSONFixture('messages.geo.json') 100 | notExpected = [] 101 | 102 | @httpBackend.expectGET("#{@urlBase}/mission/4750/messages.geo.json").respond(expected) 103 | 104 | positiveTestSuccess = (results) -> 105 | expect(results.data).toEqual expected 106 | results 107 | 108 | negativeTestSuccess = (results) -> 109 | expect(results.data).not.toEqual notExpected 110 | results 111 | 112 | @missionService.get_geojson(4750).then(positiveTestSuccess).then(negativeTestSuccess) 113 | 114 | @httpBackend.flush() 115 | --------------------------------------------------------------------------------