├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── lerna.json ├── package.json └── packages ├── pzgps-angular1 ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bs-config.json ├── package.json └── src │ ├── components │ ├── pzgps-about │ │ ├── pzgps-about.html │ │ └── pzgps-about.js │ ├── pzgps-mapquest │ │ ├── pzgps-mapquest.html │ │ └── pzgps-mapquest.js │ ├── pzgps-navs │ │ ├── pzgps-navs.html │ │ └── pzgps-navs.js │ └── pzgps-websocket │ │ ├── pzgps-websocket.html │ │ └── pzgps-websocket.js │ ├── css │ └── pzgps.css │ ├── index.html │ └── js │ ├── constants │ └── urls.js │ ├── factories │ ├── intercept.js │ └── sckt.js │ └── pzgps.js ├── pzgps-preact ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src │ ├── assets │ │ └── .gitkeep │ ├── components │ │ ├── About │ │ │ └── About.jsx │ │ ├── App │ │ │ └── App.jsx │ │ ├── ContentBox │ │ │ └── ContentBox.jsx │ │ ├── GpsData │ │ │ ├── GpsData.css │ │ │ └── GpsData.jsx │ │ ├── KeyValuePair │ │ │ ├── KeyValuePair.css │ │ │ └── KeyValuePair.jsx │ │ ├── ListRoutes │ │ │ ├── DeleteButton │ │ │ │ ├── DeleteButton.css │ │ │ │ └── DeleteButton.jsx │ │ │ ├── ListRoutes.css │ │ │ └── ListRoutes.jsx │ │ ├── MapQuest │ │ │ ├── MapQuest.css │ │ │ └── MapQuest.jsx │ │ ├── NavBar │ │ │ ├── NavBar.css │ │ │ └── NavBar.jsx │ │ ├── NavButton │ │ │ ├── NavButton.css │ │ │ └── NavButton.jsx │ │ └── RouteEditor │ │ │ ├── CragInput.jsx │ │ │ ├── RouteEditor.css │ │ │ └── RouteEditor.jsx │ ├── index.html │ ├── index.js │ ├── lib │ │ ├── conf.js │ │ ├── db.js │ │ ├── ratings.js │ │ └── sckt.js │ ├── manifest.json │ ├── pwa.js │ └── style │ │ ├── helpers.less │ │ ├── index.less │ │ ├── mixins.less │ │ └── variables.less ├── test │ ├── browser │ │ └── index.js │ └── karma.conf.js └── webpack.config.babel.js ├── pzgps-reactjs ├── .babelrc ├── .eslintrc.yml ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── lib │ └── sckt.js ├── package.json ├── postcss.config.js ├── src │ ├── About │ │ └── about.jsx │ ├── App │ │ └── app.jsx │ ├── ContentBox │ │ └── ContentBox.jsx │ ├── GpsData │ │ ├── GpsData.jsx │ │ └── gpsdata.scss │ ├── MapQuest │ │ ├── MapQuest.jsx │ │ └── mapquest.scss │ ├── NavBar │ │ ├── navbar.jsx │ │ └── navbar.scss │ ├── NavButton │ │ ├── NavButton.jsx │ │ └── navbutton.scss │ ├── index.html │ └── index.jsx ├── webpack.config.js ├── webpack.loaders.js └── webpack.production.config.js └── pzgps-server ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── lib └── daemon.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | 11 | [*.{js,jsx,html,sass}] 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /lerna-debug.log 4 | /lib/mqkey.js 5 | /default.realm 6 | /default.realm.lock 7 | /default.realm.management/** 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pzgps 2 | The goal of this project is to collect data from a GPS unit connected to a Raspberry Pi Zero and stream that data out to a web front end via a WebSocket. 3 | 4 | ## Lerna 5 | This project uses [Lerna](https://github.com/lerna/lerna) to manage the server and front end `packages` (in `--independant` mode). If you want to develop anything, you'll need to install Lerna so you can `lerna bootstrap` which will install all of the dependencies for each repo. 6 | 7 | npm install --global lerna 8 | 9 | ## Pull Requests Accepted! 10 | * Please feel free to submit pull requests. 11 | * This project is meant to be a sandbox for learning various things, so expect things to change. 12 | * If this info turns out to be useful to you, [please let me know](https://twitter.com/dankapusta)! 13 | 14 | ## Using the Server Package 15 | The server package, that provides the GPS data over the WebSocket, resides in [`/packages/pzgps-server/`](https://github.com/kapusta/pzgps/tree/master/packages/pzgps-server) and has [a thorough README.md that you should read](https://github.com/kapusta/pzgps/blob/master/packages/pzgps-server/README.md). 16 | 17 | ## Using the Front End Packages 18 | 19 | ### Preact 20 | Currently [the Preact version](https://github.com/kapusta/pzgps/tree/master/packages/pzgps-preact) of the front end has the most code/features/effort. 21 | 22 | Run the webserver with `npm start`. 23 | 24 | Note the `packages/pzgps-preact/src/lib/conf.js` file, which should be modified to match your pizero's name on your network. You can change the name by logging into the pizero, then... 25 | * `raspi-config` 26 | * Go to `Advanced Options` 27 | * Go to `Hostname` 28 | * Type in a new hostname then hit `Ok` 29 | 30 | ### Enabling a MapQuest staticmap 31 | One of the views can load a [Mapquest "staticmap"](http://www.mapquestapi.com/staticmap/) if you have a "Consumer Key" and provide a module from the NodeJS application that includes that key. 32 | 33 | * [Register for a developer account for free](https://developer.mapquest.com/). 34 | * Go to your new profile, and click the "Manage Keys" on the left side menu. 35 | * Click the "Create a New Key" button and provide a name (callback url is not needed for this project). 36 | * You can always find your Consumer Key on the "Manage Keys" page after creating one. 37 | * Make a file in the `/packages/pzgps-server/lib` directory named `mqkey.js` and format it like the example below. 38 | 39 | 40 | module.exports = { 41 | 'consumerKey': 'PASTE YOUR CONSUMER KEY HERE' 42 | }; 43 | 44 | 45 | When starting the server use the `--mq` flag. An NPM command is provided in `/packages/pzgps-server/package.json` that will start with the MapQuest module included (eg, `npm run withMapquest` will execute `node index.js --port 9000 --mq`). 46 | 47 | Assuming all of the above is in place, the MapQuest component in the UI will receive the key over the WebSocket and use it to formulate the URL to get the static map. Because the client is receiving updates from the server continually, the map will update if the coordinates change. 48 | 49 | 50 | ### AngularJS 51 | In the `/packages/pzgps-angular1/` directory, run `npm start` to start the [webserver](https://github.com/johnpapa/lite-server). The default port of the webserver can be changed in the `/packages/pzgps-angular1/bs-config.json` file. 52 | 53 | 54 | ### ReactJS 55 | In the `/packages/pzgps-reactjs/` directory, run `npm start` to start the webserver. This project uses [webpack](https://webpack.github.io/) and will auto-reload your browser for you. 56 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.38", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "independent" 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dankapusta/pzgps", 3 | "version": "3.3.0", 4 | "description": "Read GPS data from a PiZero in a single page web app via a WebSocket", 5 | "keywords": [ 6 | "gps" 7 | ], 8 | "scripts": { 9 | "lerna-wizard": "lerna-wizard" 10 | }, 11 | "author": "dankapusta", 12 | "license": "Apache-2.0", 13 | "devDependencies": { 14 | "lerna": "2.0.0-beta.38", 15 | "lerna-wizard": "^1.0.6" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /www 3 | /tmp 4 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 2, 3 | "white": false, 4 | "curly": true, 5 | "devel": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": false, 10 | "noarg": true, 11 | "sub": true, 12 | "strict": true, 13 | "undef": true, 14 | "boss": true, 15 | "eqnull": true, 16 | "browser": true, 17 | "laxcomma": true 18 | } 19 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 3 | grunt.loadNpmTasks("grunt-contrib-jshint"); 4 | grunt.loadNpmTasks("grunt-contrib-uglify"); 5 | grunt.loadNpmTasks("grunt-contrib-watch"); 6 | grunt.loadNpmTasks("grunt-contrib-cssmin"); 7 | grunt.loadNpmTasks("grunt-contrib-copy"); 8 | grunt.loadNpmTasks("grunt-contrib-clean"); 9 | grunt.loadNpmTasks("grunt-contrib-concat"); 10 | grunt.loadNpmTasks('grunt-angular-templates'); 11 | grunt.loadNpmTasks("grunt-ng-annotate"); 12 | 13 | // if you simply run "grunt" these default tasks will execute, IN THE ORDER THEY APPEAR! 14 | grunt.registerTask('default', ['jshint', 'clean', 'ngAnnotate', 'ngtemplates', 'uglify', 'concat', 'cssmin', 'copy']); 15 | 16 | grunt.initConfig({ 17 | pkg: grunt.file.readJSON('package.json'), 18 | 19 | jshint: { 20 | files: ['./src/js/**/*.js', './src/components/**/*.js'], 21 | options: { 22 | jshintrc: '.jshintrc' 23 | }, 24 | }, 25 | 26 | clean: { 27 | options: { 28 | force: true, // danger will robinson! 29 | }, 30 | target: { 31 | files: [{ 32 | expand: true, 33 | cwd: './www/', 34 | src: ['js/**', 'css/**', 'index.html'], 35 | }] 36 | } 37 | }, 38 | 39 | ngAnnotate: { 40 | options: { 41 | add: true, 42 | singleQuotes: true 43 | }, 44 | pzgps: { 45 | files: { 46 | './tmp/pzgps.annotated.js': 47 | ['./src/js/pzgps.js', './src/js/**/*.js', './src/components/**/*.js'] 48 | } 49 | } 50 | }, 51 | 52 | // https://github.com/ericclemmons/grunt-angular-templates/blob/master/README.md 53 | ngtemplates: { 54 | 'pzgps': { 55 | cwd: 'src', 56 | src: [ 57 | 'components/**/*.html' 58 | ], 59 | dest: 'tmp/pzgps-components.min.js', 60 | options: { 61 | standalone: false, 62 | prefix: '/', 63 | htmlmin: { // NOTE: disable this if anything breaks 64 | collapseWhitespace: true, 65 | removeRedundantAttributes: true, 66 | removeScriptTypeAttributes: true, 67 | removeStyleLinkTypeAttributes: true, 68 | keepClosingSlash: true // needed for SVGs 69 | } 70 | } 71 | } 72 | }, 73 | 74 | uglify: { 75 | pzgps: { 76 | options: { 77 | sourceMap: true, 78 | report: 'min' 79 | }, 80 | src: ['./tmp/pzgps.annotated.js'], 81 | dest: './tmp/pzgps.uglified.js' 82 | } 83 | }, 84 | 85 | concat: { 86 | 'pzgps': { 87 | src: ['<%= uglify.pzgps.dest %>', '<%= ngtemplates.pzgps.dest %>'], 88 | dest: 'tmp/pzgps.min.js' 89 | } 90 | }, 91 | 92 | /* (dest : src) */ 93 | cssmin: { 94 | compress: { 95 | files: { 96 | './tmp/pzgps.min.css': ['./src/css/pzgps.css'] 97 | } 98 | } 99 | }, 100 | 101 | 102 | copy: { 103 | idx: { 104 | files: [ 105 | { 106 | expand: false, 107 | src: ['./src/index.html'], 108 | dest: './www/index.html', 109 | filter: 'isFile' 110 | } 111 | ] 112 | }, 113 | js: { 114 | files: [ 115 | { 116 | expand: true, 117 | flatten: true, 118 | src: [ 119 | 'tmp/pzgps.min.js', 120 | 'tmp/pzgps.uglified.js.map', 121 | 'node_modules/angular-websocket/dist/angular-websocket.min.js' 122 | ], 123 | dest: './www/js/', 124 | filter: 'isFile' 125 | } 126 | ] 127 | }, 128 | css: { 129 | files: [ 130 | { 131 | expand: true, 132 | flatten: true, 133 | src: ['tmp/pzgps.min.css'], 134 | dest: './www/css/', 135 | filter: 'isFile' 136 | } 137 | ] 138 | } 139 | }, 140 | 141 | watch: { 142 | stuff: { 143 | files: "<%= './src/**/*' %>", 144 | tasks: ["default"] 145 | }, 146 | } 147 | }); 148 | }; 149 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/README.md: -------------------------------------------------------------------------------- 1 | # pzgps 2 | ======= 3 | 4 | An AngularJS 1.x UI for pzgps 5 | 6 | ## Get Started 7 | 8 | * `sudo npm install -g live-server` an http server, installed globally 9 | * `npm install` - installs your site and build dependencies 10 | * `npm start` - starts a web server using the assets in the `www` directory 11 | * `grunt` - (re)builds the site 12 | 13 | ## License 14 | 15 | Apache Version 2 16 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/bs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 9001, 3 | "reloadDebounce": 250, 4 | "files": ["www/**/*.{html,htm,css,js}"], 5 | "server": { 6 | "baseDir": "www", 7 | "index": "index.html" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pzgps-angular1", 3 | "version": "1.0.1", 4 | "description": "pzgps fronted by AngularJS 1.x", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Daniel Kapusta", 8 | "url": "http://about.me/dankapusta" 9 | }, 10 | "scripts": { 11 | "start": "concurrently --kill-others 'lite-server -c bs-config.json' 'grunt watch'", 12 | "server": "lite-server -c bs-config.json" 13 | }, 14 | "devDependencies": { 15 | "concurrently": "^3.1.0", 16 | "grunt": "^0.4.5", 17 | "grunt-angular-templates": "^1.1.0", 18 | "grunt-contrib-clean": "^0.6.0", 19 | "grunt-contrib-concat": "^1.0.1", 20 | "grunt-contrib-copy": "^0.8.0", 21 | "grunt-contrib-cssmin": "^0.12.3", 22 | "grunt-contrib-jshint": "^0.11.2", 23 | "grunt-contrib-uglify": "^0.9.1", 24 | "grunt-contrib-watch": "^0.6.1", 25 | "grunt-ng-annotate": "^1.0.1", 26 | "lite-server": "^2.2.2" 27 | }, 28 | "dependencies": { 29 | "angular-websocket": "^2.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-about/pzgps-about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

pzgps

5 | 6 |

This UI is built with angular 1.x and uses angular-websocket to talk to the websocket server. The URL to the server lives in src/js/constants/urls.js. When you click the Raw GPS Data button, that component will run and the connection the server will be made.

7 | 8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-about/pzgps-about.js: -------------------------------------------------------------------------------- 1 | /** 2 | @memberof pzgps 3 | @ngdoc component 4 | @name pzgpsAbout 5 | @description about the site 6 | @example 7 | */ 8 | (function(angular){ 9 | 'use strict'; 10 | angular.module('pzgps').component('pzgpsAbout', { 11 | templateUrl: '/components/pzgps-about/pzgps-about.html', 12 | controllerAs: 'ctrl', 13 | controller: function($log) { 14 | $log.log('pzgpsAbout component is running'); 15 | var ctrl = this; 16 | } 17 | }); 18 | }(window.angular)); 19 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-mapquest/pzgps-mapquest.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

MapQuest Map

5 | Looking for data... 6 | 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-mapquest/pzgps-mapquest.js: -------------------------------------------------------------------------------- 1 | /** 2 | @memberof pzgps 3 | @ngdoc component 4 | @name pzgpsMapquest 5 | @description opens a websocket and gets gps data 6 | @example 7 | */ 8 | (function(angular){ 9 | 'use strict'; 10 | angular.module('pzgps').component('pzgpsMapquest', { 11 | templateUrl: '/components/pzgps-mapquest/pzgps-mapquest.html', 12 | controllerAs: 'ctrl', 13 | controller: function($log, sckt, urls) { 14 | $log.log('pzgpsMapquest component is running'); 15 | 16 | var ctrl = this; 17 | ctrl.gpsData = null; 18 | ctrl.consumerKey = ''; 19 | 20 | ctrl.getConsumerKey = function() { 21 | ctrl.socket.send({ 22 | 'action': 'pzgps.get.consumerKey' 23 | }); 24 | }; 25 | 26 | ctrl.$onInit = function() { 27 | ctrl.socket = sckt.connect(urls.gps).socket; 28 | ctrl.socket.onMessage(function(message) { 29 | var parsedData = JSON.parse(message.data); 30 | 31 | if (parsedData.consumerKey) { 32 | ctrl.consumerKey = parsedData.consumerKey; 33 | } 34 | if (parsedData.lat) { 35 | ctrl.gpsData = parsedData; 36 | } 37 | }); 38 | 39 | ctrl.getConsumerKey(); 40 | 41 | }; 42 | 43 | 44 | } 45 | }); 46 | }(window.angular)); 47 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-navs/pzgps-navs.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | About 5 | 6 | 7 | 8 | Raw GPS Data 9 | 10 | 11 | 12 | MapQuest 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-navs/pzgps-navs.js: -------------------------------------------------------------------------------- 1 | /** 2 | @memberof pzgps 3 | @ngdoc component 4 | @name pzgpsNavs 5 | @description opens a websocket and gets gps data 6 | @example 7 | */ 8 | (function(angular){ 9 | 'use strict'; 10 | angular.module('pzgps').component('pzgpsNavs', { 11 | templateUrl: '/components/pzgps-navs/pzgps-navs.html', 12 | controllerAs: 'ctrl', 13 | controller: function($log, $location, $rootScope) { 14 | $log.log('pzgpsNavs component is running'); 15 | var ctrl = this; 16 | 17 | $rootScope.$on('$routeChangeSuccess', function (event, current, previous, rejection) { 18 | ctrl.$location = $location.url(); 19 | }); 20 | 21 | } 22 | }); 23 | }(window.angular)); 24 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-websocket/pzgps-websocket.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Looking for data... 5 | 6 |
7 |

Location Details

8 | Latitude {{ctrl.gpsData.lat}} 9 |
10 | Longitude {{ctrl.gpsData.lon}} 11 |
12 | Altitude {{ctrl.gpsData.alt}} meters 13 |
14 | 15 |

Raw Data

16 |
    17 |
  • {{k}} - {{v}}
  • 18 |
19 | 20 |
21 | 22 |
23 | 24 | 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/components/pzgps-websocket/pzgps-websocket.js: -------------------------------------------------------------------------------- 1 | /** 2 | @memberof pzgps 3 | @ngdoc component 4 | @name pzgpsWebsocket 5 | @description opens a websocket and gets gps data 6 | @example 7 | */ 8 | (function(angular){ 9 | 'use strict'; 10 | angular.module('pzgps').component('pzgpsWebsocket', { 11 | templateUrl: '/components/pzgps-websocket/pzgps-websocket.html', 12 | controllerAs: 'ctrl', 13 | controller: function($log, $scope, sckt, urls) { 14 | $log.log('pzgpsWebsocket component is running'); 15 | var ctrl = this; 16 | 17 | ctrl.$onInit = function() { 18 | ctrl.socket = sckt.connect(urls.gps).socket; 19 | ctrl.socket.onMessage(function(message) { 20 | ctrl.gpsData = JSON.parse(message.data); 21 | }); 22 | }; 23 | 24 | // $scope.$on('$destroy', function(evt) { 25 | // sckt.disconnect(urls.gps); 26 | // }); 27 | 28 | } 29 | }); 30 | }(window.angular)); 31 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/css/pzgps.css: -------------------------------------------------------------------------------- 1 | .keyname { 2 | display: inline-block; 3 | width: 80px; 4 | font-weight: bold; 5 | } 6 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pzgps 5 | 6 | 7 | 8 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/js/constants/urls.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 'use strict'; 3 | 4 | angular.module('pzgps').constant('urls', { 5 | 'gps': 'ws://circ.local:9000', 6 | }); 7 | 8 | }(window.angular)); 9 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/js/factories/intercept.js: -------------------------------------------------------------------------------- 1 | (function(angular){ 2 | 'use strict'; 3 | 4 | /** 5 | @name intercept 6 | @memberof pzgps 7 | @ngdoc factory 8 | @description intercepts inbound and outbound http connections 9 | @see https://gist.github.com/gnomeontherun/5678505 10 | */ 11 | angular.module('pzgps').factory('intercept', ['$q', '$rootScope', '$log', function($q, $rootScope, $log) { 12 | $log.log("intercept factory is running"); 13 | 14 | var request = function(obj) { 15 | // Access to lots of great stuff in here... 16 | // obj.headers (object), obj.method (string), obj.url (string), obj.withCredentials (boolean) 17 | //$log.log("intercept: request"); 18 | //$log.log(obj); 19 | // transform the response here if you need to, the request config is in config 20 | return obj || $q.when(obj); 21 | }; 22 | 23 | var requestError = function(obj) { // a failed request 24 | //$log.log("intercept: requestError"); 25 | //$log.log(obj); 26 | return $q.reject(obj); 27 | }; 28 | 29 | var response = function(obj) { // a succuesful response 30 | //$log.log("intercept: response"); 31 | //$log.log(obj); 32 | // transform the response here if you need to, the deserialize JSON is in obj.data 33 | return obj || $q.when(obj); 34 | }; 35 | 36 | var responseError = function(obj) { // a failed response 37 | $log.log("intercept factory caught an error: " + obj.status); 38 | //$log.log(obj); // this object is handed to $http and .then() fires with the object 39 | return $q.reject(obj); 40 | }; 41 | 42 | return { 43 | 'request': request, 44 | 'requestError': requestError, 45 | 'response': response, 46 | 'responseError': responseError 47 | }; 48 | 49 | }]); 50 | 51 | }(window.angular)); 52 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/js/factories/sckt.js: -------------------------------------------------------------------------------- 1 | (function(angular){ 2 | 'use strict'; 3 | 4 | /** 5 | @name sckt 6 | @memberof pzgps 7 | @ngdoc factory 8 | @description uses the $websocket service to provide reuseable sockets to components 9 | */ 10 | angular.module('pzgps').factory('sckt', function($q, $rootScope, $log, $websocket) { 11 | $log.log("sckt factory is running"); 12 | 13 | var sockets = {}; 14 | 15 | var connect = function(url) { 16 | var b64 = window.btoa(url); 17 | if (sockets[b64]) { 18 | sockets[b64].connections++; 19 | return sockets[b64]; 20 | } else { 21 | var socket = $websocket(url); 22 | sockets[b64] = { 23 | 'socket': socket, 24 | 'connections' : 1 25 | }; 26 | return sockets[b64]; 27 | } 28 | }; 29 | 30 | var disconnect = function(url) { 31 | var b64 = window.btoa(url); 32 | if (sockets[b64]) { 33 | sockets[b64].connections--; 34 | } 35 | if (!sockets[b64].connections) { 36 | sockets[b64].socket.close(); 37 | delete sockets[b64]; 38 | } 39 | $log.log(Object.keys(sockets).length, 'sockets'); 40 | }; 41 | 42 | return { 43 | 'connect': connect, 44 | 'disconnect': disconnect 45 | }; 46 | 47 | }); 48 | 49 | }(window.angular)); 50 | -------------------------------------------------------------------------------- /packages/pzgps-angular1/src/js/pzgps.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | 'use strict'; // ECMA5 strict mode 3 | 4 | var pzgps = angular.module('pzgps', ['ngRoute', 'ngWebSocket']); 5 | 6 | // Configure the module. 7 | pzgps.config(function($routeProvider, $locationProvider, $httpProvider) { 8 | var baseUrl = '/'; 9 | 10 | $locationProvider.html5Mode(true); 11 | 12 | $routeProvider 13 | .when(baseUrl, { 14 | template: function(params){ 15 | return ''; 16 | } 17 | }) 18 | .when(baseUrl + ':component', { 19 | template: function(params){ 20 | return ''; 21 | } 22 | }) 23 | .otherwise({redirectTo: baseUrl}); 24 | 25 | }); 26 | 27 | pzgps.run(function($routeParams, $route, $rootScope, $http, $location) { 28 | 29 | $http.defaults.headers.get = { 30 | 'Content-Type': 'application/json' 31 | }; 32 | 33 | $rootScope.$on('$routeChangeError', function (event, current, previous, rejection) { 34 | $location.replace().path($rootScope.baseUrl); 35 | }); 36 | 37 | }); 38 | 39 | }(window.angular)); 40 | -------------------------------------------------------------------------------- /packages/pzgps-preact/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMaps": true, 3 | "presets": [ 4 | "es2015-minimal", 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | ["transform-decorators-legacy"], 9 | ["transform-react-jsx", { "pragma": "h" }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/pzgps-preact/.eslintignore: -------------------------------------------------------------------------------- 1 | test/*.conf.js 2 | -------------------------------------------------------------------------------- /packages/pzgps-preact/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "mocha": true, 8 | "es6": true 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "modules": true, 13 | "jsx": true 14 | } 15 | }, 16 | "globals": {}, 17 | "rules": { 18 | "no-empty": 0, 19 | "no-console": 0, 20 | "no-unused-vars": [0, { "varsIgnorePattern": "^h$" }], 21 | "no-cond-assign": 1, 22 | "semi": [2, "always"], 23 | "camelcase": 0, 24 | "comma-style": 2, 25 | "comma-dangle": [2, "never"], 26 | "indent": ["error", 2], 27 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 28 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 29 | "max-nested-callbacks": [2, 3], 30 | "no-eval": 2, 31 | "no-implied-eval": 2, 32 | "no-new-func": 2, 33 | "guard-for-in": 2, 34 | "eqeqeq": 1, 35 | "no-else-return": 2, 36 | "no-redeclare": 2, 37 | "no-dupe-keys": 2, 38 | "radix": 2, 39 | "strict": [2, "never"], 40 | "no-shadow": 0, 41 | "no-delete-var": 2, 42 | "no-undef-init": 2, 43 | "no-shadow-restricted-names": 2, 44 | "handle-callback-err": 0, 45 | "no-lonely-if": 2, 46 | "keyword-spacing": 2, 47 | "constructor-super": 2, 48 | "no-this-before-super": 2, 49 | "no-dupe-class-members": 2, 50 | "no-const-assign": 2, 51 | "prefer-spread": 2, 52 | "no-useless-concat": 2, 53 | "no-var": 2, 54 | "object-shorthand": 2, 55 | "prefer-arrow-callback": 2 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/pzgps-preact/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /build 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/pzgps-preact/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | -------------------------------------------------------------------------------- /packages/pzgps-preact/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /packages/pzgps-preact/README.md: -------------------------------------------------------------------------------- 1 | # pzgps-preact 2 | 3 | This [Preact based](https://github.com/developit/preact-boilerplate) UI is intended to be used with [pzgps-server](https://github.com/kapusta/pzgps/tree/master/packages/pzgps-server). Please see that package's [README](https://github.com/kapusta/pzgps/blob/master/packages/pzgps-server/README.md) for more information. 4 | 5 | --- 6 | 7 | 8 | # Quick-Start Guide 9 | 10 | - [Installation](#installation) 11 | - [Development Workflow](#development) 12 | - [Production Build](#production-build) 13 | - [Lint Your Code](#lint-your-code) 14 | - [Testing](#testing) 15 | - [CSS Modules](#css-modules) 16 | - [Handling URLS](#handling-urls) 17 | - [License](#license) 18 | 19 | 20 | ## Installation 21 | 22 | npm install 23 | 24 | 25 | ## Development 26 | 27 | Start a live-reload development server 28 | 29 | npm start 30 | 31 | 32 | This is a full web server nicely suited to your project. Any time you make changes within the `src` directory, it will rebuild and even refresh your browser. 33 | 34 | 35 | ## Production Build 36 | 37 | npm run build 38 | 39 | 40 | You can now deploy the contents of the `build` directory to production! 41 | 42 | 43 | ## Lint Your Code 44 | 45 | npm run lint 46 | 47 | 48 | ## Testing 49 | 50 | npm test 51 | 52 | 53 | ## CSS Modules 54 | 55 | This project is set up to support [CSS Modules](https://github.com/css-modules/css-modules). By default, styles in `src/style` are **global** (not using CSS Modules) to make global declarations, imports and helpers easy to declare. Styles in `src/components` are loaded as CSS Modules via [Webpack's css-loader](https://github.com/webpack/css-loader#css-modules). Modular CSS namespaces class names, and when imported into JavaScript returns a mapping of canonical (unmodified) CSS classes to their local (namespaced/suffixed) counterparts. 56 | 57 | When imported, this LESS/CSS: 58 | 59 | ```css 60 | .redText { color:red; } 61 | .blueText { color:blue; } 62 | ``` 63 | 64 | ... returns the following map: 65 | 66 | ```js 67 | import styles from './style.css'; 68 | console.log(styles); 69 | // { 70 | // redText: 'redText_local_9gt72', 71 | // blueText: 'blueText_local_9gt72' 72 | // } 73 | ``` 74 | 75 | Note that the suffix for local classNames is generated based on an md5 hash of the file. Changing the file changes the hash. 76 | 77 | 78 | ## Handling URLS 79 | 80 | :information_desk_person: This project contains a basic two-page app with [URL routing](http://git.io/preact-router). 81 | 82 | Pages are just regular components that get mounted when you navigate to a certain URL. Any URL parameters get passed to the component as `props`. 83 | 84 | Defining what component(s) to load for a given URL is easy and declarative. You can even mix-and-match URL parameters and normal props. 85 | 86 | ```js 87 | 88 | 89 | 90 | 91 | 92 | ``` 93 | 94 | 95 | --- 96 | 97 | 98 | ## License 99 | 100 | Apache 2.0 101 | -------------------------------------------------------------------------------- /packages/pzgps-preact/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pzgps-preact", 3 | "version": "1.1.5", 4 | "description": "pzgps fronted by Preact.", 5 | "scripts": { 6 | "start": "cross-env NODE_ENV=development webpack-dev-server --inline --hot --progress", 7 | "build": "cross-env NODE_ENV=production webpack -p --progress", 8 | "prebuild": "mkdirp build && ncp src/assets build/assets", 9 | "test": "npm run -s lint && npm run -s test:karma", 10 | "test:karma": "karma start test/karma.conf.js --single-run", 11 | "lint": "eslint --ext .js src --ext .js,.jsx || exit 0" 12 | }, 13 | "keywords": [ 14 | "preact", 15 | "pzgps", 16 | "gps" 17 | ], 18 | "license": "Apache-2.0", 19 | "contributors": [ 20 | { 21 | "name": "Jason Miller", 22 | "email": "jason@developit.ca" 23 | }, 24 | { 25 | "name": "Daniel Kapusta", 26 | "email": "kapusta@gmail.com", 27 | "url": "https://github.com/kapusta/" 28 | } 29 | ], 30 | "devDependencies": { 31 | "autoprefixer": "^6.4.0", 32 | "babel": "^6.5.2", 33 | "babel-core": "^6.23.1", 34 | "babel-eslint": "^7.1.1", 35 | "babel-loader": "^6.2.5", 36 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 37 | "babel-plugin-transform-react-jsx": "^6.8.0", 38 | "babel-preset-es2015": "^6.22.0", 39 | "babel-preset-es2015-minimal": "^2.1.0", 40 | "babel-preset-stage-0": "^6.22.0", 41 | "babel-register": "^6.18.0", 42 | "babel-runtime": "^6.23.0", 43 | "chai": "^3.5.0", 44 | "copy-webpack-plugin": "^4.0.1", 45 | "core-js": "^2.4.1", 46 | "cross-env": "^2.0.0", 47 | "css-loader": "^0.25.0", 48 | "eslint": "^6.7.2", 49 | "extract-text-webpack-plugin": "^1.0.1", 50 | "file-loader": "^0.9.0", 51 | "html-webpack-plugin": "^2.22.0", 52 | "json-loader": "^0.5.4", 53 | "karma": "^1.0.0", 54 | "karma-chai": "^0.1.0", 55 | "karma-chai-sinon": "^0.1.5", 56 | "karma-mocha": "^1.0.1", 57 | "karma-mocha-reporter": "^2.1.0", 58 | "karma-phantomjs-launcher": "^1.0.2", 59 | "karma-sourcemap-loader": "^0.3.7", 60 | "karma-webpack": "^1.8.0", 61 | "less": "^2.7.1", 62 | "less-loader": "^2.2.3", 63 | "mkdirp": "^0.5.1", 64 | "mocha": "^3.0.0", 65 | "ncp": "^2.0.0", 66 | "offline-plugin": "^4.5.5", 67 | "path": "^0.12.7", 68 | "phantomjs-prebuilt": "^2.1.12", 69 | "postcss-loader": "^1.2.0", 70 | "raw-loader": "^0.5.1", 71 | "sinon": "^1.17.5", 72 | "sinon-chai": "^2.8.0", 73 | "source-map-loader": "^0.1.5", 74 | "style-loader": "^0.13.0", 75 | "superstatic": "^4.0.3", 76 | "url-loader": "^0.5.7", 77 | "webpack": "^1.14.0", 78 | "webpack-dev-server": "^1.16.2" 79 | }, 80 | "dependencies": { 81 | "bootstrap": "^4.0.0-alpha.6", 82 | "classnames": "^2.2.5", 83 | "font-awesome": "^4.7.0", 84 | "haversine": "^1.0.2", 85 | "lodash": "^4.17.4", 86 | "pouchdb": "^6.1.1", 87 | "preact": "^7.1.0", 88 | "preact-compat": "^3.9.4", 89 | "preact-router": "^2.3.2", 90 | "proptypes": "^0.14.3", 91 | "react-select": "^1.0.0-rc.2", 92 | "shortid": "^2.2.6" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapusta/pzgps/854a07650f1d59a139c484ca1c0f16d9e266508d/packages/pzgps-preact/src/assets/.gitkeep -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/About/About.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | 3 | class About extends Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | render() { 8 | return ( 9 |
10 |
11 |

About

12 |
13 |

14 | This is a Preact implementation of the GPS front end based on the preact-boilerplate project. 15 |

16 |

17 | If the server on the pizero is set up correctly, more buttons will appear above after establishing the WebSocket connection. 18 |

19 |
20 |
21 |
22 | 23 |
24 |

Settings

25 |
26 |
    27 |
  • setting a
  • 28 |
  • setting b
  • 29 |
  • setting c
  • 30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | export default About; 39 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/App/App.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import sckt from '../../lib/sckt.js'; 3 | import conf from '../../lib/conf.js'; 4 | import NavBar from '../NavBar/NavBar.jsx'; 5 | import ContentBox from '../ContentBox/ContentBox.jsx'; 6 | import db from '../../lib/db.js'; 7 | 8 | require('offline-plugin/runtime').install(); 9 | 10 | class App extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | componentName: 'About', 15 | consumerKey: '', 16 | gpsData: {}, 17 | socket: {} 18 | }; 19 | } 20 | componentWillMount() { 21 | db.init(); 22 | sckt.init(conf.socketServer, this); 23 | } 24 | componentWillUnmount() { 25 | this.state.socket.close(); 26 | } 27 | // method is passed down to App -> NavBar -> NavButton 28 | handleClick = name => { 29 | this.setState({ 30 | componentName: name 31 | }); 32 | } 33 | render() { 34 | return ( 35 |
36 | 42 | 48 |
49 | ); 50 | } 51 | } 52 | 53 | export default App; 54 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/ContentBox/ContentBox.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import About from '../About/About.jsx'; 3 | import GpsData from '../GpsData/GpsData.jsx'; 4 | import MapQuest from '../MapQuest/MapQuest.jsx'; 5 | import RouteEditor from '../RouteEditor/RouteEditor.jsx'; 6 | 7 | // @description this is kind of gross, but neccessary 8 | // @see https://github.com/facebook/react/issues/3365 9 | const contentComponents = { 10 | About, 11 | GpsData, 12 | MapQuest, 13 | RouteEditor 14 | }; 15 | 16 | class ContentBox extends Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | render({children, content, gpsData, databases}) { 21 | let Content = contentComponents[content]; 22 | switch (content) { 23 | case 'MapQuest': 24 | return ( 25 |
26 | {children} 27 |
28 | ); 29 | case 'RouteEditor': 30 | return ( 31 |
32 | 37 | {children} 38 | 39 |
40 | ); 41 | default: 42 | return ( 43 |
44 | 47 | {children} 48 | 49 |
50 | ); 51 | } 52 | } 53 | } 54 | 55 | export default ContentBox; 56 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/GpsData/GpsData.css: -------------------------------------------------------------------------------- 1 | .rawdata { 2 | max-height: 300px; 3 | overflow: auto; 4 | } 5 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/GpsData/GpsData.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import RouteEditor from '../RouteEditor/RouteEditor.jsx'; 3 | import shortid from 'shortid'; 4 | import classnames from 'classnames/bind'; 5 | import styles from './GpsData.css'; 6 | import Select from 'react-select'; 7 | import KeyValuePair from '../KeyValuePair/KeyValuePair.jsx'; 8 | 9 | class GpsData extends Component { 10 | constructor(props) { 11 | super(props); 12 | } 13 | selectChanged = val => { 14 | this.setState({ 15 | climbRating: val 16 | }); 17 | } 18 | handleChange = evt => { 19 | this.setState({ 20 | [evt.target.id]: evt.target.value 21 | }); 22 | } 23 | render() { 24 | let cn = classnames.bind(styles); 25 | let rawdata = cn('card card-block', { 26 | 'rawdata': true 27 | }); 28 | return ( 29 |
30 |
31 |

GPS Data

32 |
33 | 37 | 41 | 45 |
46 |
47 |
48 | 49 |
50 |

Raw Data

51 |
52 |
53 | {Object.keys(this.props.gpsData).map((val) => { 54 | return ( 55 | 60 | ); 61 | })} 62 |
63 |
64 |
65 |
66 | ); 67 | } 68 | } 69 | 70 | export default GpsData; 71 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/KeyValuePair/KeyValuePair.css: -------------------------------------------------------------------------------- 1 | .label { 2 | font-weight: bold; 3 | min-width: 100px; 4 | } 5 | .mybuttons { 6 | margin-left: 10px; 7 | } 8 | .search-enabled { 9 | cursor: pointer; 10 | opacity: 1; 11 | } 12 | .search-disabled { 13 | cursor: default; 14 | opacity: 0.6 15 | } 16 | .blue { 17 | background-color: #0275d8; 18 | color: #ffffff; 19 | } 20 | 21 | @media (min-width: 576px) { 22 | .row-route { 23 | margin: 0px 0px 10px 0px; 24 | } 25 | } 26 | 27 | @media (min-width: 992px) { 28 | .label { 29 | line-height: 40px; 30 | } 31 | .value { 32 | line-height: 40px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/KeyValuePair/KeyValuePair.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import proptypes from 'proptypes'; 3 | import classnames from 'classnames/bind'; 4 | import styles from './KeyValuePair.css'; 5 | 6 | class KeyValuePair extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render({label, value}) { 11 | let cn = classnames.bind(styles); 12 | let rowStyles = cn('row', { 13 | 'row-route': true 14 | }); 15 | let labelStyles = cn('col-lg-2 col-form-label', { 16 | 'label': true 17 | }); 18 | let valueStyles = cn('col-lg-6', { 19 | 'value': true 20 | }); 21 | 22 | return ( 23 | 24 |
25 | 26 |
27 | {value} 28 |
29 |
30 | 31 | ); 32 | } 33 | } 34 | 35 | export default KeyValuePair; 36 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/ListRoutes/DeleteButton/DeleteButton.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapusta/pzgps/854a07650f1d59a139c484ca1c0f16d9e266508d/packages/pzgps-preact/src/components/ListRoutes/DeleteButton/DeleteButton.css -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/ListRoutes/DeleteButton/DeleteButton.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import proptypes from 'proptypes'; 3 | import classNames from 'classnames/bind'; 4 | import styles from './DeleteButton.css'; 5 | 6 | class DeleteButton extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | deleteRoute = () => { 11 | this.setState({ 12 | deleting: true 13 | }); 14 | this.props.removeRoute(this.props.route); 15 | // after the delete happens, the parent component will refetch the route list 16 | // and deliver that to this component forcing a re-render which removes the 17 | // route 'doc' from the view, so no need to reset `deleting` to false 18 | } 19 | render() { 20 | let cn = classNames.bind(styles); 21 | let deleteButton = cn('btn btn-sm btn-primary'); 22 | let deleteButtonIcon = cn('fa', { 23 | 'fa-trash': !this.props.deleting, 24 | 'fa-spinner': this.props.deleting, 25 | 'fa-spin': this.props.deleting 26 | }); 27 | return ( 28 | 36 | ); 37 | } 38 | } 39 | 40 | export default DeleteButton; 41 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/ListRoutes/ListRoutes.css: -------------------------------------------------------------------------------- 1 | .routes { 2 | margin-top: 0px; 3 | } 4 | .cell { 5 | background: white; 6 | padding: 5px; 7 | } 8 | .trash { 9 | width: 10px; 10 | text-align: center; 11 | } 12 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/ListRoutes/ListRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import shortid from 'shortid'; 3 | import haversine from 'haversine'; 4 | import classNames from 'classnames/bind'; 5 | import styles from './ListRoutes.css'; 6 | import DeleteButton from './DeleteButton/DeleteButton.jsx'; 7 | 8 | class ListRoutes extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | gettingRoutes: true 13 | }; 14 | } 15 | componentWillUpdate = () => { 16 | this.setState({ 17 | gettingRoutes: false 18 | }); 19 | } 20 | distance = (from, to) => { 21 | let unit = 'mile'; 22 | let d = haversine(from, to, { unit }); 23 | if (d <= 1) { 24 | unit = 'meter'; 25 | d = haversine(from, to, { unit }); 26 | } 27 | return parseFloat(d).toFixed(2) + ' ' + unit + 's'; 28 | } 29 | render() { 30 | let cn = classNames.bind(styles); 31 | let routeBlock = cn('col-lg-6', { 32 | 'routes': true 33 | }); 34 | let cell = cn({ 35 | 'cell': true 36 | }); 37 | let trash = cn({ 38 | 'trash': true 39 | }); 40 | 41 | return ( 42 |
43 |

My Routes

44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {(this.state.gettingRoutes) ? 57 | 58 | 59 | : '' 60 | } 61 | 62 | {this.props.routeList.map((route) => { 63 | return ( 64 | 65 | 72 | 73 | 74 | 83 | 89 | 90 | ); 91 | })} 92 | 93 |
RouteRatingPitchesDistance
Loading...
66 | {route.name} 71 | {route.rating}{route.pitches} 75 | {this.distance({ 76 | latitude: this.props.lat, 77 | longitude: this.props.lon 78 | }, { 79 | latitude: route.lat, 80 | longitude: route.lon 81 | })} 82 | 84 | 88 |
94 |
95 | ); 96 | } 97 | } 98 | 99 | export default ListRoutes; 100 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/MapQuest/MapQuest.css: -------------------------------------------------------------------------------- 1 | .mapdiv { 2 | padding: 0px; 3 | margin: 0px; 4 | min-height: 480px; 5 | background-repeat: no-repeat; 6 | background-position: initial; 7 | background-size: cover; 8 | } 9 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/MapQuest/MapQuest.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import classNames from 'classnames/bind'; 3 | import styles from './MapQuest.css'; 4 | 5 | class MapQuest extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | mapUrl: '' 10 | }; 11 | } 12 | componentDidMount() { 13 | this.setState({ 14 | mapUrl: 'https://www.mapquestapi.com/staticmap/v4/getmap?key=' 15 | + this.props.consumerKey 16 | + '¢er=' + this.props.gpsData.lat + ',' + this.props.gpsData.lon 17 | + '&zoom=10&size=640,480&type=map&imagetype=png&pois=blue,' 18 | + this.props.gpsData.lat + ',' + this.props.gpsData.lon 19 | }); 20 | } 21 | render() { 22 | let cn = classNames.bind(styles); 23 | let divStyles = cn('card card-block', { 24 | 'mapdiv': true 25 | }); 26 | 27 | return ( 28 |
29 |
30 |

MapQuest Staticmap

31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | 38 | export default MapQuest; 39 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/NavBar/NavBar.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | padding: 10px 10px 20px 0px; 3 | } 4 | .nav-icon { 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/NavBar/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import classNames from 'classnames/bind'; 3 | import styles from './NavBar.css'; 4 | import NavButton from '../NavButton/NavButton.jsx'; 5 | 6 | class Navbar extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render() { 11 | let cn = classNames.bind(styles); 12 | let navIcon = cn('col-3', { 13 | 'nav-icon': true 14 | }); 15 | return ( 16 | 55 | ); 56 | } 57 | } 58 | 59 | export default Navbar; 60 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/NavButton/NavButton.css: -------------------------------------------------------------------------------- 1 | .button { 2 | cursor: pointer; 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/NavButton/NavButton.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import proptypes from 'proptypes'; 3 | import classNames from 'classnames/bind'; 4 | import styles from './NavButton.css'; 5 | 6 | class Navbutton extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.handleClick = this.handleClick.bind(this); 10 | } 11 | handleClick = e => { 12 | //console.log('button click event', e); 13 | this.props.handleClick(this.props.section); 14 | } 15 | render({children}) { 16 | let cx = classNames.bind(styles); 17 | let buttonStyles = cx('btn', { 18 | 'btn-info': this.props.isActive, 19 | 'button': true 20 | }); 21 | return ( 22 | {children} 26 | ); 27 | } 28 | } 29 | 30 | export default Navbutton; 31 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/RouteEditor/CragInput.jsx: -------------------------------------------------------------------------------- 1 | import { h, Component } from 'preact'; 2 | import proptypes from 'proptypes'; 3 | import merge from 'lodash/merge'; 4 | import classNames from 'classnames/bind'; 5 | import styles from './RouteEditor.css'; 6 | 7 | class CragInput extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | cragName: '' 12 | }; 13 | } 14 | handleInputChange = evt => { 15 | if (evt.keyCode === 13) { // return 16 | this.search({ 17 | db: this.props.db, 18 | name: evt.target.value 19 | }); 20 | } 21 | this.setState({ 22 | cragName: evt.target.value 23 | }); 24 | this.props.handleCragChange(this.state.cragName); 25 | } 26 | search = ({db, name}) => { 27 | if (name.length) { 28 | this.setState({ 29 | searching: true 30 | }); 31 | // get data based on the name, if found, use the data to 32 | // prepopulate the rest of the form 33 | db.get(name) 34 | .then(doc => { 35 | let crag = merge({}, { 36 | name: doc.name 37 | }); 38 | this.setState({ 39 | searching: false 40 | }); 41 | this.props.handleCragChange(this.state.cragName); 42 | }) 43 | .catch(err => { 44 | this.setState({ 45 | searching: false 46 | }); 47 | console.error('searched for', name, 'but not found', err); 48 | }); 49 | } 50 | } 51 | render({label, db, handleCragChange}) { 52 | let cn = classNames.bind(styles); 53 | let rowStyles = cn('row', { 54 | 'row-route': true 55 | }); 56 | let labelStyles = cn('col-lg-2 col-form-label', { 57 | 'label': true 58 | }); 59 | let inputGroupAddon = cn('input-group-addon', { 60 | 'blue': this.state.cragName 61 | }); 62 | let searchButtonStyles = cn('fa', { 63 | 'search-enabled': this.state.cragName, // there has to be a better... 64 | 'search-disabled': !this.state.cragName, // ...way to do this. 65 | 'fa-search-plus': !this.state.searching, 66 | 'fa-spinner': this.state.searching, 67 | 'fa-spin': this.state.searching 68 | }); 69 | return ( 70 | 71 |
72 | 73 |
74 |
75 | 83 | this.search({ 86 | db, 87 | name: this.state.cragName 88 | })} 89 | > 90 | 91 |
92 |
93 |
94 | 95 | ); 96 | } 97 | } 98 | 99 | export default CragInput; 100 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/RouteEditor/RouteEditor.css: -------------------------------------------------------------------------------- 1 | .label { 2 | font-weight: bold; 3 | min-width: 100px; 4 | } 5 | .mybuttons { 6 | margin-left: 10px; 7 | } 8 | .search-enabled { 9 | cursor: pointer; 10 | opacity: 1; 11 | } 12 | .search-disabled { 13 | cursor: default; 14 | opacity: 0.6 15 | } 16 | .blue { 17 | background-color: #0275d8; 18 | color: #ffffff; 19 | } 20 | 21 | @media (min-width: 576px) { 22 | .row-route { 23 | margin: 0px 0px 10px 0px; 24 | } 25 | } 26 | 27 | @media (min-width: 992px) { 28 | .label { 29 | line-height: 40px; 30 | } 31 | .value { 32 | line-height: 40px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/components/RouteEditor/RouteEditor.jsx: -------------------------------------------------------------------------------- 1 | /* global emit:false */ 2 | /* 3 | @global `emit` is in scope in getRoutes() when run in PouchDB context 4 | @see https://pouchdb.com/guides/queries.html 5 | */ 6 | import { h, Component } from 'preact'; 7 | import Select from 'react-select'; 8 | import classNames from 'classnames/bind'; 9 | import merge from 'lodash/merge'; 10 | import { yds } from '../../lib/ratings.js'; 11 | import styles from './RouteEditor.css'; 12 | import ListRoutes from '../ListRoutes/ListRoutes.jsx'; 13 | import CragInput from './CragInput.jsx'; 14 | import KeyValuePair from '../KeyValuePair/KeyValuePair.jsx'; 15 | 16 | class RouteEditor extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | saving: false, // is the page saving data? 21 | searching: false, // is the page searching for data? 22 | updateLocation: false, 23 | routeList: [], 24 | route: { 25 | crag: '', 26 | name: '', 27 | pitches: null, 28 | rating: null 29 | } 30 | }; 31 | this.pitches = new Array(30).fill(0).map((val, idx) => { 32 | return { 33 | 'value': idx + 1, 34 | 'label': idx + 1 35 | }; 36 | }); 37 | } 38 | componentDidMount = () => { 39 | this.getRoutes(); 40 | } 41 | handleRatingChange = val => { 42 | let route = merge({}, this.state.route, {rating: val.value}); 43 | this.setState({ 44 | route 45 | }); 46 | } 47 | handlePitchesChange = val => { 48 | let route = merge({}, this.state.route, {pitches: val.value}); 49 | this.setState({ 50 | route 51 | }); 52 | } 53 | handleRouteChange = (evt, data) => { 54 | if (evt.keyCode === 13) { // return 55 | this.search(data); 56 | } 57 | let route = merge({}, this.state.route, {name: evt.target.value}); 58 | this.setState({ 59 | doc: null, // kill the doc in state if/when the user changes the name 60 | route 61 | }); 62 | } 63 | handleCragChange = crag => { 64 | let route = merge({}, this.state.route, {crag}); 65 | this.setState({ 66 | route 67 | }); 68 | } 69 | handleCheckbox = evt => { 70 | this.setState({ 71 | updateLocation: !this.state.updateLocation 72 | }); 73 | } 74 | logState = () => { 75 | console.log(this.state); 76 | } 77 | search = ({db, name}) => { 78 | if (name.length) { 79 | this.setState({ 80 | searching: true 81 | }); 82 | // get data based on the name, if found, use the data to 83 | // prepopulate the rest of the form 84 | this.props[db].get(name) 85 | .then(doc => { 86 | let route = merge({}, { 87 | crag: (doc.crag) ? doc.crag : this.state.route.crag, 88 | name: doc.name, 89 | pitches: doc.pitches, 90 | rating: doc.rating 91 | }); 92 | this.setState({ 93 | searching: false, 94 | doc, 95 | route 96 | }); 97 | }) 98 | .catch(err => { 99 | this.setState({ 100 | searching: false 101 | }); 102 | console.error('searched for', name, 'but no route found', err); 103 | }); 104 | } 105 | } 106 | removeRoute = route => { 107 | this.props.routesDb.get(route._id).then(doc => { 108 | return this.props.routesDb.remove(doc); 109 | }).then(response => { 110 | this.getRoutes(); 111 | }).catch(err => { 112 | console.error('failed to delete a route', err); 113 | }); 114 | } 115 | create = evt => { 116 | this.setState({ 117 | saving: true 118 | }); 119 | let newRoute = merge( 120 | { _id: this.state.route.name }, 121 | this.state.route, 122 | this.props.gpsData 123 | ); 124 | this.props.routesDb.put(newRoute).then(response => { 125 | this.getRoutes(); 126 | this.setState({ 127 | saving: false, 128 | doc: null, 129 | route: { 130 | name: '' 131 | } 132 | }); 133 | }); 134 | } 135 | save = evt => { 136 | // get attr data from the clicked button via the undocumented Symbol feature 137 | // @see https://twitter.com/_developit/status/815027807818514432 138 | // let attrData = evt.target[Symbol.for('preactattr')]; 139 | this.setState({ 140 | saving: true 141 | }); 142 | 143 | let routeData = merge( 144 | {}, // new object 145 | this.state.doc, // the document from the database 146 | this.state.route, // the route data from the from in the UI 147 | ((this.state.updateLocation) ? this.props.gpsData : {}) // gps data if update location is true 148 | ); 149 | 150 | this.props.routesDb.put(routeData) 151 | .then(response => { 152 | this.getRoutes(); 153 | this.setState({ 154 | saving: false, 155 | doc: null, 156 | route: { 157 | name: '' 158 | } 159 | }); 160 | }).catch(err => { 161 | this.setState({ 162 | saving: false 163 | }); 164 | }); 165 | } 166 | getRoutes = () => { 167 | function getRoutes(doc) { 168 | emit(doc.name); 169 | } 170 | let opt = { 171 | include_docs: true 172 | }; 173 | this.props.routesDb.query(getRoutes, opt).then(res => { 174 | let routeList = res.rows.map(route => { 175 | return route.doc; 176 | }); 177 | this.setState({ 178 | routeList 179 | }); 180 | }).catch(err => { 181 | return err; 182 | }); 183 | } 184 | render() { 185 | let cn = classNames.bind(styles); 186 | let rowStyles = cn('row', { 187 | 'row-route': true 188 | }); 189 | let labelStyles = cn('col-lg-2 col-form-label', { 190 | 'label': true 191 | }); 192 | let valueStyles = cn('col-lg-6', { 193 | 'value': true 194 | }); 195 | let loggerStyles = cn('btn btn-sm mybuttons', { 196 | mybuttons: true // add css module styles 197 | }); 198 | let searchButtonStyles = cn('fa', { 199 | 'search-enabled': this.state.route.name, // there has to be a better... 200 | 'search-disabled': !this.state.route.name, // ...way to do this. 201 | 'fa-search-plus': !this.state.searching, 202 | 'fa-spinner': this.state.searching, 203 | 'fa-spin': this.state.searching 204 | }); 205 | let saveButtonStyles = cn('btn btn-sm btn-primary'); 206 | let saveButtonIcon = cn('fa', { 207 | 'fa-floppy-o': !this.state.saving, 208 | 'fa-spinner': this.state.saving, 209 | 'fa-spin': this.state.saving 210 | }); 211 | let inputGroupAddon = cn('input-group-addon', { 212 | 'blue': this.state.route.name 213 | }); 214 | 215 | return ( 216 |
217 | 218 |
219 |

{(this.state.doc) ? 'Save' : 'Create New'} Route

220 |
221 |
222 | 223 | 224 | 225 |
226 | 227 |
228 |
229 | this.handleRouteChange(evt, { 236 | db: 'routesDb', 237 | name: this.state.route.name 238 | })} 239 | /> 240 | this.search({ 243 | db: 'routesDb', 244 | name: this.state.route.name 245 | })} 246 | > 247 | 248 |
249 | 250 | {(this.state.doc) ? 251 | 252 | 259 | : '' 260 | } 261 |
262 |
263 | 264 |
265 | 266 |
267 | 287 |
288 |
289 | 290 | 294 | 295 | 299 | 300 | 304 | 305 | 306 |
307 |
308 | 309 | 317 | 318 | 325 |

326 |
327 | 328 | 334 | 335 |
336 | ); 337 | } 338 | } 339 | 340 | export default RouteEditor; 341 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pzgps 6 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/index.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import 'font-awesome/css/font-awesome.min.css'; 3 | import 'react-select/dist/react-select.css'; 4 | import './style'; 5 | import { h, render } from 'preact'; 6 | import App from './components/App/App.jsx'; 7 | 8 | let root; 9 | 10 | function init() { 11 | console.log('initializing app'); 12 | root = render( 13 | , 14 | document.querySelector("#app"), 15 | root 16 | ); 17 | } 18 | 19 | // register ServiceWorker via OfflinePlugin, for prod only: 20 | if (process.env.NODE_ENV==='production') { 21 | require('./pwa.js'); 22 | } 23 | 24 | // in development, set up HMR: 25 | if (module.hot) { 26 | //require('preact/devtools'); // turn this on if you want to enable React DevTools! 27 | module.hot.accept('./components/App/App.jsx', () => requestAnimationFrame(init) ); 28 | } 29 | 30 | init(); 31 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/lib/conf.js: -------------------------------------------------------------------------------- 1 | // change these values to match your environment 2 | export default { 3 | 'socketServer': 'ws://pzgps.local:9000', 4 | 'couchdb': 'http://pzgps.local:5984' 5 | }; 6 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/lib/db.js: -------------------------------------------------------------------------------- 1 | /* global emit */ 2 | import conf from './conf.js'; 3 | import PouchDB from 'pouchdb'; 4 | 5 | let databases = {}; 6 | let cancelSync = name => { 7 | databases[name + '-sync'].cancel(); 8 | }; 9 | 10 | // these could get a bit out of hand overtime, consider moving out to separate files 11 | let queries = { 12 | routes: { 13 | _id: '_design/routes', 14 | views: { 15 | by_name: { 16 | map: function (doc) { emit(doc.name); }.toString() 17 | } 18 | } 19 | }, 20 | boulders: { 21 | _id: '_design/boulders', 22 | views: { 23 | by_name: { 24 | map: function (doc) { emit(doc.name); }.toString() 25 | } 26 | } 27 | }, 28 | crags: { 29 | _id: '_design/crags', 30 | views: { 31 | by_name: { 32 | map: function (doc) { emit(doc.name); }.toString() 33 | } 34 | } 35 | } 36 | }; 37 | 38 | const init = () => { 39 | ['routes', 'boulders', 'crags'].forEach(name => { 40 | console.log('Initializing ' + name + ' database...'); 41 | databases[name + '-local'] = new PouchDB(name); 42 | databases[name + '-remote'] = new PouchDB(conf.couchdb + '/' + name); 43 | databases[name + '-sync'] = databases[name + '-local'].sync(databases[name + '-remote'], { 44 | live: true, 45 | retry: true 46 | }).on('complete', n => { 47 | console.log('local/remote database synced', n); 48 | }).on('error', err => { 49 | console.error('replication error', err); 50 | }); 51 | }); 52 | 53 | Object.keys(queries).forEach(q => { 54 | databases[q + '-local'].put(queries[q]).then(() => { 55 | console.log('PUT query into db', q); 56 | }).catch(err => { 57 | if (err.status !== 409) { 58 | console.error('FAILED to PUT query into db', err); 59 | } 60 | }); 61 | }); 62 | 63 | }; 64 | 65 | 66 | export default { 67 | cancelSync, 68 | databases, 69 | init 70 | }; 71 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/lib/ratings.js: -------------------------------------------------------------------------------- 1 | /** 2 | @description lets of things in ready to consume format for react-select 3 | @see https://en.wikipedia.org/wiki/Grade_(climbing) 4 | */ 5 | const yds = new Array(16).fill(0).map((val, idx) => { 6 | return { 7 | 'value': '5.' + idx, 8 | 'label': '5.' + idx 9 | }; 10 | }); 11 | 12 | const hueco = new Array(18).fill(0).map((val, idx) => { 13 | return { 14 | 'value': 'V' + idx, 15 | 'label': 'V' + idx 16 | }; 17 | }); 18 | 19 | const routeTypes = ['trad', 'sport', 'mixed', 'boulder', 'ice'].map((val, idx) => { 20 | return { 21 | 'value': val, 22 | 'label': val 23 | }; 24 | }); 25 | 26 | export { 27 | yds, 28 | hueco, 29 | routeTypes 30 | }; 31 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/lib/sckt.js: -------------------------------------------------------------------------------- 1 | import {isEqual} from 'lodash'; 2 | 3 | const connect = serverUrl => { 4 | return new Promise((resolve, reject) => { 5 | let socket = new WebSocket(serverUrl); 6 | socket.onopen = e => { 7 | console.log('Connected.', e); 8 | return resolve(socket); 9 | }; 10 | socket.onerror = e => { 11 | return reject(e); 12 | }; 13 | }); // don't need a catch clause here because of the onerror above 14 | }; 15 | 16 | // TODO: not purely functional 17 | const init = (serverUrl, Component) => { 18 | 19 | let lastLocation = {}; // location, uses to compare current to previous 20 | 21 | connect(serverUrl).then(socket => { 22 | 23 | Component.setState({ 24 | socket 25 | }); 26 | 27 | socket.send(JSON.stringify({ 28 | 'action': 'pzgps.get.consumerKey' 29 | })); 30 | 31 | socket.onclose = e => { 32 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent 33 | console.error('The WebSocket connection closed', e); 34 | console.log('Trying to reconnect...'); 35 | window.setTimeout(() => { 36 | init(serverUrl, Component); 37 | }, 5000); 38 | }; 39 | 40 | socket.onmessage = e => { 41 | let data = JSON.parse(e.data); 42 | if (data.consumerKey) { 43 | Component.setState({ 44 | consumerKey: data.consumerKey 45 | }); 46 | } 47 | if (data.lat && !isEqual(lastLocation, data)) { 48 | Component.setState({ 49 | gpsData: data 50 | }); 51 | } 52 | }; 53 | 54 | }).catch((err) => { 55 | console.error('could not connect to WebSocket server', err); 56 | console.log('Trying to reconnect...'); 57 | setTimeout(() => { 58 | init(serverUrl, Component); 59 | }, 5000); 60 | }); 61 | 62 | }; 63 | 64 | export default { 65 | connect, 66 | init 67 | }; 68 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pzgps", 3 | "short_name": "pzgps", 4 | "start_url": "./", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#fff", 8 | "theme_color": "#00c0ff", 9 | "icons": [ 10 | { 11 | "src": "assets/icons/icon-128x128.png", 12 | "sizes": "128x128", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "assets/icons/icon-144x144.png", 17 | "sizes": "144x144", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "assets/icons/icon-152x152.png", 22 | "sizes": "152x152", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "assets/icons/icon-192x192.png", 27 | "sizes": "192x192", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "assets/icons/icon-256x256.png", 32 | "sizes": "256x256", 33 | "type": "image/png" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/pwa.js: -------------------------------------------------------------------------------- 1 | import runtime from 'offline-plugin/runtime'; 2 | 3 | runtime.install({ 4 | // When an update is ready, tell ServiceWorker to take control immediately: 5 | onUpdateReady() { 6 | console.log('update ready'); 7 | runtime.applyUpdate(); 8 | }, 9 | 10 | // Reload to get the new version: 11 | onUpdated() { 12 | console.log('updated'); 13 | location.reload(); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/style/helpers.less: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'mixins'; 3 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/style/index.less: -------------------------------------------------------------------------------- 1 | @import 'helpers'; 2 | 3 | html, body { 4 | width: 100%; 5 | padding: 0; 6 | margin: 40px 0px 10px 0px; 7 | background: #FAFAFA; 8 | font-family: 'Helvetica Neue', arial, sans-serif; 9 | font-weight: 400; 10 | color: #444; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | } 14 | 15 | * { 16 | box-sizing: border-box; 17 | } 18 | 19 | #app { 20 | height: 100%; 21 | } 22 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/style/mixins.less: -------------------------------------------------------------------------------- 1 | .fill() { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .scroll() { 10 | overflow: auto; 11 | overflow-scrolling: touch; 12 | 13 | & > .inner { 14 | position: relative; 15 | transform: translateZ(0); 16 | overflow: hidden; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/pzgps-preact/src/style/variables.less: -------------------------------------------------------------------------------- 1 | @red: #F00; 2 | @blue: #00F; 3 | @white: #FFF; 4 | @gray: #999; 5 | @black: #000; 6 | -------------------------------------------------------------------------------- /packages/pzgps-preact/test/browser/index.js: -------------------------------------------------------------------------------- 1 | import { h, render, rerender } from 'preact'; 2 | import { route } from 'preact-router'; 3 | import App from 'components/app'; 4 | import 'style'; 5 | 6 | /*global sinon,expect*/ 7 | 8 | describe('App', () => { 9 | let scratch; 10 | 11 | before( () => { 12 | scratch = document.createElement('div'); 13 | (document.body || document.documentElement).appendChild(scratch); 14 | }); 15 | 16 | beforeEach( () => { 17 | scratch.innerHTML = ''; 18 | }); 19 | 20 | after( () => { 21 | scratch.parentNode.removeChild(scratch); 22 | scratch = null; 23 | }); 24 | 25 | 26 | describe('routing', () => { 27 | it('should render the homepage', () => { 28 | render(, scratch); 29 | 30 | expect(scratch.innerHTML).to.contain('Home'); 31 | }); 32 | 33 | it('should render /profile', () => { 34 | render(, scratch); 35 | route('/profile'); 36 | rerender(); 37 | 38 | expect(scratch.innerHTML).to.contain('Profile: me'); 39 | }); 40 | 41 | it('should render /profile/:user', () => { 42 | render(, scratch); 43 | route('/profile/john'); 44 | rerender(); 45 | 46 | expect(scratch.innerHTML).to.contain('Profile: john'); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/pzgps-preact/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | var webpack = require('../webpack.config.babel.js'); 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '../', 7 | frameworks: ['mocha', 'chai-sinon'], 8 | reporters: ['mocha'], 9 | 10 | browsers: ['PhantomJS'], 11 | 12 | files: [ 13 | 'test/browser/**/*.js' 14 | ], 15 | 16 | preprocessors: { 17 | 'test/**/*.js': ['webpack'], 18 | 'src/**/*.js': ['webpack'], 19 | '**/*.js': ['sourcemap'] 20 | }, 21 | 22 | webpack: webpack, 23 | webpackMiddleware: { noInfo: true } 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/pzgps-preact/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import autoprefixer from 'autoprefixer'; 5 | import path from 'path'; 6 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 7 | import OfflinePlugin from 'offline-plugin'; 8 | 9 | const ENV = process.env.NODE_ENV || 'development'; 10 | 11 | const CSS_MAPS = ENV!=='production'; 12 | 13 | module.exports = { 14 | context: path.resolve(__dirname, "src"), 15 | entry: './index.js', 16 | 17 | output: { 18 | path: path.resolve(__dirname, "build"), 19 | publicPath: '/', 20 | filename: 'bundle.js' 21 | }, 22 | 23 | resolve: { 24 | extensions: ['', '.jsx', '.js', '.json', '.less'], 25 | modulesDirectories: [ 26 | path.resolve(__dirname, "src/lib"), 27 | path.resolve(__dirname, "node_modules"), 28 | 'node_modules' 29 | ], 30 | alias: { 31 | components: path.resolve(__dirname, "src/components"), // used for tests 32 | style: path.resolve(__dirname, "src/style"), 33 | 'react': 'preact-compat', 34 | 'react-dom': 'preact-compat' 35 | } 36 | }, 37 | 38 | module: { 39 | preLoaders: [ 40 | { 41 | test: /\.jsx?$/, 42 | exclude: /src\//, 43 | loader: 'source-map' 44 | } 45 | ], 46 | loaders: [ 47 | { 48 | test: /\.jsx?$/, 49 | exclude: /node_modules/, 50 | loader: 'babel' 51 | }, 52 | { 53 | test: /\.(less|css)$/, 54 | include: /src\/components\//, 55 | loader: ExtractTextPlugin.extract('style?singleton', [ 56 | `css?sourceMap=${CSS_MAPS}&modules&importLoaders=1&localIdentName=[local]${process.env.CSS_MODULES_IDENT || '_[hash:base64:5]'}`, 57 | 'postcss', 58 | `less?sourceMap=${CSS_MAPS}` 59 | ].join('!')) 60 | }, 61 | { 62 | test: /\.(less|css)$/, 63 | exclude: /src\/components\//, 64 | loader: ExtractTextPlugin.extract('style?singleton', [ 65 | `css?sourceMap=${CSS_MAPS}`, 66 | `postcss`, 67 | `less?sourceMap=${CSS_MAPS}` 68 | ].join('!')) 69 | }, 70 | { 71 | test: /\.json$/, 72 | loader: 'json' 73 | }, 74 | { 75 | test: /\.(xml|html|txt|md)$/, 76 | loader: 'raw' 77 | }, 78 | { 79 | test: /\.(svg|woff2?|ttf|eot|jpe?g|png|gif)(\?.*)?$/i, 80 | loader: ENV==='production' ? 'file?name=[path][name]_[hash:base64:5].[ext]' : 'url' 81 | } 82 | ] 83 | }, 84 | 85 | postcss: () => [ 86 | autoprefixer({ browsers: 'last 2 versions' }) 87 | ], 88 | 89 | plugins: ([ 90 | new webpack.NoErrorsPlugin(), 91 | new ExtractTextPlugin('style.css', { 92 | allChunks: true, 93 | disable: ENV!=='production' 94 | }), 95 | new webpack.optimize.DedupePlugin(), 96 | new webpack.DefinePlugin({ 97 | 'process.env': JSON.stringify({ NODE_ENV: ENV }) 98 | }), 99 | new HtmlWebpackPlugin({ 100 | template: './index.html', 101 | minify: { collapseWhitespace: true } 102 | }), 103 | new CopyWebpackPlugin([ 104 | { from: './manifest.json', to: './' } 105 | ]), 106 | new OfflinePlugin({ 107 | relativePaths: false, 108 | AppCache: false, 109 | publicPath: '/' 110 | }) 111 | ]).concat(ENV==='production' ? [ 112 | new webpack.optimize.OccurenceOrderPlugin() 113 | ] : []), 114 | 115 | stats: { colors: true }, 116 | 117 | node: { 118 | global: true, 119 | process: false, 120 | Buffer: false, 121 | __filename: false, 122 | __dirname: false, 123 | setImmediate: false 124 | }, 125 | 126 | devtool: ENV==='production' ? 'source-map' : 'cheap-module-eval-source-map', 127 | 128 | devServer: { 129 | port: process.env.PORT || 9001, 130 | host: 'localhost', 131 | colors: true, 132 | publicPath: '/', 133 | contentBase: './src', 134 | historyApiFallback: true, 135 | open: true, 136 | proxy: { 137 | // OPTIONAL: proxy configuration: 138 | // '/optional-prefix/**': { // path pattern to rewrite 139 | // target: 'http://target-host.com', 140 | // pathRewrite: path => path.replace(/^\/[^\/]+\//, '') // strip first path segment 141 | // } 142 | } 143 | } 144 | }; 145 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - plugin:react/recommended 4 | 5 | env: 6 | browser: true 7 | node: true 8 | es6: true 9 | 10 | parser: babel-eslint 11 | 12 | parserOptions: 13 | ecmaVersion: 6 14 | sourceType: "module" 15 | ecmaFeatures: 16 | jsx: true 17 | 18 | globals: 19 | __DEV__: true 20 | __SERVER__: true 21 | 22 | plugins: 23 | - react 24 | 25 | rules: 26 | react/jsx-uses-vars: 1 27 | react/prop-types: [1, { ignore: [children] }] 28 | 29 | semi: 0 30 | key-spacing: 1 31 | curly: 0 32 | consistent-return: 0 33 | space-infix-ops: 1 34 | camelcase: 0 35 | no-spaced-func: 1 36 | no-alert: 1 37 | eol-last: 1 38 | comma-spacing: 1 39 | eqeqeq: 1 40 | 41 | # possible errors 42 | comma-dangle: 0 43 | no-cond-assign: 2 44 | no-console: 0 45 | no-constant-condition: 2 46 | no-control-regex: 2 47 | no-debugger: 2 48 | no-dupe-args: 2 49 | no-dupe-keys: 2 50 | no-duplicate-case: 2 51 | no-empty-character-class: 2 52 | no-empty: 2 53 | no-ex-assign: 2 54 | no-extra-boolean-cast: 2 55 | no-extra-parens: 0 56 | no-extra-semi: 2 57 | no-func-assign: 2 58 | no-inner-declarations: 2 59 | no-invalid-regexp: 2 60 | no-irregular-whitespace: 2 61 | no-negated-in-lhs: 2 62 | no-obj-calls: 2 63 | no-regex-spaces: 2 64 | no-sparse-arrays: 2 65 | no-unexpected-multiline: 2 66 | no-unreachable: 2 67 | use-isnan: 2 68 | valid-jsdoc: 2 69 | valid-typeof: 2 70 | 71 | no-redeclare: 2 72 | 73 | init-declarations: 2 74 | no-catch-shadow: 2 75 | no-delete-var: 2 76 | no-label-var: 2 77 | no-shadow-restricted-names: 2 78 | no-shadow: 2 79 | no-undef-init: 2 80 | no-undef: 2 81 | no-undefined: 2 82 | no-unused-vars: 2 83 | no-use-before-define: 2 84 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Ignore build files 30 | public 31 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/README.md: -------------------------------------------------------------------------------- 1 | # react-webpack-babel 2 | Simple React Webpack Babel Starter Kit 3 | 4 | Tired of complicated starters with 200MB of dependencies which are hard to understand and modify? 5 | 6 | Try this is a simple [React](https://facebook.github.io/react/), [Webpack](http://webpack.github.io/) and [Babel](https://babeljs.io/) application with nothing else in it. 7 | 8 | ### What's in it? 9 | 10 | * Simple src/index.jsx and src/index.css (local module css). 11 | * Webpack configuration for development (with hot reloading) and production (with minification). 12 | * CSS module loading, so you can include your css by ```import styles from './path/to.css';```. 13 | * Both js(x) and css hot loaded during development. 14 | 15 | ### To run 16 | 17 | * You'll need to have [git](https://git-scm.com/) and [node](https://nodejs.org/en/) installed in your system. 18 | * Fork and clone the project: 19 | 20 | ``` 21 | > $ git clone THIS_REPO_URL 22 | ``` 23 | 24 | * Then install the dependencies: 25 | 26 | ``` 27 | > $ npm install 28 | ``` 29 | 30 | * Run development server: 31 | 32 | ``` 33 | > $ npm start 34 | ``` 35 | 36 | Open the web browser to `http://localhost:8888/` 37 | 38 | ### To build production package 39 | 40 | ``` 41 | > $ npm run build 42 | ``` 43 | 44 | ### Nginx Config 45 | 46 | Here is the suggested Nginx config: 47 | ``` 48 | server { 49 | # ... root and other options 50 | 51 | gzip on; 52 | gzip_http_version 1.1; 53 | gzip_types text/plain text/css text/xml application/javascript image/svg+xml; 54 | 55 | location ~ \.html?$ { 56 | expires 1d; 57 | } 58 | 59 | location ~ \.(svg|ttf|js|css|svgz|eot|otf|woff|jpg|jpeg|gif|png|ico)$ { 60 | access_log off; 61 | log_not_found off; 62 | expires max; 63 | } 64 | } 65 | ``` 66 | 67 | ### Eslint 68 | There is a .eslint.yaml config for eslint ready with React plugin. 69 | To use it, you need to install additional dependencies though: 70 | 71 | ``` 72 | > npm install --save-dev eslint eslint-plugin-react 73 | ``` 74 | 75 | To do the actual linting, run: 76 | 77 | ``` 78 | > npm run lint 79 | ``` 80 | 81 | ### Notes on importing css styles 82 | * styles having /src/ in their absolute path are considered part of the application and exported as local css modules. 83 | * styles having /node_modules|global/ in their absolute path are considered global styles used by many components and are included in the css bundle directly. 84 | 85 | ### Contribute 86 | Please contribute to the project if you think this can be done better in anyway even for this README :) 87 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/lib/sckt.js: -------------------------------------------------------------------------------- 1 | const connect = serverUrl => { 2 | return new Promise((resolve, reject) => { 3 | let socket = new WebSocket(serverUrl); 4 | socket.onopen = e => { 5 | console.log('Connected.', e); 6 | return resolve(socket); 7 | }; 8 | socket.onerror = e => { 9 | return reject(e); 10 | }; 11 | }); // don't need a catch clause here because of the onerror above 12 | }; 13 | 14 | // TODO: not purely functional 15 | const setUpSocket = (serverUrl, Component) => { 16 | 17 | connect(serverUrl).then(socket => { 18 | 19 | Component.setState({ 20 | socket 21 | }); 22 | 23 | socket.send(JSON.stringify({ 24 | 'action': 'pzgps.get.consumerKey' 25 | })); 26 | 27 | socket.onclose = e => { 28 | // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent 29 | console.error('The WebSocket connection closed', e); 30 | console.log('Trying to reconnect...'); 31 | window.setTimeout(() => { 32 | setUpSocket(Component); 33 | }, 5000); 34 | }; 35 | 36 | socket.onmessage = e => { 37 | let data = JSON.parse(e.data); 38 | if (data.consumerKey) { 39 | Component.setState({ 40 | consumerKey: data.consumerKey 41 | }); 42 | } else { 43 | Component.setState({ 44 | gpsData: data 45 | }); 46 | } 47 | }; 48 | 49 | }).catch((err) => { 50 | console.error('could not connect to WebSocket server', err); 51 | console.log('Trying to reconnect...'); 52 | setTimeout(() => { 53 | setUpSocket(Component); 54 | }, 5000); 55 | }); 56 | 57 | }; 58 | 59 | module.exports = { 60 | connect, 61 | setUpSocket 62 | }; 63 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pzgps-reactjs", 3 | "version": "1.0.1", 4 | "description": "pzgps fronted by ReactJS", 5 | "main": "''", 6 | "scripts": { 7 | "build": "webpack --config webpack.production.config.js --progress --profile --colors", 8 | "start": "webpack-dev-server --progress --profile --colors", 9 | "lint": "eslint --ext .js lib --ext js --ext jsx src || exit 0" 10 | }, 11 | "contributors": [ 12 | { 13 | "name": "Ali Al Dallal" 14 | }, 15 | { 16 | "name": "Daniel Kapusta", 17 | "email": "kapusta@gmail.com", 18 | "url": "https://github.com/kapusta/" 19 | } 20 | ], 21 | "license": "Apache-2.0", 22 | "dependencies": { 23 | "bootstrap": "^4.0.0-alpha.3", 24 | "node-sass": "^4.14.1", 25 | "react": "15.3.2", 26 | "react-dom": "15.3.2", 27 | "react-router": "^3.0.0", 28 | "sass-loader": "^4.0.2", 29 | "shortid": "^2.2.6" 30 | }, 31 | "devDependencies": { 32 | "babel-core": "^6.18.2", 33 | "babel-eslint": "^7.1.1", 34 | "babel-loader": "^6.2.7", 35 | "babel-plugin-transform-class-properties": "^6.16.0", 36 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 37 | "babel-plugin-transform-runtime": "^6.15.0", 38 | "babel-preset-es2015": "^6.18.0", 39 | "babel-preset-react": "6.16.0", 40 | "babel-preset-stage-1": "^6.16.0", 41 | "babel-runtime": "^6.18.0", 42 | "css-loader": "0.25.0", 43 | "eslint-plugin-react": "^6.6.0", 44 | "extract-text-webpack-plugin": "^1.0.1", 45 | "file-loader": "0.9.0", 46 | "html-webpack-plugin": "^2.22.0", 47 | "postcss-loader": "^1.0.0", 48 | "react-hot-loader": "^3.0.0-beta.6", 49 | "style-loader": "0.13.1", 50 | "url-loader": "0.5.7", 51 | "webpack": "1.13.2", 52 | "webpack-cleanup-plugin": "^0.4.1", 53 | "webpack-dev-server": "1.16.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/postcss.config.js: -------------------------------------------------------------------------------- 1 | const AUTOPREFIXER_BROWSERS = [ 2 | 'Android 2.3', 3 | 'Android >= 4', 4 | 'Chrome >= 35', 5 | 'Firefox >= 31', 6 | 'Explorer >= 9', 7 | 'iOS >= 7', 8 | 'Opera >= 12', 9 | 'Safari >= 7.1', 10 | ]; 11 | 12 | module.exports = { 13 | plugins: [ 14 | require('autoprefixer')({ browsers: AUTOPREFIXER_BROWSERS }) 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/About/about.jsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | //import styles from '../index.scss'; 3 | import React from 'react'; 4 | 5 | class About extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | render() { 10 | return ( 11 |
12 |
13 |

About

14 |

15 | This is a ReactJS implementation of the GPS front end based on the react-webpack-babel project. If the server on the pizero is set up correctly, a GPS Data and MapQuest button will appear above. 16 |

17 |
18 |
19 | ) 20 | } 21 | } 22 | 23 | export default About; 24 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/App/app.jsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | import sckt from '../../lib/sckt.js'; 4 | import NavBar from '../NavBar/NavBar.jsx'; 5 | import ContentBox from '../ContentBox/ContentBox.jsx'; 6 | 7 | const serverUrl = 'ws://circ.local:9000'; // you'll want to change this 8 | 9 | class App extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | componentName: 'About', 14 | gpsData: {}, 15 | consumerKey: '', 16 | socket: {} 17 | }; 18 | } 19 | 20 | componentDidMount() { 21 | sckt.setUpSocket(serverUrl, this); 22 | } 23 | 24 | componentWillUnmount() { 25 | this.state.socket.close(); 26 | } 27 | 28 | // method is passed down to app -> NavBar -> NavButton 29 | handleClick = name => { 30 | this.setState({ 31 | componentName: name 32 | }); 33 | } 34 | 35 | render() { 36 | return ( 37 |
38 | 44 | 45 | 50 |
51 | ) 52 | } 53 | } 54 | 55 | App.propTypes = { 56 | componentName: React.PropTypes.string 57 | }; 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/ContentBox/ContentBox.jsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | import About from '../About/About.jsx'; 4 | import GpsData from '../GpsData/GpsData.jsx'; 5 | import MapQuest from '../MapQuest/MapQuest.jsx'; 6 | 7 | // @description this is kind of gross, but neccessary 8 | // @see https://github.com/facebook/react/issues/3365 9 | const contentComponents = { 10 | About: About, 11 | GpsData: GpsData, 12 | MapQuest: MapQuest 13 | } 14 | 15 | class ContentBox extends React.Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | render() { 22 | // must be capitalized, contains a reference to a Component 23 | let Content = contentComponents[this.props.content]; 24 | if (this.props.content === 'MapQuest') { 25 | return ( 26 |
27 | {this.props.children} 28 |
29 | ) 30 | } else { 31 | return ( 32 |
33 | {this.props.children} 34 |
35 | ) 36 | } 37 | } 38 | } 39 | 40 | ContentBox.propTypes = { 41 | content: React.PropTypes.string, 42 | gpsData: React.PropTypes.object 43 | }; 44 | 45 | export default ContentBox; 46 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/GpsData/GpsData.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import shortid from 'shortid'; 3 | import 'bootstrap/dist/css/bootstrap.min.css'; 4 | import styles from './gpsdata.scss'; 5 | 6 | class GpsData extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | render() { 11 | return ( 12 |
13 |
14 |

GPS Data

15 | 16 | Latitude {this.props.gpsData.lat}
17 | Longitude {this.props.gpsData.lon}
18 | Altitude {this.props.gpsData.alt} meters
19 | 20 |

Raw Data

21 |
22 | {Object.keys(this.props.gpsData).map(function(val) { 23 | return ( 24 |
  • {val} - {this.props.gpsData[val]}
  • 25 | ) 26 | }.bind(this))} 27 |
    28 |
    29 |
    30 | ) 31 | } 32 | } 33 | 34 | GpsData.propTypes = { 35 | gpsData: React.PropTypes.object 36 | }; 37 | 38 | export default GpsData; 39 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/GpsData/gpsdata.scss: -------------------------------------------------------------------------------- 1 | .keyname { 2 | display: inline-block; 3 | width: 80px; 4 | font-weight: bold; 5 | } 6 | .header { 7 | margin-top: 10px; 8 | } 9 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/MapQuest/MapQuest.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | //import styles from './mapquest.scss'; 4 | 5 | class MapQuest extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | mapUrl: '' 10 | }; 11 | } 12 | componentDidMount() { 13 | this.setState({ 14 | mapUrl: 'https://www.mapquestapi.com/staticmap/v4/getmap?key=' 15 | + this.props.consumerKey 16 | + '¢er=' + this.props.gpsData.lat + ',' + this.props.gpsData.lon 17 | + '&zoom=10&size=640,480&type=map&imagetype=png&pois=blue,' 18 | + this.props.gpsData.lat + ',' + this.props.gpsData.lon 19 | }); 20 | } 21 | render() { 22 | return ( 23 |
    24 |
    25 |

    MapQuest

    26 | 27 |
    28 |
    29 | ) 30 | } 31 | } 32 | 33 | MapQuest.propTypes = { 34 | gpsData: React.PropTypes.object, 35 | consumerKey: React.PropTypes.string 36 | }; 37 | 38 | export default MapQuest; 39 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/MapQuest/mapquest.scss: -------------------------------------------------------------------------------- 1 | .staticmap { 2 | padding: 5px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/NavBar/navbar.jsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | //import { render } from 'react-dom'; 4 | //import styles from '../index.scss'; 5 | import navbarStyles from './Navbar.scss'; 6 | import NavButton from '../NavButton/NavButton.jsx'; 7 | 8 | class Navbar extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | render() { 13 | return ( 14 |
    15 |
    16 | 17 | About 22 | 23 | 24 | {(this.props.gpsData) ? GPS Data : '' 29 | } 30 | 31 | {(this.props.gpsData && this.props.consumerKey) ? MapQuest : '' 36 | } 37 | 38 |
    39 |
    40 | ) 41 | } 42 | } 43 | 44 | Navbar.propTypes = { 45 | consumerKey: React.PropTypes.bool, 46 | componentName: React.PropTypes.string, 47 | gpsData: React.PropTypes.bool, 48 | handleClick: React.PropTypes.func 49 | }; 50 | 51 | export default Navbar; 52 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/NavBar/navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | padding: 10px 10px 0px 0px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/NavButton/NavButton.jsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | import buttonStyles from './navbutton.scss'; 4 | 5 | class Navbutton extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.handleClick = this.handleClick.bind(this); 9 | } 10 | handleClick = e => { 11 | console.log('button click event', e); 12 | this.props.handleClick(this.props.section); 13 | } 14 | render() { 15 | return ( 16 | 20 | ) 21 | } 22 | } 23 | 24 | Navbutton.propTypes = { 25 | isActive: React.PropTypes.bool, 26 | handleClick: React.PropTypes.func, 27 | section: React.PropTypes.string 28 | }; 29 | 30 | export default Navbutton; 31 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/NavButton/navbutton.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | margin: 10px 10px 10px 0px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pzgps-reactjs 6 | 7 | 8 | 9 |
    10 |
    Loading...
    11 |
    12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App/App.jsx'; 4 | import { Router, Route, browserHistory, IndexRoute } from 'react-router'; 5 | 6 | render( 7 | 8 | 9 | , 10 | document.querySelector("#app") 11 | ); 12 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/webpack.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var webpack = require('webpack'); 3 | var path = require('path'); 4 | var loaders = require('./webpack.loaders'); 5 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | 7 | const HOST = process.env.HOST || "127.0.0.1"; 8 | const PORT = process.env.PORT || "9001"; 9 | 10 | // global css 11 | loaders.push({ 12 | test: /[\/\\](node_modules|global)[\/\\].*\.css$/, 13 | loaders: [ 14 | 'style?sourceMap', 15 | 'css' 16 | ] 17 | }); 18 | // local scss modules 19 | loaders.push({ 20 | test: /[\/\\]src[\/\\].*\.scss/, 21 | exclude: /(node_modules|bower_components|public)/, 22 | loaders: [ 23 | 'style?sourceMap', 24 | 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]', 25 | 'postcss', 26 | 'sass' 27 | ] 28 | }); 29 | 30 | // local css modules 31 | loaders.push({ 32 | test: /[\/\\]src[\/\\].*\.css/, 33 | exclude: /(node_modules|bower_components|public)/, 34 | loaders: [ 35 | 'style?sourceMap', 36 | 'css?modules&importLoaders=1&localIdentName=[path]___[name]__[local]___[hash:base64:5]' 37 | ] 38 | }); 39 | 40 | module.exports = { 41 | entry: [ 42 | `webpack-dev-server/client?http://${HOST}:${PORT}`, 43 | `webpack/hot/only-dev-server`, 44 | `./src/index.jsx` // Your appʼs entry point 45 | ], 46 | devtool: process.env.WEBPACK_DEVTOOL || 'cheap-module-source-map', 47 | output: { 48 | path: path.join(__dirname, 'public'), 49 | filename: 'bundle.js' 50 | }, 51 | resolve: { 52 | extensions: ['', '.js', '.jsx'] 53 | }, 54 | module: { 55 | loaders 56 | }, 57 | devServer: { 58 | contentBase: "./public", 59 | // do not print bundle build stats 60 | noInfo: true, 61 | // enable HMR 62 | hot: true, 63 | // embed the webpack-dev-server runtime into the bundle 64 | inline: true, 65 | // serve index.html in place of 404 responses to allow HTML5 history 66 | historyApiFallback: true, 67 | port: PORT, 68 | host: HOST 69 | }, 70 | plugins: [ 71 | new webpack.NoErrorsPlugin(), 72 | new webpack.HotModuleReplacementPlugin(), 73 | new HtmlWebpackPlugin({ 74 | template: './src/index.html' 75 | }), 76 | ] 77 | }; 78 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/webpack.loaders.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | test: /\.jsx?$/, 4 | exclude: /(node_modules|bower_components|public)/, 5 | loaders: ['react-hot-loader/webpack'] 6 | }, 7 | { 8 | test: /\.jsx?$/, 9 | exclude: /(node_modules|bower_components|public)/, 10 | loader: 'babel', 11 | query: { 12 | presets: ['es2015', 'react', 'stage-1'], 13 | plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-class-properties'], 14 | } 15 | }, 16 | { 17 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 18 | exclude: /(node_modules|bower_components)/, 19 | loader: "file" 20 | }, 21 | { 22 | test: /\.(woff|woff2)$/, 23 | exclude: /(node_modules|bower_components)/, 24 | loader: "url?prefix=font/&limit=5000" 25 | }, 26 | { 27 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 28 | exclude: /(node_modules|bower_components)/, 29 | loader: "url?limit=10000&mimetype=application/octet-stream" 30 | }, 31 | { 32 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 33 | exclude: /(node_modules|bower_components)/, 34 | loader: "url?limit=10000&mimetype=image/svg+xml" 35 | }, 36 | { 37 | test: /\.gif/, 38 | exclude: /(node_modules|bower_components)/, 39 | loader: "url-loader?limit=10000&mimetype=image/gif" 40 | }, 41 | { 42 | test: /\.jpg/, 43 | exclude: /(node_modules|bower_components)/, 44 | loader: "url-loader?limit=10000&mimetype=image/jpg" 45 | }, 46 | { 47 | test: /\.png/, 48 | exclude: /(node_modules|bower_components)/, 49 | loader: "url-loader?limit=10000&mimetype=image/png" 50 | } 51 | ]; 52 | -------------------------------------------------------------------------------- /packages/pzgps-reactjs/webpack.production.config.js: -------------------------------------------------------------------------------- 1 | 2 | var webpack = require('webpack'); 3 | var path = require('path'); 4 | var loaders = require('./webpack.loaders'); 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | var WebpackCleanupPlugin = require('webpack-cleanup-plugin'); 8 | 9 | // local css modules 10 | loaders.push({ 11 | test: /[\/\\]src[\/\\].*\.css/, 12 | exclude: /(node_modules|bower_components|public)/, 13 | loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]') 14 | }); 15 | 16 | // local scss modules 17 | loaders.push({ 18 | test: /[\/\\]src[\/\\].*\.scss/, 19 | exclude: /(node_modules|bower_components|public)/, 20 | loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss!sass') 21 | }); 22 | // global css files 23 | loaders.push({ 24 | test: /[\/\\](node_modules|global)[\/\\].*\.css$/, 25 | loader: ExtractTextPlugin.extract('style', 'css') 26 | }); 27 | 28 | module.exports = { 29 | entry: [ 30 | './src/index.jsx' 31 | ], 32 | output: { 33 | path: path.join(__dirname, 'public'), 34 | filename: '[chunkhash].js' 35 | }, 36 | resolve: { 37 | extensions: ['', '.js', '.jsx'] 38 | }, 39 | module: { 40 | loaders 41 | }, 42 | plugins: [ 43 | new WebpackCleanupPlugin(), 44 | new webpack.DefinePlugin({ 45 | 'process.env': { 46 | NODE_ENV: '"production"' 47 | } 48 | }), 49 | new webpack.optimize.UglifyJsPlugin({ 50 | compress: { 51 | warnings: false, 52 | screw_ie8: true, 53 | drop_console: true, 54 | drop_debugger: true 55 | } 56 | }), 57 | new webpack.optimize.OccurenceOrderPlugin(), 58 | new ExtractTextPlugin('[contenthash].css', { 59 | allChunks: true 60 | }), 61 | new HtmlWebpackPlugin({ 62 | template: './src/index.html', 63 | title: 'pzgps-reactjs' 64 | }), 65 | new webpack.optimize.DedupePlugin() 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /packages/pzgps-server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /lib/mqkey.js 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/pzgps-server/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /packages/pzgps-server/README.md: -------------------------------------------------------------------------------- 1 | # pzgps-server 2 | The goal of this project is to collect data from the a GPS unit and stream that data out to a web front end via a WebSocket. 3 | 4 | We'll use [NodeJS](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) and [node-gpsd](https://github.com/eelcocramer/node-gpsd) to read and process the data, make it available via [ws](https://www.npmjs.com/package/ws), and render in the UI with the help of [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API). 5 | 6 | That data, along with user defined information, can then be saved to a [PouchDB database](https://pouchdb.com/guides/databases.html) on your browser/device that syncs to a CouchDB database on your [#pizero](https://www.raspberrypi.org/products/pi-zero/). 7 | 8 | ## Assumptions 9 | You... 10 | * Have a [#pizero](https://www.raspberrypi.org/products/pi-zero/) with an [ARM11](https://en.wikipedia.org/wiki/ARM11) and a GPIO header soldered onto it. 11 | * Have an [Adafruit Ultimate GPS Breakout](https://www.adafruit.com/product/746) 12 | * Have [Raspbian Jessie](https://www.raspberrypi.org/downloads/raspbian/) installed. 13 | * Have [Connected your Adafruit GPS Breakout](https://learn.adafruit.com/adafruit-ultimate-gps-on-the-raspberry-pi/using-uart-instead-of-usb) 14 | 15 | ## Package Structure 16 | * At the root of this package is an `index.js` file, which is a NodeJS application that reads the GPS data and provides it over a WebSocket (on port 9001). 17 | * Sample web apps are in the `/packages/` directory, each with it's own `package.json` file. 18 | - Currently the [Preact](https://preactjs.com/) version of the front end has the most code/features/effort. 19 | - You'll run each part of the project by running `npm start` where the `package.json` is located. 20 | - All front end web apps run on port 9001. 21 | * If you want to persist data to a database, then install CouchDB on your pizero (see below) which will run on port 5984 (the Futon UI would then be at http://yourhostname:5984/_utils/index.html). 22 | 23 | ## Installing NodeJS on the pizero 24 | The version of NodeJS you get via `apt-get install nodejs` is out of date (so you'd be missing some important security patches). 25 | 26 | If you want to compile node from scratch on your [#pizero](https://www.raspberrypi.org/products/pi-zero/) and wait all night for it to complete, then [check out this guide](https://www.youtube.com/watch?v=J6g53Hm0rq4). 27 | 28 | If you want to make things a bit easier, then [download Node](https://nodejs.org/en/download/) using `wget` and install it directly. In this case we'll download the latest version (as of this writing) for ARM on the 6.x branch. Log in to your pi and remain in your home directory... 29 | 30 | wget https://nodejs.org/dist/v6.10.0/node-v6.10.0-linux-armv6l.tar.xz 31 | cd /usr/local 32 | sudo tar --strip-components 1 -xvf ~/node-v6.10.0-linux-armv6l.tar.xz 33 | cd && rm node-v6.10.0-linux-armv6l.tar.xz 34 | 35 | Node is installed now, along with npm. 36 | * `node -v` should yield `v6.10.0` 37 | * `npm -v` should yield `3.10.10` 38 | 39 | Your `/usr/local` dir has a few files left over from the install (ie, CHANGELOG.md, LICENSE, README.md). You can safely remove those. 40 | 41 | Now consider installing [n](https://github.com/tj/n) or [nvm](https://github.com/creationix/nvm) so you can easily install new versions of Node and NPM (and switch between them at will). 42 | 43 | ## Installing CouchDB on the pizero. 44 | [The official PouchDB set up guide is excellent](https://pouchdb.com/guides/setup-couchdb.html). The Preact based front end uses PouchDB to store data locally and to persist data to the CouchDB (see below) instance. 45 | 46 | To install CouchDB on the pizero, log into it, then... 47 | 48 | sudo apt-get install couchdb 49 | 50 | 51 | ## Installing `gpsd` 52 | Log into your pi... 53 | 54 | sudo apt-get install gpsd gpsd-clients python-gps 55 | 56 | Start `gpsd` in verbose/debug mode... 57 | 58 | sudo gpsd /dev/ttyAMA0 -D 2 -n -b -N -P /tmp/gpsd.pid -F /var/run/gpsd.sock 59 | 60 | When starting `gpsd` you might see an error like this... 61 | 62 | gpsd:ERROR: can't bind to local socket /var/run/gpsd.sock 63 | 64 | 65 | You can confirm data is coming to your [#pizero](https://www.raspberrypi.org/products/pi-zero/) with `cat /dev/ttyAMA0` which should show a stream of data. If there is no data from that device, try to `cat /dev/ttyS0` and see if data is streaming from that device, if so, use that device instead wherever you provide the name of a device in a config. 66 | 67 | If you see data coming thru but the command to start `gpsd` failed with an error about not being able to connect, then you might have to disable terminal over serial. 68 | 69 | ### How disable terminal over serial 70 | 71 | * `sudo raspi-config` 72 | * go to `Advanced Options` 73 | * go to `Interfacing Options` 74 | * go to `P6 Serial` and and choose `no` 75 | * `sudo reboot` 76 | 77 | The Adafruit guide mentioned above says you can do this from `/etc/inittab` but that file doesn't exist in Raspbian Jessie (it did in Wheezy). Raspbian Jessie has moved everything to services and there is no `/etc/inittab` file at all, so it's best to use the `raspi-config` command. 78 | 79 | ### Configuring gpsd 80 | To have `gpsd` start up correctly, edit `/etc/default/gpsd` 81 | 82 | # Default settings for the gpsd init script and the hotplug wrapper. 83 | 84 | # Start the gpsd daemon automatically at boot time 85 | START_DAEMON="true" 86 | 87 | # Use USB hotplugging to add new USB devices automatically to the daemon 88 | USBAUTO="false" 89 | 90 | # Devices gpsd should collect to at boot time. 91 | # They need to be read/writeable, either by user gpsd or the group dialout. 92 | DEVICES="/dev/ttyAMA0" 93 | 94 | # Other options you want to pass to gpsd 95 | GPSD_OPTIONS 96 | 97 | GPSD_SOCKET="/var/run/gpsd.sock" 98 | 99 | Then restart: `sudo /etc/init.d/gpsd restart` 100 | 101 | Then try `cgps -s` and you should now see real data. If the GPS Breakout can't see the sky then you might see `no fix` which means it can't see any satellites. Either go outside or put the [#pizero](https://www.raspberrypi.org/products/pi-zero/) on a window sill. 😀 102 | 103 | 104 | ### More useful Commands for dealing with GPSD on the pizero 105 | * `sudo killall gpsd` - To kill gpsd 106 | * `sudo /etc/init.d/gpsd restart` - To elegantly restart gpsd 107 | * `cgps -s` - to open a terminal UI for gps data 108 | * `cat /dev/ttyAMA0` - See raw data from the [Adafruit Ultimate GPS Breakout](https://www.adafruit.com/product/746) 109 | 110 | 111 | ## GPS data via NodeJS 112 | Now that data is coming from the gps unit, thru `gpsd`, we can read that data from node with the help of [node-gpsd](https://github.com/eelcocramer/node-gpsd). 113 | 114 | Run `npm install` to install the deps which includes [node-gpsd](https://github.com/eelcocramer/node-gpsd) 115 | 116 | This will handle the streaming of data from `gpsd` for us and provide the data as JSON (it can also start and stop the daemon, you should read the docs). 117 | 118 | Have a look at the `index.js` in this repo and run `npm start` in your terminal. If everything is set up correctly, you should see some basic info, then a bunch of TPV events streaming by. Now you have something you can write an application around. 119 | 120 | ## Auto Start the WebSocket server at System Boot 121 | After getting everything set up, you might want to have the WebSocket server [auto-start when your pizero boots up](https://www.raspberrypi.org/documentation/linux/usage/rc-local.md). 122 | * Look in the `package.json` file for the `scripts` section 123 | * note the value for the `start` command 124 | * Add that command to `rc.local` with the correct path to the `index.js` file 125 | 126 | Your entry in `rc.local` will look something like this... 127 | 128 | /usr/local/bin/node /home/pi/Projects/pzgps/index.js --port 9000 & 129 | 130 | Change the port number as needed. 131 | -------------------------------------------------------------------------------- /packages/pzgps-server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let argv = require('yargs').argv; 4 | let Server = require('ws').Server; 5 | let merge = require('lodash/merge'); 6 | let haversine = require('haversine'); 7 | 8 | let daemon = require('./lib/daemon.js'); 9 | 10 | let mqkey = { 11 | consumerKey: '' 12 | }; 13 | 14 | let location = { 15 | current: {} 16 | }; 17 | 18 | if (argv.mq) { 19 | try { 20 | mqkey.consumerKey = require('./lib/mqkey.js').consumerKey; 21 | } catch (err) { 22 | console.warn('Failed to import mqkey', err); 23 | } 24 | } 25 | 26 | let wss = new Server({ 27 | port: (argv.port) ? argv.port : '9000' 28 | }); 29 | 30 | const dmon = daemon.init({ 31 | port: (argv.daemonPort) ? argv.daemonPort : 2947 32 | }); 33 | dmon.start(); 34 | const listener = daemon.listen({}); 35 | 36 | const distance = (from, to) => { 37 | return haversine(from, to); 38 | }; 39 | 40 | // set up server listener 41 | wss.on('connection', socket => { 42 | console.log('new websocket connection, (', wss.clients.length, ' total)'); 43 | 44 | // send out the location data on an interval 45 | let intervalId = setInterval(function () { 46 | socket.send(JSON.stringify(location.current)); 47 | }, 2000); 48 | 49 | socket.on('message', data => { // (data, flags) 50 | let parsedData = JSON.parse(data); 51 | 52 | // there is room for more structure around recieving messages with 53 | // different actions, probably starts to look like a router of some sort 54 | if (parsedData.action === 'pzgps.get.consumerKey' && mqkey.consumerKey) { 55 | socket.send(JSON.stringify(mqkey)); 56 | } 57 | 58 | if (parsedData.action === 'pzgps.get.distance') { 59 | let d = distance(parsedData.from, parsedData.to); 60 | socket.send(JSON.stringify(merge(d, { 61 | event: 'pzgps.get.distance' 62 | }))); 63 | } 64 | }); 65 | 66 | socket.on('close', () => { 67 | clearInterval(intervalId); 68 | console.log('websocket connection closed, (', wss.clients.length, ' remain)'); 69 | }); 70 | }); 71 | 72 | // set up event listeners 73 | listener.on('connected', () => console.log('listener is conected')); 74 | listener.on('DEVICE', data => console.log('device', data)); 75 | listener.on('TPV', tpvData => { 76 | location.current = tpvData; 77 | }); 78 | 79 | listener.connect(); 80 | listener.watch(); 81 | listener.device(); 82 | -------------------------------------------------------------------------------- /packages/pzgps-server/lib/daemon.js: -------------------------------------------------------------------------------- 1 | let gpsd = require('node-gpsd'); 2 | 3 | let init = opt => { 4 | return new gpsd.Daemon({ 5 | program: 'gpsd', 6 | port: (opt.port) ? opt.port : 2947, 7 | device: (opt.dev) ? (opt.dev) : '/dev/ttyAMA0', 8 | pid: (opt.pid) ? opt.pid : '/tmp/gpsd.pid', 9 | logger: { 10 | info: data => { 11 | console.log('daemon info -', data); 12 | }, 13 | warn: err => { 14 | console.warn('daemon warn -', err); 15 | }, 16 | error: (err, msg) => { 17 | console.error('daemon error -', err, msg); 18 | } 19 | } 20 | }); 21 | }; 22 | 23 | let startDaemon = daemon => { 24 | daemon.start(function (arg) { 25 | console.log('GPSD start', arg); 26 | }); 27 | }; 28 | 29 | let stopDaemon = daemon => { 30 | // why doesn't stop() work (gpsd exiting when run via .spawn()) 31 | // since it exits, gpsd.js sets it to undefined, but somehow the daemon is still running! 32 | // see: https://github.com/eelcocramer/node-gpsd/blob/master/lib/gpsd.js#L205 33 | daemon.stop(function (arg) { 34 | console.log('GPSD Stop', arg); 35 | }); 36 | }; 37 | 38 | let listen = opt => { 39 | return new gpsd.Listener({ 40 | port: (opt.port) ? opt.port : 2947, 41 | hostname: 'localhost', 42 | parse: true, 43 | logger: { 44 | info: data => { 45 | console.log('listener info -', data); 46 | }, 47 | warn: err => { 48 | console.warn('listener warn -', err); 49 | }, 50 | error: (err, msg) => { 51 | console.error('listener error -', err, msg); 52 | } 53 | } 54 | }); 55 | }; 56 | 57 | module.exports = { 58 | init: init, 59 | startDaemon: startDaemon, 60 | stopDaemon: stopDaemon, 61 | listen: listen 62 | }; 63 | -------------------------------------------------------------------------------- /packages/pzgps-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pzgps-server", 3 | "version": "1.1.2", 4 | "description": "Read GPS data from a PiZero in a single page web app via a WebSocket", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "xo", 8 | "start": "node index.js --port 9000", 9 | "withMapquest": "node index.js --port 9000 --mq" 10 | }, 11 | "keywords": [ 12 | "gps" 13 | ], 14 | "author": "dankapusta", 15 | "license": "Apache-2.0", 16 | "xo": { 17 | "space": true, 18 | "semicolon": true, 19 | "ignores": [ 20 | "front-ends/**" 21 | ], 22 | "rules": { 23 | "import/no-dynamic-require": 1 24 | } 25 | }, 26 | "dependencies": { 27 | "haversine": "^1.0.2", 28 | "lodash": "^4.17.2", 29 | "node-gpsd": "^0.2.6", 30 | "ws": "^7.2.0", 31 | "yargs": "^6.0.0" 32 | }, 33 | "devDependencies": { 34 | "xo": "^0.17.1" 35 | } 36 | } 37 | --------------------------------------------------------------------------------