├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── FUNDING.yml ├── LICENSE ├── Logo.svg ├── README.md ├── docs ├── CNAME ├── GD-quick-overview.gif ├── favicon.ico ├── gtr-cof.css └── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── chord-interval-module.ts ├── cof-module.ts ├── cookie-module.ts ├── events-module.ts ├── gtr-cof.ts ├── gtr-module.ts ├── menu-module.ts ├── midi-module.ts ├── mod-module.ts ├── modes-module.ts ├── music-module.ts ├── permalink-module.ts ├── scale-family-module.ts ├── settings-module.ts ├── state-module.ts ├── tonics-module.ts └── tuning-module.ts └── tsconfig.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | jobs: 4 | build-and-deploy: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v1 9 | - run: npm ci 10 | - run: tsc --build tsconfig.json 11 | - run: mkdir docs/src 12 | - run: cp src/*.ts docs/src 13 | - run: echo "Run number=$GITHUB_RUN_NUMBER, Actor=$GITHUB_ACTOR, Sha=$GITHUB_SHA" > docs/version.txt 14 | - name: Deploy 15 | uses: peaceiris/actions-gh-pages@v3 16 | with: 17 | github_token: ${{ secrets.GITHUB_TOKEN }} 18 | publish_dir: ./docs 19 | publish_branch: publish -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | debug.log 4 | gtr-cof.js 5 | gtr-cof.js.map -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "typescript", 6 | "tsconfig": "tsconfig.json", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | } 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | # repo: mikehadlow/gtr-cof 2 | # filename: FUNDING.YML 3 | 4 | github: mikehadlow 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Mike Hadlow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 43 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 67 | G 78 | 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guitar Dashboard 2 | An interactive music theory dashboard for guitarists. http://guitardashboard.com/ 3 | 4 | The aim is to provide a graphical representation of music theory elements (scales, modes, chords etc) mapped to a guitar fretboard. 5 | 6 | ## Developing with VS Code 7 | 8 | Guitar Dashboard is written in Typescript using VS Code. Make all code changes in the src/*.ts files. Compilation outputs to the docs folder, do not edit the *.js or *.js.map files in this directory. They are included in the source repository because the website is hosted in GitHub pages which does not support Typescript compilation. 9 | 10 | 1. Clone or fork-and-clone this repository. 11 | 2. File -> Open folder at the root directory of the cloned repository. 12 | 4. To develop locally using lite-server: 13 | - npm install 14 | - npm start 15 | 5. Browse to http://localhost:10001/ 16 | 6. Edit the src/*.ts, index.html and gtr-cof.css files. 17 | 8. Commit, push to GitHub and create a pull request :) 18 | 19 | ## Developing without VS Code 20 | 21 | First, make sure you have TypeScript installed. If not, `npm install -g typescript` will do the trick. 22 | 23 | 1. Clone the repo and go into it 24 | 2. Run `npm install` 25 | 3. Open a shell and run `tsc --watch` so that the sources are always rebuilt automatically on source changes 26 | 4. Open another shell and run `npm start` in it so that results will be visible in a browser 27 | 5. Browse to http://localhost:10001/ 28 | 6. Edit the src/*.ts, index.html and gtr-cof.css files. 29 | 7. Commit, push to GitHub and create a pull request :) 30 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | guitardashboard.com -------------------------------------------------------------------------------- /docs/GD-quick-overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehadlow/gtr-cof/7d108d983dc439d81f2fae7472810bc0b9440c36/docs/GD-quick-overview.gif -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikehadlow/gtr-cof/7d108d983dc439d81f2fae7472810bc0b9440c36/docs/favicon.ico -------------------------------------------------------------------------------- /docs/gtr-cof.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; 3 | font-size: 12pt; 4 | margin: 0px; 5 | } 6 | 7 | text { 8 | pointer-events: none; 9 | } 10 | 11 | div { 12 | margin: 0px; 13 | padding: 0px; 14 | } 15 | 16 | .header { 17 | margin: 0; 18 | padding: 0; 19 | overflow: hidden; 20 | background-color: #333; 21 | } 22 | 23 | footer { 24 | margin: 0; 25 | padding: 0px 16px; 26 | overflow: hidden; 27 | color: white; 28 | background-color: #333; 29 | } 30 | 31 | .title { 32 | font-weight: bold; 33 | font-size: 14pt; 34 | margin: 0pt; 35 | } 36 | 37 | .menu { 38 | display: inline-block; 39 | color: white; 40 | text-align: center; 41 | padding: 14px 16px; 42 | text-decoration: none; 43 | cursor: default; 44 | } 45 | 46 | .dropdown { 47 | display: inline-block; 48 | } 49 | 50 | .dropdown:hover { 51 | background-color: #444; 52 | } 53 | 54 | .dropdown-content { 55 | display: none; 56 | position: absolute; 57 | background-color: #f9f9f9; 58 | color: black; 59 | min-width: 160px; 60 | box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); 61 | text-align: left; 62 | z-index: 1; 63 | } 64 | 65 | .with-padding { 66 | padding: 12px 16px; 67 | } 68 | 69 | /* Links inside the dropdown */ 70 | .dropdown-content-item { 71 | color: black; 72 | padding: 12px 16px; 73 | text-decoration: none; 74 | display: block; 75 | cursor: default; 76 | } 77 | 78 | /* Change color of dropdown links on hover */ 79 | .dropdown-content-item:hover { 80 | background-color: #f1f1f1; 81 | } 82 | 83 | .dropdown-content-visible { 84 | display: block; 85 | } 86 | 87 | .content { 88 | padding: 10px 10px 10px 10px; 89 | } 90 | 91 | /* Circle of Fifths */ 92 | .note-segment { 93 | fill: lightgrey; 94 | stroke: none; 95 | } 96 | .note-segment-tonic { 97 | fill: yellow; 98 | stroke: black; 99 | stroke-width: 3px; 100 | } 101 | .note-segment-scale { 102 | fill: white; 103 | stroke: black; 104 | stroke-width: 3px; 105 | } 106 | .note-segment-text { 107 | font-size: 35px; 108 | text-anchor: middle; 109 | fill: black; 110 | } 111 | .degree-segment { 112 | fill: none; 113 | stroke: none; 114 | } 115 | .degree-segment-selected { 116 | fill: white; 117 | stroke: black; 118 | stroke-width: 2px; 119 | } 120 | .degree-segment-text { 121 | font-size: 20px; 122 | text-anchor: middle; 123 | fill: black; 124 | } 125 | .chord-segment { 126 | fill: none; 127 | stroke: none; 128 | } 129 | .chord-segment-dim { 130 | fill: lightcoral; 131 | stroke: black; 132 | stroke-width: 2px; 133 | } 134 | .chord-segment-aug { 135 | fill: lightsalmon; 136 | stroke: black; 137 | stroke-width: 2px; 138 | } 139 | .chord-segment-minor { 140 | fill: lightblue; 141 | stroke: black; 142 | stroke-width: 2px; 143 | } 144 | .chord-segment-major { 145 | fill: lightgreen; 146 | stroke: black; 147 | stroke-width: 2px; 148 | } 149 | .chord-segment-note { 150 | fill: none; 151 | stroke: none; 152 | } 153 | .interval-note-selected { 154 | stroke: black; 155 | stroke-width: 2px; 156 | } 157 | .interval-segment { 158 | fill: white; 159 | stroke: none; 160 | } 161 | .interval-note { 162 | fill: none; 163 | stroke: none; 164 | } 165 | /* Modes */ 166 | .mode-button { 167 | fill: white; 168 | stroke: black; 169 | } 170 | .mode-button-selected { 171 | fill: yellow; 172 | } 173 | .mode-text { 174 | font-size: 15; 175 | text-anchor: left; 176 | fill: black; 177 | } 178 | 179 | /* Tonics */ 180 | .tonic-button { 181 | fill: white; 182 | stroke: black; 183 | } 184 | .tonic-button-grey { 185 | fill: grey; 186 | } 187 | .tonic-button-selected { 188 | fill: yellow; 189 | } 190 | .tonic-text { 191 | font-size: 15; 192 | text-anchor: left; 193 | fill: black; 194 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Guitar Chord Finder, Free Interactive Tool | Guitar Dashboard 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 83 | 84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 |
92 | 95 | 96 | 97 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | }, 5 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gtr-cof", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/d3": { 8 | "version": "3.5.44", 9 | "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.44.tgz", 10 | "integrity": "sha512-hFEcf03YGJ2uQoDYEp3nFD5mXWxly5kf6KOstuOQFEs9sUCN7kNlKhcYkpZ3gK6PiHz4XRLkoHa80NVCJNeLBw==", 11 | "dev": true 12 | }, 13 | "@types/webmidi": { 14 | "version": "2.0.4", 15 | "resolved": "https://registry.npmjs.org/@types/webmidi/-/webmidi-2.0.4.tgz", 16 | "integrity": "sha512-ouA837zUpSzURsUGvcnay6JwUz2B/UoJP3Lkacnz60y+rul8/auVOFS9LSO6v0gdNbGvRkbHEpayD8PbO7IKhw==", 17 | "dev": true 18 | }, 19 | "accepts": { 20 | "version": "1.3.7", 21 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 22 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 23 | "dev": true, 24 | "requires": { 25 | "mime-types": "~2.1.24", 26 | "negotiator": "0.6.2" 27 | } 28 | }, 29 | "after": { 30 | "version": "0.8.2", 31 | "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", 32 | "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", 33 | "dev": true 34 | }, 35 | "ansi-regex": { 36 | "version": "3.0.0", 37 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 38 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 39 | "dev": true 40 | }, 41 | "ansi-styles": { 42 | "version": "3.2.1", 43 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 44 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 45 | "dev": true, 46 | "requires": { 47 | "color-convert": "^1.9.0" 48 | } 49 | }, 50 | "anymatch": { 51 | "version": "3.1.1", 52 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 53 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 54 | "dev": true, 55 | "requires": { 56 | "normalize-path": "^3.0.0", 57 | "picomatch": "^2.0.4" 58 | } 59 | }, 60 | "arraybuffer.slice": { 61 | "version": "0.0.7", 62 | "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", 63 | "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", 64 | "dev": true 65 | }, 66 | "async": { 67 | "version": "1.5.2", 68 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 69 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", 70 | "dev": true 71 | }, 72 | "async-each-series": { 73 | "version": "0.1.1", 74 | "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", 75 | "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=", 76 | "dev": true 77 | }, 78 | "async-limiter": { 79 | "version": "1.0.1", 80 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 81 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", 82 | "dev": true 83 | }, 84 | "axios": { 85 | "version": "0.19.0", 86 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", 87 | "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", 88 | "dev": true, 89 | "requires": { 90 | "follow-redirects": "1.5.10", 91 | "is-buffer": "^2.0.2" 92 | }, 93 | "dependencies": { 94 | "follow-redirects": { 95 | "version": "1.5.10", 96 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 97 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 98 | "dev": true, 99 | "requires": { 100 | "debug": "=3.1.0" 101 | } 102 | } 103 | } 104 | }, 105 | "backo2": { 106 | "version": "1.0.2", 107 | "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", 108 | "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", 109 | "dev": true 110 | }, 111 | "balanced-match": { 112 | "version": "1.0.0", 113 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 114 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 115 | "dev": true 116 | }, 117 | "base64-arraybuffer": { 118 | "version": "0.1.4", 119 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", 120 | "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", 121 | "dev": true 122 | }, 123 | "base64id": { 124 | "version": "1.0.0", 125 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", 126 | "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", 127 | "dev": true 128 | }, 129 | "batch": { 130 | "version": "0.6.1", 131 | "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", 132 | "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", 133 | "dev": true 134 | }, 135 | "better-assert": { 136 | "version": "1.0.2", 137 | "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", 138 | "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", 139 | "dev": true, 140 | "requires": { 141 | "callsite": "1.0.0" 142 | } 143 | }, 144 | "binary-extensions": { 145 | "version": "2.1.0", 146 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", 147 | "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", 148 | "dev": true 149 | }, 150 | "blob": { 151 | "version": "0.0.5", 152 | "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", 153 | "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", 154 | "dev": true 155 | }, 156 | "brace-expansion": { 157 | "version": "1.1.11", 158 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 159 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 160 | "dev": true, 161 | "requires": { 162 | "balanced-match": "^1.0.0", 163 | "concat-map": "0.0.1" 164 | } 165 | }, 166 | "braces": { 167 | "version": "3.0.2", 168 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 169 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 170 | "dev": true, 171 | "requires": { 172 | "fill-range": "^7.0.1" 173 | } 174 | }, 175 | "browser-sync": { 176 | "version": "2.26.13", 177 | "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.26.13.tgz", 178 | "integrity": "sha512-JPYLTngIzI+Dzx+StSSlMtF+Q9yjdh58HW6bMFqkFXuzQkJL8FCvp4lozlS6BbECZcsM2Gmlgp0uhEjvl18X4w==", 179 | "dev": true, 180 | "requires": { 181 | "browser-sync-client": "^2.26.13", 182 | "browser-sync-ui": "^2.26.13", 183 | "bs-recipes": "1.3.4", 184 | "bs-snippet-injector": "^2.0.1", 185 | "chokidar": "^3.4.1", 186 | "connect": "3.6.6", 187 | "connect-history-api-fallback": "^1", 188 | "dev-ip": "^1.0.1", 189 | "easy-extender": "^2.3.4", 190 | "eazy-logger": "3.1.0", 191 | "etag": "^1.8.1", 192 | "fresh": "^0.5.2", 193 | "fs-extra": "3.0.1", 194 | "http-proxy": "^1.18.1", 195 | "immutable": "^3", 196 | "localtunnel": "^2.0.0", 197 | "micromatch": "^4.0.2", 198 | "opn": "5.3.0", 199 | "portscanner": "2.1.1", 200 | "qs": "6.2.3", 201 | "raw-body": "^2.3.2", 202 | "resp-modifier": "6.0.2", 203 | "rx": "4.1.0", 204 | "send": "0.16.2", 205 | "serve-index": "1.9.1", 206 | "serve-static": "1.13.2", 207 | "server-destroy": "1.0.1", 208 | "socket.io": "2.1.1", 209 | "ua-parser-js": "^0.7.18", 210 | "yargs": "^15.4.1" 211 | }, 212 | "dependencies": { 213 | "ansi-regex": { 214 | "version": "5.0.0", 215 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 216 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 217 | "dev": true 218 | }, 219 | "ansi-styles": { 220 | "version": "4.3.0", 221 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 222 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 223 | "dev": true, 224 | "requires": { 225 | "color-convert": "^2.0.1" 226 | } 227 | }, 228 | "cliui": { 229 | "version": "6.0.0", 230 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", 231 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", 232 | "dev": true, 233 | "requires": { 234 | "string-width": "^4.2.0", 235 | "strip-ansi": "^6.0.0", 236 | "wrap-ansi": "^6.2.0" 237 | } 238 | }, 239 | "color-convert": { 240 | "version": "2.0.1", 241 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 242 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 243 | "dev": true, 244 | "requires": { 245 | "color-name": "~1.1.4" 246 | } 247 | }, 248 | "color-name": { 249 | "version": "1.1.4", 250 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 251 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 252 | "dev": true 253 | }, 254 | "emoji-regex": { 255 | "version": "8.0.0", 256 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 257 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 258 | "dev": true 259 | }, 260 | "find-up": { 261 | "version": "4.1.0", 262 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 263 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 264 | "dev": true, 265 | "requires": { 266 | "locate-path": "^5.0.0", 267 | "path-exists": "^4.0.0" 268 | } 269 | }, 270 | "get-caller-file": { 271 | "version": "2.0.5", 272 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 273 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 274 | "dev": true 275 | }, 276 | "is-fullwidth-code-point": { 277 | "version": "3.0.0", 278 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 279 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 280 | "dev": true 281 | }, 282 | "locate-path": { 283 | "version": "5.0.0", 284 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 285 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 286 | "dev": true, 287 | "requires": { 288 | "p-locate": "^4.1.0" 289 | } 290 | }, 291 | "p-locate": { 292 | "version": "4.1.0", 293 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 294 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 295 | "dev": true, 296 | "requires": { 297 | "p-limit": "^2.2.0" 298 | } 299 | }, 300 | "path-exists": { 301 | "version": "4.0.0", 302 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 303 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 304 | "dev": true 305 | }, 306 | "require-main-filename": { 307 | "version": "2.0.0", 308 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 309 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 310 | "dev": true 311 | }, 312 | "string-width": { 313 | "version": "4.2.0", 314 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", 315 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", 316 | "dev": true, 317 | "requires": { 318 | "emoji-regex": "^8.0.0", 319 | "is-fullwidth-code-point": "^3.0.0", 320 | "strip-ansi": "^6.0.0" 321 | } 322 | }, 323 | "strip-ansi": { 324 | "version": "6.0.0", 325 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 326 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 327 | "dev": true, 328 | "requires": { 329 | "ansi-regex": "^5.0.0" 330 | } 331 | }, 332 | "wrap-ansi": { 333 | "version": "6.2.0", 334 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 335 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 336 | "dev": true, 337 | "requires": { 338 | "ansi-styles": "^4.0.0", 339 | "string-width": "^4.1.0", 340 | "strip-ansi": "^6.0.0" 341 | } 342 | }, 343 | "yargs": { 344 | "version": "15.4.1", 345 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", 346 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", 347 | "dev": true, 348 | "requires": { 349 | "cliui": "^6.0.0", 350 | "decamelize": "^1.2.0", 351 | "find-up": "^4.1.0", 352 | "get-caller-file": "^2.0.1", 353 | "require-directory": "^2.1.1", 354 | "require-main-filename": "^2.0.0", 355 | "set-blocking": "^2.0.0", 356 | "string-width": "^4.2.0", 357 | "which-module": "^2.0.0", 358 | "y18n": "^4.0.0", 359 | "yargs-parser": "^18.1.2" 360 | } 361 | }, 362 | "yargs-parser": { 363 | "version": "18.1.3", 364 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", 365 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", 366 | "dev": true, 367 | "requires": { 368 | "camelcase": "^5.0.0", 369 | "decamelize": "^1.2.0" 370 | } 371 | } 372 | } 373 | }, 374 | "browser-sync-client": { 375 | "version": "2.26.13", 376 | "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.26.13.tgz", 377 | "integrity": "sha512-p2VbZoYrpuDhkreq+/Sv1MkToHklh7T1OaIntDwpG6Iy2q/XkBcgwPcWjX+WwRNiZjN8MEehxIjEUh12LweLmQ==", 378 | "dev": true, 379 | "requires": { 380 | "etag": "1.8.1", 381 | "fresh": "0.5.2", 382 | "mitt": "^1.1.3", 383 | "rxjs": "^5.5.6" 384 | }, 385 | "dependencies": { 386 | "rxjs": { 387 | "version": "5.5.12", 388 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", 389 | "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", 390 | "dev": true, 391 | "requires": { 392 | "symbol-observable": "1.0.1" 393 | } 394 | } 395 | } 396 | }, 397 | "browser-sync-ui": { 398 | "version": "2.26.13", 399 | "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.26.13.tgz", 400 | "integrity": "sha512-6NJ/pCnhCnBMzaty1opWo7ipDmFAIk8U71JMQGKJxblCUaGfdsbF2shf6XNZSkXYia1yS0vwKu9LIOzpXqQZCA==", 401 | "dev": true, 402 | "requires": { 403 | "async-each-series": "0.1.1", 404 | "connect-history-api-fallback": "^1", 405 | "immutable": "^3", 406 | "server-destroy": "1.0.1", 407 | "socket.io-client": "^2.0.4", 408 | "stream-throttle": "^0.1.3" 409 | } 410 | }, 411 | "bs-recipes": { 412 | "version": "1.3.4", 413 | "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", 414 | "integrity": "sha1-DS1NSKcYyMBEdp/cT4lZLci2lYU=", 415 | "dev": true 416 | }, 417 | "bs-snippet-injector": { 418 | "version": "2.0.1", 419 | "resolved": "https://registry.npmjs.org/bs-snippet-injector/-/bs-snippet-injector-2.0.1.tgz", 420 | "integrity": "sha1-YbU5PxH1JVntEgaTEANDtu2wTdU=", 421 | "dev": true 422 | }, 423 | "bytes": { 424 | "version": "3.1.0", 425 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 426 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 427 | "dev": true 428 | }, 429 | "callsite": { 430 | "version": "1.0.0", 431 | "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", 432 | "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", 433 | "dev": true 434 | }, 435 | "camelcase": { 436 | "version": "5.3.1", 437 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 438 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 439 | "dev": true 440 | }, 441 | "chalk": { 442 | "version": "2.4.2", 443 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 444 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 445 | "dev": true, 446 | "requires": { 447 | "ansi-styles": "^3.2.1", 448 | "escape-string-regexp": "^1.0.5", 449 | "supports-color": "^5.3.0" 450 | }, 451 | "dependencies": { 452 | "supports-color": { 453 | "version": "5.5.0", 454 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 455 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 456 | "dev": true, 457 | "requires": { 458 | "has-flag": "^3.0.0" 459 | } 460 | } 461 | } 462 | }, 463 | "chokidar": { 464 | "version": "3.4.3", 465 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", 466 | "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", 467 | "dev": true, 468 | "requires": { 469 | "anymatch": "~3.1.1", 470 | "braces": "~3.0.2", 471 | "fsevents": "~2.1.2", 472 | "glob-parent": "~5.1.0", 473 | "is-binary-path": "~2.1.0", 474 | "is-glob": "~4.0.1", 475 | "normalize-path": "~3.0.0", 476 | "readdirp": "~3.5.0" 477 | } 478 | }, 479 | "cliui": { 480 | "version": "4.1.0", 481 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", 482 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", 483 | "dev": true, 484 | "requires": { 485 | "string-width": "^2.1.1", 486 | "strip-ansi": "^4.0.0", 487 | "wrap-ansi": "^2.0.0" 488 | } 489 | }, 490 | "code-point-at": { 491 | "version": "1.1.0", 492 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 493 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 494 | "dev": true 495 | }, 496 | "color-convert": { 497 | "version": "1.9.3", 498 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 499 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 500 | "dev": true, 501 | "requires": { 502 | "color-name": "1.1.3" 503 | } 504 | }, 505 | "color-name": { 506 | "version": "1.1.3", 507 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 508 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 509 | "dev": true 510 | }, 511 | "commander": { 512 | "version": "2.20.3", 513 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 514 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 515 | "dev": true 516 | }, 517 | "component-bind": { 518 | "version": "1.0.0", 519 | "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", 520 | "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", 521 | "dev": true 522 | }, 523 | "component-emitter": { 524 | "version": "1.3.0", 525 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 526 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", 527 | "dev": true 528 | }, 529 | "component-inherit": { 530 | "version": "0.0.3", 531 | "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", 532 | "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", 533 | "dev": true 534 | }, 535 | "concat-map": { 536 | "version": "0.0.1", 537 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 538 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 539 | "dev": true 540 | }, 541 | "concurrently": { 542 | "version": "4.0.1", 543 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.0.1.tgz", 544 | "integrity": "sha512-D8UI+mlI/bfvrA57SeKOht6sEpb01dKk+8Yee4fbnkk1Ue8r3S+JXoEdFZIpzQlXJGtnxo47Wvvg/kG4ba3U6Q==", 545 | "dev": true, 546 | "requires": { 547 | "chalk": "^2.4.1", 548 | "date-fns": "^1.23.0", 549 | "lodash": "^4.17.10", 550 | "read-pkg": "^4.0.1", 551 | "rxjs": "6.2.2", 552 | "spawn-command": "^0.0.2-1", 553 | "supports-color": "^4.5.0", 554 | "tree-kill": "^1.1.0", 555 | "yargs": "^12.0.1" 556 | } 557 | }, 558 | "connect": { 559 | "version": "3.6.6", 560 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", 561 | "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", 562 | "dev": true, 563 | "requires": { 564 | "debug": "2.6.9", 565 | "finalhandler": "1.1.0", 566 | "parseurl": "~1.3.2", 567 | "utils-merge": "1.0.1" 568 | }, 569 | "dependencies": { 570 | "debug": { 571 | "version": "2.6.9", 572 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 573 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 574 | "dev": true, 575 | "requires": { 576 | "ms": "2.0.0" 577 | } 578 | } 579 | } 580 | }, 581 | "connect-history-api-fallback": { 582 | "version": "1.6.0", 583 | "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", 584 | "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", 585 | "dev": true 586 | }, 587 | "connect-logger": { 588 | "version": "0.0.1", 589 | "resolved": "https://registry.npmjs.org/connect-logger/-/connect-logger-0.0.1.tgz", 590 | "integrity": "sha1-TZmZeKHSC7RgjnzUNNdBZSJVF0s=", 591 | "dev": true, 592 | "requires": { 593 | "moment": "*" 594 | } 595 | }, 596 | "cookie": { 597 | "version": "0.3.1", 598 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 599 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", 600 | "dev": true 601 | }, 602 | "cross-spawn": { 603 | "version": "6.0.5", 604 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 605 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 606 | "dev": true, 607 | "requires": { 608 | "nice-try": "^1.0.4", 609 | "path-key": "^2.0.1", 610 | "semver": "^5.5.0", 611 | "shebang-command": "^1.2.0", 612 | "which": "^1.2.9" 613 | } 614 | }, 615 | "d3": { 616 | "version": "3.5.17", 617 | "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", 618 | "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=", 619 | "dev": true 620 | }, 621 | "date-fns": { 622 | "version": "1.30.1", 623 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", 624 | "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", 625 | "dev": true 626 | }, 627 | "debug": { 628 | "version": "3.1.0", 629 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 630 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 631 | "dev": true, 632 | "requires": { 633 | "ms": "2.0.0" 634 | } 635 | }, 636 | "decamelize": { 637 | "version": "1.2.0", 638 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 639 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 640 | "dev": true 641 | }, 642 | "depd": { 643 | "version": "1.1.2", 644 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 645 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 646 | "dev": true 647 | }, 648 | "destroy": { 649 | "version": "1.0.4", 650 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 651 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", 652 | "dev": true 653 | }, 654 | "dev-ip": { 655 | "version": "1.0.1", 656 | "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", 657 | "integrity": "sha1-p2o+0YVb56ASu4rBbLgPPADcKPA=", 658 | "dev": true 659 | }, 660 | "dlv": { 661 | "version": "1.1.3", 662 | "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", 663 | "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", 664 | "dev": true 665 | }, 666 | "easy-extender": { 667 | "version": "2.3.4", 668 | "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", 669 | "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", 670 | "dev": true, 671 | "requires": { 672 | "lodash": "^4.17.10" 673 | } 674 | }, 675 | "eazy-logger": { 676 | "version": "3.1.0", 677 | "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-3.1.0.tgz", 678 | "integrity": "sha512-/snsn2JqBtUSSstEl4R0RKjkisGHAhvYj89i7r3ytNUKW12y178KDZwXLXIgwDqLW6E/VRMT9qfld7wvFae8bQ==", 679 | "dev": true, 680 | "requires": { 681 | "tfunk": "^4.0.0" 682 | } 683 | }, 684 | "ee-first": { 685 | "version": "1.1.1", 686 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 687 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", 688 | "dev": true 689 | }, 690 | "emoji-regex": { 691 | "version": "7.0.3", 692 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 693 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 694 | "dev": true 695 | }, 696 | "encodeurl": { 697 | "version": "1.0.2", 698 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 699 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 700 | "dev": true 701 | }, 702 | "end-of-stream": { 703 | "version": "1.4.4", 704 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 705 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 706 | "dev": true, 707 | "requires": { 708 | "once": "^1.4.0" 709 | } 710 | }, 711 | "engine.io": { 712 | "version": "3.2.1", 713 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", 714 | "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", 715 | "dev": true, 716 | "requires": { 717 | "accepts": "~1.3.4", 718 | "base64id": "1.0.0", 719 | "cookie": "0.3.1", 720 | "debug": "~3.1.0", 721 | "engine.io-parser": "~2.1.0", 722 | "ws": "~3.3.1" 723 | }, 724 | "dependencies": { 725 | "base64-arraybuffer": { 726 | "version": "0.1.5", 727 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 728 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", 729 | "dev": true 730 | }, 731 | "engine.io-parser": { 732 | "version": "2.1.3", 733 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 734 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 735 | "dev": true, 736 | "requires": { 737 | "after": "0.8.2", 738 | "arraybuffer.slice": "~0.0.7", 739 | "base64-arraybuffer": "0.1.5", 740 | "blob": "0.0.5", 741 | "has-binary2": "~1.0.2" 742 | } 743 | }, 744 | "ws": { 745 | "version": "3.3.3", 746 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 747 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 748 | "dev": true, 749 | "requires": { 750 | "async-limiter": "~1.0.0", 751 | "safe-buffer": "~5.1.0", 752 | "ultron": "~1.1.0" 753 | } 754 | } 755 | } 756 | }, 757 | "engine.io-client": { 758 | "version": "3.4.4", 759 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz", 760 | "integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==", 761 | "dev": true, 762 | "requires": { 763 | "component-emitter": "~1.3.0", 764 | "component-inherit": "0.0.3", 765 | "debug": "~3.1.0", 766 | "engine.io-parser": "~2.2.0", 767 | "has-cors": "1.1.0", 768 | "indexof": "0.0.1", 769 | "parseqs": "0.0.6", 770 | "parseuri": "0.0.6", 771 | "ws": "~6.1.0", 772 | "xmlhttprequest-ssl": "~1.5.4", 773 | "yeast": "0.1.2" 774 | } 775 | }, 776 | "engine.io-parser": { 777 | "version": "2.2.1", 778 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", 779 | "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", 780 | "dev": true, 781 | "requires": { 782 | "after": "0.8.2", 783 | "arraybuffer.slice": "~0.0.7", 784 | "base64-arraybuffer": "0.1.4", 785 | "blob": "0.0.5", 786 | "has-binary2": "~1.0.2" 787 | } 788 | }, 789 | "error-ex": { 790 | "version": "1.3.2", 791 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 792 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 793 | "dev": true, 794 | "requires": { 795 | "is-arrayish": "^0.2.1" 796 | } 797 | }, 798 | "escape-html": { 799 | "version": "1.0.3", 800 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 801 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", 802 | "dev": true 803 | }, 804 | "escape-string-regexp": { 805 | "version": "1.0.5", 806 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 807 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 808 | "dev": true 809 | }, 810 | "etag": { 811 | "version": "1.8.1", 812 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 813 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 814 | "dev": true 815 | }, 816 | "eventemitter3": { 817 | "version": "4.0.7", 818 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 819 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 820 | "dev": true 821 | }, 822 | "execa": { 823 | "version": "1.0.0", 824 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", 825 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", 826 | "dev": true, 827 | "requires": { 828 | "cross-spawn": "^6.0.0", 829 | "get-stream": "^4.0.0", 830 | "is-stream": "^1.1.0", 831 | "npm-run-path": "^2.0.0", 832 | "p-finally": "^1.0.0", 833 | "signal-exit": "^3.0.0", 834 | "strip-eof": "^1.0.0" 835 | } 836 | }, 837 | "fill-range": { 838 | "version": "7.0.1", 839 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 840 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 841 | "dev": true, 842 | "requires": { 843 | "to-regex-range": "^5.0.1" 844 | } 845 | }, 846 | "finalhandler": { 847 | "version": "1.1.0", 848 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", 849 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", 850 | "dev": true, 851 | "requires": { 852 | "debug": "2.6.9", 853 | "encodeurl": "~1.0.1", 854 | "escape-html": "~1.0.3", 855 | "on-finished": "~2.3.0", 856 | "parseurl": "~1.3.2", 857 | "statuses": "~1.3.1", 858 | "unpipe": "~1.0.0" 859 | }, 860 | "dependencies": { 861 | "debug": { 862 | "version": "2.6.9", 863 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 864 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 865 | "dev": true, 866 | "requires": { 867 | "ms": "2.0.0" 868 | } 869 | } 870 | } 871 | }, 872 | "find-up": { 873 | "version": "3.0.0", 874 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 875 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 876 | "dev": true, 877 | "requires": { 878 | "locate-path": "^3.0.0" 879 | } 880 | }, 881 | "follow-redirects": { 882 | "version": "1.13.0", 883 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 884 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", 885 | "dev": true 886 | }, 887 | "fresh": { 888 | "version": "0.5.2", 889 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 890 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 891 | "dev": true 892 | }, 893 | "fs-extra": { 894 | "version": "3.0.1", 895 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", 896 | "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", 897 | "dev": true, 898 | "requires": { 899 | "graceful-fs": "^4.1.2", 900 | "jsonfile": "^3.0.0", 901 | "universalify": "^0.1.0" 902 | } 903 | }, 904 | "fsevents": { 905 | "version": "2.1.3", 906 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", 907 | "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", 908 | "dev": true, 909 | "optional": true 910 | }, 911 | "function-bind": { 912 | "version": "1.1.1", 913 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 914 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 915 | "dev": true 916 | }, 917 | "get-caller-file": { 918 | "version": "1.0.3", 919 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 920 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", 921 | "dev": true 922 | }, 923 | "get-stream": { 924 | "version": "4.1.0", 925 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", 926 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", 927 | "dev": true, 928 | "requires": { 929 | "pump": "^3.0.0" 930 | } 931 | }, 932 | "glob-parent": { 933 | "version": "5.1.1", 934 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", 935 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", 936 | "dev": true, 937 | "requires": { 938 | "is-glob": "^4.0.1" 939 | } 940 | }, 941 | "graceful-fs": { 942 | "version": "4.2.4", 943 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 944 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", 945 | "dev": true 946 | }, 947 | "has": { 948 | "version": "1.0.3", 949 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 950 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 951 | "dev": true, 952 | "requires": { 953 | "function-bind": "^1.1.1" 954 | } 955 | }, 956 | "has-ansi": { 957 | "version": "2.0.0", 958 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 959 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 960 | "dev": true, 961 | "requires": { 962 | "ansi-regex": "^2.0.0" 963 | }, 964 | "dependencies": { 965 | "ansi-regex": { 966 | "version": "2.1.1", 967 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 968 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 969 | "dev": true 970 | } 971 | } 972 | }, 973 | "has-binary2": { 974 | "version": "1.0.3", 975 | "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", 976 | "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", 977 | "dev": true, 978 | "requires": { 979 | "isarray": "2.0.1" 980 | } 981 | }, 982 | "has-cors": { 983 | "version": "1.1.0", 984 | "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", 985 | "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", 986 | "dev": true 987 | }, 988 | "has-flag": { 989 | "version": "3.0.0", 990 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 991 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 992 | "dev": true 993 | }, 994 | "hosted-git-info": { 995 | "version": "2.8.9", 996 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", 997 | "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", 998 | "dev": true 999 | }, 1000 | "http-errors": { 1001 | "version": "1.7.3", 1002 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", 1003 | "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", 1004 | "dev": true, 1005 | "requires": { 1006 | "depd": "~1.1.2", 1007 | "inherits": "2.0.4", 1008 | "setprototypeof": "1.1.1", 1009 | "statuses": ">= 1.5.0 < 2", 1010 | "toidentifier": "1.0.0" 1011 | }, 1012 | "dependencies": { 1013 | "statuses": { 1014 | "version": "1.5.0", 1015 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1016 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1017 | "dev": true 1018 | } 1019 | } 1020 | }, 1021 | "http-proxy": { 1022 | "version": "1.18.1", 1023 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 1024 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 1025 | "dev": true, 1026 | "requires": { 1027 | "eventemitter3": "^4.0.0", 1028 | "follow-redirects": "^1.0.0", 1029 | "requires-port": "^1.0.0" 1030 | } 1031 | }, 1032 | "iconv-lite": { 1033 | "version": "0.4.24", 1034 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1035 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1036 | "dev": true, 1037 | "requires": { 1038 | "safer-buffer": ">= 2.1.2 < 3" 1039 | } 1040 | }, 1041 | "immutable": { 1042 | "version": "3.8.2", 1043 | "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", 1044 | "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=", 1045 | "dev": true 1046 | }, 1047 | "indexof": { 1048 | "version": "0.0.1", 1049 | "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", 1050 | "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", 1051 | "dev": true 1052 | }, 1053 | "inherits": { 1054 | "version": "2.0.4", 1055 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1056 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1057 | "dev": true 1058 | }, 1059 | "invert-kv": { 1060 | "version": "2.0.0", 1061 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", 1062 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", 1063 | "dev": true 1064 | }, 1065 | "is-arrayish": { 1066 | "version": "0.2.1", 1067 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1068 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 1069 | "dev": true 1070 | }, 1071 | "is-binary-path": { 1072 | "version": "2.1.0", 1073 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1074 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1075 | "dev": true, 1076 | "requires": { 1077 | "binary-extensions": "^2.0.0" 1078 | } 1079 | }, 1080 | "is-buffer": { 1081 | "version": "2.0.4", 1082 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", 1083 | "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", 1084 | "dev": true 1085 | }, 1086 | "is-core-module": { 1087 | "version": "2.0.0", 1088 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", 1089 | "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", 1090 | "dev": true, 1091 | "requires": { 1092 | "has": "^1.0.3" 1093 | } 1094 | }, 1095 | "is-extglob": { 1096 | "version": "2.1.1", 1097 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1098 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 1099 | "dev": true 1100 | }, 1101 | "is-fullwidth-code-point": { 1102 | "version": "2.0.0", 1103 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 1104 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 1105 | "dev": true 1106 | }, 1107 | "is-glob": { 1108 | "version": "4.0.1", 1109 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 1110 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 1111 | "dev": true, 1112 | "requires": { 1113 | "is-extglob": "^2.1.1" 1114 | } 1115 | }, 1116 | "is-number": { 1117 | "version": "7.0.0", 1118 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1119 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1120 | "dev": true 1121 | }, 1122 | "is-number-like": { 1123 | "version": "1.0.8", 1124 | "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", 1125 | "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", 1126 | "dev": true, 1127 | "requires": { 1128 | "lodash.isfinite": "^3.3.2" 1129 | } 1130 | }, 1131 | "is-stream": { 1132 | "version": "1.1.0", 1133 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 1134 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", 1135 | "dev": true 1136 | }, 1137 | "is-wsl": { 1138 | "version": "1.1.0", 1139 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", 1140 | "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", 1141 | "dev": true 1142 | }, 1143 | "isarray": { 1144 | "version": "2.0.1", 1145 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", 1146 | "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", 1147 | "dev": true 1148 | }, 1149 | "isexe": { 1150 | "version": "2.0.0", 1151 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1152 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1153 | "dev": true 1154 | }, 1155 | "json-parse-better-errors": { 1156 | "version": "1.0.2", 1157 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 1158 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", 1159 | "dev": true 1160 | }, 1161 | "jsonfile": { 1162 | "version": "3.0.1", 1163 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", 1164 | "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", 1165 | "dev": true, 1166 | "requires": { 1167 | "graceful-fs": "^4.1.6" 1168 | } 1169 | }, 1170 | "lcid": { 1171 | "version": "2.0.0", 1172 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 1173 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", 1174 | "dev": true, 1175 | "requires": { 1176 | "invert-kv": "^2.0.0" 1177 | } 1178 | }, 1179 | "limiter": { 1180 | "version": "1.1.5", 1181 | "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", 1182 | "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", 1183 | "dev": true 1184 | }, 1185 | "lite-server": { 1186 | "version": "1.3.4", 1187 | "resolved": "https://registry.npmjs.org/lite-server/-/lite-server-1.3.4.tgz", 1188 | "integrity": "sha1-0B8xvqGJb1K9rkdw3nVv8L9xW50=", 1189 | "dev": true, 1190 | "requires": { 1191 | "browser-sync": "^2.11.1", 1192 | "connect-history-api-fallback": "^1.1.0", 1193 | "connect-logger": "0.0.1", 1194 | "yargs": "^3.32.0" 1195 | }, 1196 | "dependencies": { 1197 | "ansi-regex": { 1198 | "version": "2.1.1", 1199 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 1200 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 1201 | "dev": true 1202 | }, 1203 | "camelcase": { 1204 | "version": "2.1.1", 1205 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", 1206 | "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", 1207 | "dev": true 1208 | }, 1209 | "cliui": { 1210 | "version": "3.2.0", 1211 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", 1212 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", 1213 | "dev": true, 1214 | "requires": { 1215 | "string-width": "^1.0.1", 1216 | "strip-ansi": "^3.0.1", 1217 | "wrap-ansi": "^2.0.0" 1218 | } 1219 | }, 1220 | "invert-kv": { 1221 | "version": "1.0.0", 1222 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 1223 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", 1224 | "dev": true 1225 | }, 1226 | "is-fullwidth-code-point": { 1227 | "version": "1.0.0", 1228 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 1229 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 1230 | "dev": true, 1231 | "requires": { 1232 | "number-is-nan": "^1.0.0" 1233 | } 1234 | }, 1235 | "lcid": { 1236 | "version": "1.0.0", 1237 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 1238 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 1239 | "dev": true, 1240 | "requires": { 1241 | "invert-kv": "^1.0.0" 1242 | } 1243 | }, 1244 | "os-locale": { 1245 | "version": "1.4.0", 1246 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", 1247 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", 1248 | "dev": true, 1249 | "requires": { 1250 | "lcid": "^1.0.0" 1251 | } 1252 | }, 1253 | "string-width": { 1254 | "version": "1.0.2", 1255 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 1256 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 1257 | "dev": true, 1258 | "requires": { 1259 | "code-point-at": "^1.0.0", 1260 | "is-fullwidth-code-point": "^1.0.0", 1261 | "strip-ansi": "^3.0.0" 1262 | } 1263 | }, 1264 | "strip-ansi": { 1265 | "version": "3.0.1", 1266 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1267 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1268 | "dev": true, 1269 | "requires": { 1270 | "ansi-regex": "^2.0.0" 1271 | } 1272 | }, 1273 | "y18n": { 1274 | "version": "3.2.2", 1275 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", 1276 | "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", 1277 | "dev": true 1278 | }, 1279 | "yargs": { 1280 | "version": "3.32.0", 1281 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", 1282 | "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", 1283 | "dev": true, 1284 | "requires": { 1285 | "camelcase": "^2.0.1", 1286 | "cliui": "^3.0.3", 1287 | "decamelize": "^1.1.1", 1288 | "os-locale": "^1.4.0", 1289 | "string-width": "^1.0.1", 1290 | "window-size": "^0.1.4", 1291 | "y18n": "^3.2.0" 1292 | } 1293 | } 1294 | } 1295 | }, 1296 | "localtunnel": { 1297 | "version": "2.0.0", 1298 | "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.0.tgz", 1299 | "integrity": "sha512-g6E0aLgYYDvQDxIjIXkgJo2+pHj3sGg4Wz/XP3h2KtZnRsWPbOQY+hw1H8Z91jep998fkcVE9l+kghO+97vllg==", 1300 | "dev": true, 1301 | "requires": { 1302 | "axios": "0.19.0", 1303 | "debug": "4.1.1", 1304 | "openurl": "1.1.1", 1305 | "yargs": "13.3.0" 1306 | }, 1307 | "dependencies": { 1308 | "ansi-regex": { 1309 | "version": "4.1.0", 1310 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 1311 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 1312 | "dev": true 1313 | }, 1314 | "cliui": { 1315 | "version": "5.0.0", 1316 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 1317 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 1318 | "dev": true, 1319 | "requires": { 1320 | "string-width": "^3.1.0", 1321 | "strip-ansi": "^5.2.0", 1322 | "wrap-ansi": "^5.1.0" 1323 | } 1324 | }, 1325 | "debug": { 1326 | "version": "4.1.1", 1327 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 1328 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 1329 | "dev": true, 1330 | "requires": { 1331 | "ms": "^2.1.1" 1332 | } 1333 | }, 1334 | "get-caller-file": { 1335 | "version": "2.0.5", 1336 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1337 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 1338 | "dev": true 1339 | }, 1340 | "ms": { 1341 | "version": "2.1.2", 1342 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1343 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1344 | "dev": true 1345 | }, 1346 | "require-main-filename": { 1347 | "version": "2.0.0", 1348 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 1349 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 1350 | "dev": true 1351 | }, 1352 | "string-width": { 1353 | "version": "3.1.0", 1354 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 1355 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 1356 | "dev": true, 1357 | "requires": { 1358 | "emoji-regex": "^7.0.1", 1359 | "is-fullwidth-code-point": "^2.0.0", 1360 | "strip-ansi": "^5.1.0" 1361 | } 1362 | }, 1363 | "strip-ansi": { 1364 | "version": "5.2.0", 1365 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 1366 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 1367 | "dev": true, 1368 | "requires": { 1369 | "ansi-regex": "^4.1.0" 1370 | } 1371 | }, 1372 | "wrap-ansi": { 1373 | "version": "5.1.0", 1374 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 1375 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 1376 | "dev": true, 1377 | "requires": { 1378 | "ansi-styles": "^3.2.0", 1379 | "string-width": "^3.0.0", 1380 | "strip-ansi": "^5.0.0" 1381 | } 1382 | }, 1383 | "yargs": { 1384 | "version": "13.3.0", 1385 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", 1386 | "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", 1387 | "dev": true, 1388 | "requires": { 1389 | "cliui": "^5.0.0", 1390 | "find-up": "^3.0.0", 1391 | "get-caller-file": "^2.0.1", 1392 | "require-directory": "^2.1.1", 1393 | "require-main-filename": "^2.0.0", 1394 | "set-blocking": "^2.0.0", 1395 | "string-width": "^3.0.0", 1396 | "which-module": "^2.0.0", 1397 | "y18n": "^4.0.0", 1398 | "yargs-parser": "^13.1.1" 1399 | } 1400 | }, 1401 | "yargs-parser": { 1402 | "version": "13.1.2", 1403 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 1404 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 1405 | "dev": true, 1406 | "requires": { 1407 | "camelcase": "^5.0.0", 1408 | "decamelize": "^1.2.0" 1409 | } 1410 | } 1411 | } 1412 | }, 1413 | "locate-path": { 1414 | "version": "3.0.0", 1415 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 1416 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 1417 | "dev": true, 1418 | "requires": { 1419 | "p-locate": "^3.0.0", 1420 | "path-exists": "^3.0.0" 1421 | } 1422 | }, 1423 | "lodash": { 1424 | "version": "4.17.21", 1425 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1426 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 1427 | "dev": true 1428 | }, 1429 | "lodash.isfinite": { 1430 | "version": "3.3.2", 1431 | "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", 1432 | "integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=", 1433 | "dev": true 1434 | }, 1435 | "map-age-cleaner": { 1436 | "version": "0.1.3", 1437 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", 1438 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", 1439 | "dev": true, 1440 | "requires": { 1441 | "p-defer": "^1.0.0" 1442 | } 1443 | }, 1444 | "mem": { 1445 | "version": "4.3.0", 1446 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", 1447 | "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", 1448 | "dev": true, 1449 | "requires": { 1450 | "map-age-cleaner": "^0.1.1", 1451 | "mimic-fn": "^2.0.0", 1452 | "p-is-promise": "^2.0.0" 1453 | } 1454 | }, 1455 | "micromatch": { 1456 | "version": "4.0.2", 1457 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", 1458 | "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", 1459 | "dev": true, 1460 | "requires": { 1461 | "braces": "^3.0.1", 1462 | "picomatch": "^2.0.5" 1463 | } 1464 | }, 1465 | "mime": { 1466 | "version": "1.4.1", 1467 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 1468 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", 1469 | "dev": true 1470 | }, 1471 | "mime-db": { 1472 | "version": "1.44.0", 1473 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 1474 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", 1475 | "dev": true 1476 | }, 1477 | "mime-types": { 1478 | "version": "2.1.27", 1479 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 1480 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 1481 | "dev": true, 1482 | "requires": { 1483 | "mime-db": "1.44.0" 1484 | } 1485 | }, 1486 | "mimic-fn": { 1487 | "version": "2.1.0", 1488 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1489 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1490 | "dev": true 1491 | }, 1492 | "minimatch": { 1493 | "version": "3.0.4", 1494 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1495 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1496 | "dev": true, 1497 | "requires": { 1498 | "brace-expansion": "^1.1.7" 1499 | } 1500 | }, 1501 | "mitt": { 1502 | "version": "1.2.0", 1503 | "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", 1504 | "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", 1505 | "dev": true 1506 | }, 1507 | "moment": { 1508 | "version": "2.29.1", 1509 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 1510 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", 1511 | "dev": true 1512 | }, 1513 | "ms": { 1514 | "version": "2.0.0", 1515 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1516 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1517 | "dev": true 1518 | }, 1519 | "negotiator": { 1520 | "version": "0.6.2", 1521 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1522 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 1523 | "dev": true 1524 | }, 1525 | "nice-try": { 1526 | "version": "1.0.5", 1527 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1528 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 1529 | "dev": true 1530 | }, 1531 | "normalize-package-data": { 1532 | "version": "2.5.0", 1533 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 1534 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 1535 | "dev": true, 1536 | "requires": { 1537 | "hosted-git-info": "^2.1.4", 1538 | "resolve": "^1.10.0", 1539 | "semver": "2 || 3 || 4 || 5", 1540 | "validate-npm-package-license": "^3.0.1" 1541 | } 1542 | }, 1543 | "normalize-path": { 1544 | "version": "3.0.0", 1545 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1546 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1547 | "dev": true 1548 | }, 1549 | "npm-run-path": { 1550 | "version": "2.0.2", 1551 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 1552 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 1553 | "dev": true, 1554 | "requires": { 1555 | "path-key": "^2.0.0" 1556 | } 1557 | }, 1558 | "number-is-nan": { 1559 | "version": "1.0.1", 1560 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1561 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 1562 | "dev": true 1563 | }, 1564 | "object-component": { 1565 | "version": "0.0.3", 1566 | "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", 1567 | "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", 1568 | "dev": true 1569 | }, 1570 | "on-finished": { 1571 | "version": "2.3.0", 1572 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1573 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1574 | "dev": true, 1575 | "requires": { 1576 | "ee-first": "1.1.1" 1577 | } 1578 | }, 1579 | "once": { 1580 | "version": "1.4.0", 1581 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1582 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1583 | "dev": true, 1584 | "requires": { 1585 | "wrappy": "1" 1586 | } 1587 | }, 1588 | "openurl": { 1589 | "version": "1.1.1", 1590 | "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", 1591 | "integrity": "sha1-OHW0sO96UsFW8NtB1GCduw+Us4c=", 1592 | "dev": true 1593 | }, 1594 | "opn": { 1595 | "version": "5.3.0", 1596 | "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", 1597 | "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", 1598 | "dev": true, 1599 | "requires": { 1600 | "is-wsl": "^1.1.0" 1601 | } 1602 | }, 1603 | "os-locale": { 1604 | "version": "3.1.0", 1605 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", 1606 | "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", 1607 | "dev": true, 1608 | "requires": { 1609 | "execa": "^1.0.0", 1610 | "lcid": "^2.0.0", 1611 | "mem": "^4.0.0" 1612 | } 1613 | }, 1614 | "p-defer": { 1615 | "version": "1.0.0", 1616 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", 1617 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", 1618 | "dev": true 1619 | }, 1620 | "p-finally": { 1621 | "version": "1.0.0", 1622 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 1623 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", 1624 | "dev": true 1625 | }, 1626 | "p-is-promise": { 1627 | "version": "2.1.0", 1628 | "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", 1629 | "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", 1630 | "dev": true 1631 | }, 1632 | "p-limit": { 1633 | "version": "2.3.0", 1634 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1635 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1636 | "dev": true, 1637 | "requires": { 1638 | "p-try": "^2.0.0" 1639 | } 1640 | }, 1641 | "p-locate": { 1642 | "version": "3.0.0", 1643 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 1644 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 1645 | "dev": true, 1646 | "requires": { 1647 | "p-limit": "^2.0.0" 1648 | } 1649 | }, 1650 | "p-try": { 1651 | "version": "2.2.0", 1652 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1653 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1654 | "dev": true 1655 | }, 1656 | "parse-json": { 1657 | "version": "4.0.0", 1658 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", 1659 | "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", 1660 | "dev": true, 1661 | "requires": { 1662 | "error-ex": "^1.3.1", 1663 | "json-parse-better-errors": "^1.0.1" 1664 | } 1665 | }, 1666 | "parseqs": { 1667 | "version": "0.0.6", 1668 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", 1669 | "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", 1670 | "dev": true 1671 | }, 1672 | "parseuri": { 1673 | "version": "0.0.6", 1674 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", 1675 | "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", 1676 | "dev": true 1677 | }, 1678 | "parseurl": { 1679 | "version": "1.3.3", 1680 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1681 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1682 | "dev": true 1683 | }, 1684 | "path-exists": { 1685 | "version": "3.0.0", 1686 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 1687 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 1688 | "dev": true 1689 | }, 1690 | "path-key": { 1691 | "version": "2.0.1", 1692 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1693 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1694 | "dev": true 1695 | }, 1696 | "path-parse": { 1697 | "version": "1.0.6", 1698 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 1699 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 1700 | "dev": true 1701 | }, 1702 | "picomatch": { 1703 | "version": "2.2.2", 1704 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 1705 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", 1706 | "dev": true 1707 | }, 1708 | "pify": { 1709 | "version": "3.0.0", 1710 | "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", 1711 | "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", 1712 | "dev": true 1713 | }, 1714 | "portscanner": { 1715 | "version": "2.1.1", 1716 | "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", 1717 | "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=", 1718 | "dev": true, 1719 | "requires": { 1720 | "async": "1.5.2", 1721 | "is-number-like": "^1.0.3" 1722 | } 1723 | }, 1724 | "pump": { 1725 | "version": "3.0.0", 1726 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1727 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1728 | "dev": true, 1729 | "requires": { 1730 | "end-of-stream": "^1.1.0", 1731 | "once": "^1.3.1" 1732 | } 1733 | }, 1734 | "qs": { 1735 | "version": "6.2.3", 1736 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", 1737 | "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=", 1738 | "dev": true 1739 | }, 1740 | "range-parser": { 1741 | "version": "1.2.1", 1742 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1743 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1744 | "dev": true 1745 | }, 1746 | "raw-body": { 1747 | "version": "2.4.1", 1748 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", 1749 | "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", 1750 | "dev": true, 1751 | "requires": { 1752 | "bytes": "3.1.0", 1753 | "http-errors": "1.7.3", 1754 | "iconv-lite": "0.4.24", 1755 | "unpipe": "1.0.0" 1756 | } 1757 | }, 1758 | "read-pkg": { 1759 | "version": "4.0.1", 1760 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", 1761 | "integrity": "sha1-ljYlN48+HE1IyFhytabsfV0JMjc=", 1762 | "dev": true, 1763 | "requires": { 1764 | "normalize-package-data": "^2.3.2", 1765 | "parse-json": "^4.0.0", 1766 | "pify": "^3.0.0" 1767 | } 1768 | }, 1769 | "readdirp": { 1770 | "version": "3.5.0", 1771 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", 1772 | "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", 1773 | "dev": true, 1774 | "requires": { 1775 | "picomatch": "^2.2.1" 1776 | } 1777 | }, 1778 | "require-directory": { 1779 | "version": "2.1.1", 1780 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1781 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 1782 | "dev": true 1783 | }, 1784 | "require-main-filename": { 1785 | "version": "1.0.1", 1786 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 1787 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", 1788 | "dev": true 1789 | }, 1790 | "requires-port": { 1791 | "version": "1.0.0", 1792 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1793 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", 1794 | "dev": true 1795 | }, 1796 | "resolve": { 1797 | "version": "1.18.1", 1798 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", 1799 | "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", 1800 | "dev": true, 1801 | "requires": { 1802 | "is-core-module": "^2.0.0", 1803 | "path-parse": "^1.0.6" 1804 | } 1805 | }, 1806 | "resp-modifier": { 1807 | "version": "6.0.2", 1808 | "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", 1809 | "integrity": "sha1-sSTeXE+6/LpUH0j/pzlw9KpFa08=", 1810 | "dev": true, 1811 | "requires": { 1812 | "debug": "^2.2.0", 1813 | "minimatch": "^3.0.2" 1814 | }, 1815 | "dependencies": { 1816 | "debug": { 1817 | "version": "2.6.9", 1818 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1819 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1820 | "dev": true, 1821 | "requires": { 1822 | "ms": "2.0.0" 1823 | } 1824 | } 1825 | } 1826 | }, 1827 | "rx": { 1828 | "version": "4.1.0", 1829 | "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", 1830 | "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", 1831 | "dev": true 1832 | }, 1833 | "rxjs": { 1834 | "version": "6.2.2", 1835 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", 1836 | "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", 1837 | "dev": true, 1838 | "requires": { 1839 | "tslib": "^1.9.0" 1840 | } 1841 | }, 1842 | "safe-buffer": { 1843 | "version": "5.1.2", 1844 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1845 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1846 | "dev": true 1847 | }, 1848 | "safer-buffer": { 1849 | "version": "2.1.2", 1850 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1851 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1852 | "dev": true 1853 | }, 1854 | "semver": { 1855 | "version": "5.7.1", 1856 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1857 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 1858 | "dev": true 1859 | }, 1860 | "send": { 1861 | "version": "0.16.2", 1862 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 1863 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 1864 | "dev": true, 1865 | "requires": { 1866 | "debug": "2.6.9", 1867 | "depd": "~1.1.2", 1868 | "destroy": "~1.0.4", 1869 | "encodeurl": "~1.0.2", 1870 | "escape-html": "~1.0.3", 1871 | "etag": "~1.8.1", 1872 | "fresh": "0.5.2", 1873 | "http-errors": "~1.6.2", 1874 | "mime": "1.4.1", 1875 | "ms": "2.0.0", 1876 | "on-finished": "~2.3.0", 1877 | "range-parser": "~1.2.0", 1878 | "statuses": "~1.4.0" 1879 | }, 1880 | "dependencies": { 1881 | "debug": { 1882 | "version": "2.6.9", 1883 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1884 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1885 | "dev": true, 1886 | "requires": { 1887 | "ms": "2.0.0" 1888 | } 1889 | }, 1890 | "http-errors": { 1891 | "version": "1.6.3", 1892 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 1893 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 1894 | "dev": true, 1895 | "requires": { 1896 | "depd": "~1.1.2", 1897 | "inherits": "2.0.3", 1898 | "setprototypeof": "1.1.0", 1899 | "statuses": ">= 1.4.0 < 2" 1900 | } 1901 | }, 1902 | "inherits": { 1903 | "version": "2.0.3", 1904 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1905 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 1906 | "dev": true 1907 | }, 1908 | "setprototypeof": { 1909 | "version": "1.1.0", 1910 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 1911 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", 1912 | "dev": true 1913 | }, 1914 | "statuses": { 1915 | "version": "1.4.0", 1916 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 1917 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", 1918 | "dev": true 1919 | } 1920 | } 1921 | }, 1922 | "serve-index": { 1923 | "version": "1.9.1", 1924 | "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", 1925 | "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", 1926 | "dev": true, 1927 | "requires": { 1928 | "accepts": "~1.3.4", 1929 | "batch": "0.6.1", 1930 | "debug": "2.6.9", 1931 | "escape-html": "~1.0.3", 1932 | "http-errors": "~1.6.2", 1933 | "mime-types": "~2.1.17", 1934 | "parseurl": "~1.3.2" 1935 | }, 1936 | "dependencies": { 1937 | "debug": { 1938 | "version": "2.6.9", 1939 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1940 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1941 | "dev": true, 1942 | "requires": { 1943 | "ms": "2.0.0" 1944 | } 1945 | }, 1946 | "http-errors": { 1947 | "version": "1.6.3", 1948 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 1949 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 1950 | "dev": true, 1951 | "requires": { 1952 | "depd": "~1.1.2", 1953 | "inherits": "2.0.3", 1954 | "setprototypeof": "1.1.0", 1955 | "statuses": ">= 1.4.0 < 2" 1956 | } 1957 | }, 1958 | "inherits": { 1959 | "version": "2.0.3", 1960 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1961 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 1962 | "dev": true 1963 | }, 1964 | "setprototypeof": { 1965 | "version": "1.1.0", 1966 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 1967 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", 1968 | "dev": true 1969 | }, 1970 | "statuses": { 1971 | "version": "1.5.0", 1972 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1973 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1974 | "dev": true 1975 | } 1976 | } 1977 | }, 1978 | "serve-static": { 1979 | "version": "1.13.2", 1980 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 1981 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 1982 | "dev": true, 1983 | "requires": { 1984 | "encodeurl": "~1.0.2", 1985 | "escape-html": "~1.0.3", 1986 | "parseurl": "~1.3.2", 1987 | "send": "0.16.2" 1988 | } 1989 | }, 1990 | "server-destroy": { 1991 | "version": "1.0.1", 1992 | "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", 1993 | "integrity": "sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0=", 1994 | "dev": true 1995 | }, 1996 | "set-blocking": { 1997 | "version": "2.0.0", 1998 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1999 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 2000 | "dev": true 2001 | }, 2002 | "setprototypeof": { 2003 | "version": "1.1.1", 2004 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 2005 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", 2006 | "dev": true 2007 | }, 2008 | "shebang-command": { 2009 | "version": "1.2.0", 2010 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 2011 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 2012 | "dev": true, 2013 | "requires": { 2014 | "shebang-regex": "^1.0.0" 2015 | } 2016 | }, 2017 | "shebang-regex": { 2018 | "version": "1.0.0", 2019 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 2020 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 2021 | "dev": true 2022 | }, 2023 | "signal-exit": { 2024 | "version": "3.0.3", 2025 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 2026 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", 2027 | "dev": true 2028 | }, 2029 | "socket.io": { 2030 | "version": "2.1.1", 2031 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", 2032 | "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", 2033 | "dev": true, 2034 | "requires": { 2035 | "debug": "~3.1.0", 2036 | "engine.io": "~3.2.0", 2037 | "has-binary2": "~1.0.2", 2038 | "socket.io-adapter": "~1.1.0", 2039 | "socket.io-client": "2.1.1", 2040 | "socket.io-parser": "~3.2.0" 2041 | }, 2042 | "dependencies": { 2043 | "base64-arraybuffer": { 2044 | "version": "0.1.5", 2045 | "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", 2046 | "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", 2047 | "dev": true 2048 | }, 2049 | "component-emitter": { 2050 | "version": "1.2.1", 2051 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 2052 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 2053 | "dev": true 2054 | }, 2055 | "engine.io-client": { 2056 | "version": "3.2.1", 2057 | "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", 2058 | "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", 2059 | "dev": true, 2060 | "requires": { 2061 | "component-emitter": "1.2.1", 2062 | "component-inherit": "0.0.3", 2063 | "debug": "~3.1.0", 2064 | "engine.io-parser": "~2.1.1", 2065 | "has-cors": "1.1.0", 2066 | "indexof": "0.0.1", 2067 | "parseqs": "0.0.5", 2068 | "parseuri": "0.0.5", 2069 | "ws": "~3.3.1", 2070 | "xmlhttprequest-ssl": "~1.5.4", 2071 | "yeast": "0.1.2" 2072 | } 2073 | }, 2074 | "engine.io-parser": { 2075 | "version": "2.1.3", 2076 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", 2077 | "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", 2078 | "dev": true, 2079 | "requires": { 2080 | "after": "0.8.2", 2081 | "arraybuffer.slice": "~0.0.7", 2082 | "base64-arraybuffer": "0.1.5", 2083 | "blob": "0.0.5", 2084 | "has-binary2": "~1.0.2" 2085 | } 2086 | }, 2087 | "parseqs": { 2088 | "version": "0.0.5", 2089 | "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", 2090 | "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", 2091 | "dev": true, 2092 | "requires": { 2093 | "better-assert": "~1.0.0" 2094 | } 2095 | }, 2096 | "parseuri": { 2097 | "version": "0.0.5", 2098 | "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", 2099 | "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", 2100 | "dev": true, 2101 | "requires": { 2102 | "better-assert": "~1.0.0" 2103 | } 2104 | }, 2105 | "socket.io-client": { 2106 | "version": "2.1.1", 2107 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", 2108 | "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", 2109 | "dev": true, 2110 | "requires": { 2111 | "backo2": "1.0.2", 2112 | "base64-arraybuffer": "0.1.5", 2113 | "component-bind": "1.0.0", 2114 | "component-emitter": "1.2.1", 2115 | "debug": "~3.1.0", 2116 | "engine.io-client": "~3.2.0", 2117 | "has-binary2": "~1.0.2", 2118 | "has-cors": "1.1.0", 2119 | "indexof": "0.0.1", 2120 | "object-component": "0.0.3", 2121 | "parseqs": "0.0.5", 2122 | "parseuri": "0.0.5", 2123 | "socket.io-parser": "~3.2.0", 2124 | "to-array": "0.1.4" 2125 | } 2126 | }, 2127 | "socket.io-parser": { 2128 | "version": "3.2.0", 2129 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", 2130 | "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", 2131 | "dev": true, 2132 | "requires": { 2133 | "component-emitter": "1.2.1", 2134 | "debug": "~3.1.0", 2135 | "isarray": "2.0.1" 2136 | } 2137 | }, 2138 | "ws": { 2139 | "version": "3.3.3", 2140 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 2141 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 2142 | "dev": true, 2143 | "requires": { 2144 | "async-limiter": "~1.0.0", 2145 | "safe-buffer": "~5.1.0", 2146 | "ultron": "~1.1.0" 2147 | } 2148 | } 2149 | } 2150 | }, 2151 | "socket.io-adapter": { 2152 | "version": "1.1.2", 2153 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", 2154 | "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", 2155 | "dev": true 2156 | }, 2157 | "socket.io-client": { 2158 | "version": "2.3.1", 2159 | "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.1.tgz", 2160 | "integrity": "sha512-YXmXn3pA8abPOY//JtYxou95Ihvzmg8U6kQyolArkIyLd0pgVhrfor/iMsox8cn07WCOOvvuJ6XKegzIucPutQ==", 2161 | "dev": true, 2162 | "requires": { 2163 | "backo2": "1.0.2", 2164 | "component-bind": "1.0.0", 2165 | "component-emitter": "~1.3.0", 2166 | "debug": "~3.1.0", 2167 | "engine.io-client": "~3.4.0", 2168 | "has-binary2": "~1.0.2", 2169 | "indexof": "0.0.1", 2170 | "parseqs": "0.0.6", 2171 | "parseuri": "0.0.6", 2172 | "socket.io-parser": "~3.3.0", 2173 | "to-array": "0.1.4" 2174 | } 2175 | }, 2176 | "socket.io-parser": { 2177 | "version": "3.3.1", 2178 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz", 2179 | "integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==", 2180 | "dev": true, 2181 | "requires": { 2182 | "component-emitter": "~1.3.0", 2183 | "debug": "~3.1.0", 2184 | "isarray": "2.0.1" 2185 | } 2186 | }, 2187 | "spawn-command": { 2188 | "version": "0.0.2-1", 2189 | "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", 2190 | "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", 2191 | "dev": true 2192 | }, 2193 | "spdx-correct": { 2194 | "version": "3.1.1", 2195 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", 2196 | "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", 2197 | "dev": true, 2198 | "requires": { 2199 | "spdx-expression-parse": "^3.0.0", 2200 | "spdx-license-ids": "^3.0.0" 2201 | } 2202 | }, 2203 | "spdx-exceptions": { 2204 | "version": "2.3.0", 2205 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", 2206 | "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", 2207 | "dev": true 2208 | }, 2209 | "spdx-expression-parse": { 2210 | "version": "3.0.1", 2211 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", 2212 | "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", 2213 | "dev": true, 2214 | "requires": { 2215 | "spdx-exceptions": "^2.1.0", 2216 | "spdx-license-ids": "^3.0.0" 2217 | } 2218 | }, 2219 | "spdx-license-ids": { 2220 | "version": "3.0.6", 2221 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", 2222 | "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", 2223 | "dev": true 2224 | }, 2225 | "statuses": { 2226 | "version": "1.3.1", 2227 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 2228 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", 2229 | "dev": true 2230 | }, 2231 | "stream-throttle": { 2232 | "version": "0.1.3", 2233 | "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", 2234 | "integrity": "sha1-rdV8jXzHOoFjDTHNVdOWHPr7qcM=", 2235 | "dev": true, 2236 | "requires": { 2237 | "commander": "^2.2.0", 2238 | "limiter": "^1.0.5" 2239 | } 2240 | }, 2241 | "string-width": { 2242 | "version": "2.1.1", 2243 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 2244 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 2245 | "dev": true, 2246 | "requires": { 2247 | "is-fullwidth-code-point": "^2.0.0", 2248 | "strip-ansi": "^4.0.0" 2249 | } 2250 | }, 2251 | "strip-ansi": { 2252 | "version": "4.0.0", 2253 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 2254 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 2255 | "dev": true, 2256 | "requires": { 2257 | "ansi-regex": "^3.0.0" 2258 | } 2259 | }, 2260 | "strip-eof": { 2261 | "version": "1.0.0", 2262 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 2263 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", 2264 | "dev": true 2265 | }, 2266 | "supports-color": { 2267 | "version": "4.5.0", 2268 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", 2269 | "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", 2270 | "dev": true, 2271 | "requires": { 2272 | "has-flag": "^2.0.0" 2273 | }, 2274 | "dependencies": { 2275 | "has-flag": { 2276 | "version": "2.0.0", 2277 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 2278 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 2279 | "dev": true 2280 | } 2281 | } 2282 | }, 2283 | "symbol-observable": { 2284 | "version": "1.0.1", 2285 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", 2286 | "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", 2287 | "dev": true 2288 | }, 2289 | "tfunk": { 2290 | "version": "4.0.0", 2291 | "resolved": "https://registry.npmjs.org/tfunk/-/tfunk-4.0.0.tgz", 2292 | "integrity": "sha512-eJQ0dGfDIzWNiFNYFVjJ+Ezl/GmwHaFTBTjrtqNPW0S7cuVDBrZrmzUz6VkMeCR4DZFqhd4YtLwsw3i2wYHswQ==", 2293 | "dev": true, 2294 | "requires": { 2295 | "chalk": "^1.1.3", 2296 | "dlv": "^1.1.3" 2297 | }, 2298 | "dependencies": { 2299 | "ansi-regex": { 2300 | "version": "2.1.1", 2301 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 2302 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 2303 | "dev": true 2304 | }, 2305 | "ansi-styles": { 2306 | "version": "2.2.1", 2307 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 2308 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 2309 | "dev": true 2310 | }, 2311 | "chalk": { 2312 | "version": "1.1.3", 2313 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 2314 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 2315 | "dev": true, 2316 | "requires": { 2317 | "ansi-styles": "^2.2.1", 2318 | "escape-string-regexp": "^1.0.2", 2319 | "has-ansi": "^2.0.0", 2320 | "strip-ansi": "^3.0.0", 2321 | "supports-color": "^2.0.0" 2322 | } 2323 | }, 2324 | "strip-ansi": { 2325 | "version": "3.0.1", 2326 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2327 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2328 | "dev": true, 2329 | "requires": { 2330 | "ansi-regex": "^2.0.0" 2331 | } 2332 | }, 2333 | "supports-color": { 2334 | "version": "2.0.0", 2335 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 2336 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 2337 | "dev": true 2338 | } 2339 | } 2340 | }, 2341 | "to-array": { 2342 | "version": "0.1.4", 2343 | "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", 2344 | "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", 2345 | "dev": true 2346 | }, 2347 | "to-regex-range": { 2348 | "version": "5.0.1", 2349 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2350 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2351 | "dev": true, 2352 | "requires": { 2353 | "is-number": "^7.0.0" 2354 | } 2355 | }, 2356 | "toidentifier": { 2357 | "version": "1.0.0", 2358 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 2359 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 2360 | "dev": true 2361 | }, 2362 | "tree-kill": { 2363 | "version": "1.2.2", 2364 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 2365 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 2366 | "dev": true 2367 | }, 2368 | "tslib": { 2369 | "version": "1.14.1", 2370 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 2371 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 2372 | "dev": true 2373 | }, 2374 | "typescript": { 2375 | "version": "4.0.5", 2376 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", 2377 | "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", 2378 | "dev": true 2379 | }, 2380 | "ua-parser-js": { 2381 | "version": "0.7.28", 2382 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", 2383 | "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", 2384 | "dev": true 2385 | }, 2386 | "ultron": { 2387 | "version": "1.1.1", 2388 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 2389 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", 2390 | "dev": true 2391 | }, 2392 | "universalify": { 2393 | "version": "0.1.2", 2394 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 2395 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 2396 | "dev": true 2397 | }, 2398 | "unpipe": { 2399 | "version": "1.0.0", 2400 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2401 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 2402 | "dev": true 2403 | }, 2404 | "utils-merge": { 2405 | "version": "1.0.1", 2406 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2407 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 2408 | "dev": true 2409 | }, 2410 | "validate-npm-package-license": { 2411 | "version": "3.0.4", 2412 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 2413 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 2414 | "dev": true, 2415 | "requires": { 2416 | "spdx-correct": "^3.0.0", 2417 | "spdx-expression-parse": "^3.0.0" 2418 | } 2419 | }, 2420 | "which": { 2421 | "version": "1.3.1", 2422 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 2423 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 2424 | "dev": true, 2425 | "requires": { 2426 | "isexe": "^2.0.0" 2427 | } 2428 | }, 2429 | "which-module": { 2430 | "version": "2.0.0", 2431 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 2432 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 2433 | "dev": true 2434 | }, 2435 | "window-size": { 2436 | "version": "0.1.4", 2437 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", 2438 | "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", 2439 | "dev": true 2440 | }, 2441 | "wrap-ansi": { 2442 | "version": "2.1.0", 2443 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 2444 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 2445 | "dev": true, 2446 | "requires": { 2447 | "string-width": "^1.0.1", 2448 | "strip-ansi": "^3.0.1" 2449 | }, 2450 | "dependencies": { 2451 | "ansi-regex": { 2452 | "version": "2.1.1", 2453 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 2454 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 2455 | "dev": true 2456 | }, 2457 | "is-fullwidth-code-point": { 2458 | "version": "1.0.0", 2459 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 2460 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 2461 | "dev": true, 2462 | "requires": { 2463 | "number-is-nan": "^1.0.0" 2464 | } 2465 | }, 2466 | "string-width": { 2467 | "version": "1.0.2", 2468 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2469 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2470 | "dev": true, 2471 | "requires": { 2472 | "code-point-at": "^1.0.0", 2473 | "is-fullwidth-code-point": "^1.0.0", 2474 | "strip-ansi": "^3.0.0" 2475 | } 2476 | }, 2477 | "strip-ansi": { 2478 | "version": "3.0.1", 2479 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2480 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2481 | "dev": true, 2482 | "requires": { 2483 | "ansi-regex": "^2.0.0" 2484 | } 2485 | } 2486 | } 2487 | }, 2488 | "wrappy": { 2489 | "version": "1.0.2", 2490 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2491 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 2492 | "dev": true 2493 | }, 2494 | "ws": { 2495 | "version": "6.1.4", 2496 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", 2497 | "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", 2498 | "dev": true, 2499 | "requires": { 2500 | "async-limiter": "~1.0.0" 2501 | } 2502 | }, 2503 | "xmlhttprequest-ssl": { 2504 | "version": "1.5.5", 2505 | "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", 2506 | "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", 2507 | "dev": true 2508 | }, 2509 | "y18n": { 2510 | "version": "4.0.1", 2511 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", 2512 | "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", 2513 | "dev": true 2514 | }, 2515 | "yargs": { 2516 | "version": "12.0.5", 2517 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", 2518 | "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", 2519 | "dev": true, 2520 | "requires": { 2521 | "cliui": "^4.0.0", 2522 | "decamelize": "^1.2.0", 2523 | "find-up": "^3.0.0", 2524 | "get-caller-file": "^1.0.1", 2525 | "os-locale": "^3.0.0", 2526 | "require-directory": "^2.1.1", 2527 | "require-main-filename": "^1.0.1", 2528 | "set-blocking": "^2.0.0", 2529 | "string-width": "^2.0.0", 2530 | "which-module": "^2.0.0", 2531 | "y18n": "^3.2.1 || ^4.0.0", 2532 | "yargs-parser": "^11.1.1" 2533 | } 2534 | }, 2535 | "yargs-parser": { 2536 | "version": "11.1.1", 2537 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", 2538 | "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", 2539 | "dev": true, 2540 | "requires": { 2541 | "camelcase": "^5.0.0", 2542 | "decamelize": "^1.2.0" 2543 | } 2544 | }, 2545 | "yeast": { 2546 | "version": "0.1.2", 2547 | "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", 2548 | "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", 2549 | "dev": true 2550 | } 2551 | } 2552 | } 2553 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gtr-cof", 3 | "version": "1.0.0", 4 | "description": "gtr-cof", 5 | "scripts": { 6 | "lite": "lite-server --baseDir=\"docs\" --port 10001", 7 | "start": "npm run lite", 8 | "dev": "concurrently --raw --kill-others 'npm start' 'tsc -w'" 9 | }, 10 | "author": "Mike Hadlow", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "@types/d3": "^3.0.0", 14 | "@types/webmidi": "^2.0.2", 15 | "concurrently": "4.0.1", 16 | "d3": "^3.0.0", 17 | "lite-server": "^1.3.1", 18 | "typescript": "^4.0.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/chord-interval-module.ts: -------------------------------------------------------------------------------- 1 | namespace chordInterval { 2 | 3 | let buttons: d3.Selection; 4 | let toggle: number = 0; 5 | 6 | export function init(): void { 7 | 8 | let radius = 10; 9 | let pad = 2; 10 | 11 | let svg = d3.select("#modes"); 12 | let intervals = svg 13 | .append("g") 14 | .attr("transform", "translate(0, 240)"); 15 | 16 | let gs = intervals.selectAll("g") 17 | .data([0,1,2,3,4,5,6], function (i) { return i.toString(); }) 18 | .enter() 19 | .append("g") 20 | .attr("transform", function (d, i) { return "translate(" + (i * (radius * 2 + pad) + pad) + ", 0)"; }); 21 | 22 | buttons = gs 23 | .append("circle") 24 | .attr("cx", radius) 25 | .attr("cy", radius) 26 | .attr("r", radius) 27 | .attr("strokeWidth", 2) 28 | .attr("class", "mode-button") 29 | .on("click", onClick); 30 | 31 | gs 32 | .append("text") 33 | .attr("x", radius) 34 | .attr("y", radius + 5) 35 | .attr("text-anchor", "middle") 36 | .text(function (x) { return x + 1; }); 37 | 38 | events.chordIntervalChange.subscribe(update); 39 | } 40 | 41 | function onClick(x:number) { 42 | let updatedToggle = toggle ^ (2**x); 43 | let chordIntervals = [0,1,2,3,4,5,6].filter(x => (2**x & updatedToggle) === 2**x); 44 | events.chordIntervalChange.publish({ chordIntervals: chordIntervals }); 45 | } 46 | 47 | export function update(event: events.ChordIntervalChangeEvent): void { 48 | toggle = 0; 49 | event.chordIntervals.forEach(x => toggle = toggle + 2**x); 50 | buttons 51 | .data(event.chordIntervals, function (m) { return m.toString(); }) 52 | .attr("class", "mode-button mode-button-selected") 53 | .exit() 54 | .attr("class", "mode-button"); 55 | } 56 | 57 | interface button { 58 | readonly id: number, 59 | selected: boolean 60 | } 61 | } -------------------------------------------------------------------------------- /src/cof-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace cof { 3 | 4 | interface NoteCircleState { 5 | noteSegments: d3.Selection; 6 | noteText: d3.Selection; 7 | intervalSegments: d3.Selection; 8 | intervalText: d3.Selection; 9 | intervalNotes: d3.Selection; 10 | chordText: d3.Selection; 11 | chordSegments: d3.Selection; 12 | chordNotes: d3.Selection; 13 | } 14 | 15 | export class NoteCircle { 16 | indexer: (x: Segment) => string = (x) => x.index + ""; 17 | 18 | constructor(svg: d3.Selection, noteIndexes: number[], label: string) { 19 | let state = this.draw(svg, rotate(noteIndexes, 3), label); 20 | let setCToNoonSubscriptionIndex = -1; 21 | 22 | events.scaleChange.subscribe(scaleChnaged => { 23 | this.update(scaleChnaged, state); 24 | 25 | setCToNoonSubscriptionIndex = events.setCToNoon.resubscribe(setCToNoonEvent => { 26 | let offset = setCToNoonEvent.isC ? 3 : 0; 27 | svg.selectAll("*").remove(); 28 | state = this.draw(svg, rotate(noteIndexes, offset), label); 29 | this.update(scaleChnaged, state); 30 | }, setCToNoonSubscriptionIndex); 31 | }); 32 | } 33 | 34 | draw (svg: d3.Selection, noteIndexes: number[], label: string) : NoteCircleState { 35 | let pad = 50; 36 | 37 | let chordRadius = 240; 38 | let noteRadius = 200; 39 | let degreeRadius = 135; 40 | let innerRadius = 90; 41 | 42 | let cof = svg 43 | .append("g") 44 | .attr("transform", "translate(" + (noteRadius + pad) + ", " + (noteRadius + pad) + ")"); 45 | 46 | cof.append("text") 47 | .attr("text-anchor", "middle") 48 | .attr("x", 0) 49 | .attr("y", 0) 50 | .text(label) 51 | 52 | let segments = generateSegments(noteIndexes); 53 | 54 | let noteArc = d3.svg.arc() 55 | .innerRadius(degreeRadius) 56 | .outerRadius(noteRadius); 57 | 58 | let degreeArc = d3.svg.arc() 59 | .innerRadius(innerRadius) 60 | .outerRadius(degreeRadius); 61 | 62 | let chordArc = d3.svg.arc() 63 | .innerRadius(noteRadius) 64 | .outerRadius(chordRadius); 65 | 66 | let noteSegments = cof.append("g").selectAll("path") 67 | .data(segments, this.indexer) 68 | .enter() 69 | .append("path") 70 | .attr("d", noteArc) 71 | .attr("class", "note-segment") 72 | .on("click", handleNoteClick); 73 | 74 | let noteText = cof.append("g").selectAll("text") 75 | .data(segments) 76 | .enter() 77 | .append("text") 78 | .attr("x", function (x) { return noteArc.centroid(x)[0]; }) 79 | .attr("y", function (x) { return noteArc.centroid(x)[1] + 11; }) 80 | .text("") 81 | .attr("class", "note-segment-text"); 82 | 83 | let intervalSegments = cof.append("g").selectAll("path") 84 | .data(segments, this.indexer) 85 | .enter() 86 | .append("path") 87 | .attr("d", degreeArc) 88 | .attr("class", "interval-segment") 89 | .on("click", handleIntervalClick); 90 | 91 | let intervalNotes = cof.append("g").selectAll("circle") 92 | .data(segments, this.indexer) 93 | .enter() 94 | .append("circle") 95 | .style("pointer-events", "none") 96 | .attr("r", 25) 97 | .attr("cx", function (x) { return degreeArc.centroid(x)[0]; }) 98 | .attr("cy", function (x) { return degreeArc.centroid(x)[1]; }) 99 | .attr("class", "interval-note") 100 | 101 | let intervalText = cof.append("g").selectAll("text") 102 | .data(segments, this.indexer) 103 | .enter() 104 | .append("text") 105 | .attr("x", function (x) { return degreeArc.centroid(x)[0]; }) 106 | .attr("y", function (x) { return degreeArc.centroid(x)[1] + 8; }) 107 | .text("") 108 | .attr("class", "degree-segment-text"); 109 | 110 | let chordSegments = cof.append("g").selectAll("path") 111 | .data(segments, this.indexer) 112 | .enter() 113 | .append("path") 114 | .attr("d", chordArc) 115 | .attr("class", "chord-segment") 116 | .on("click", handleChordClick); 117 | 118 | let chordNotes = cof.append("g").selectAll("circle") 119 | .data(segments, this.indexer) 120 | .enter() 121 | .append("circle") 122 | .style("pointer-events", "none") 123 | .attr("r", 28) 124 | .attr("cx", function (x) { return chordArc.centroid(x)[0]; }) 125 | .attr("cy", function (x) { return chordArc.centroid(x)[1]; }) 126 | .attr("class", "chord-segment-note"); 127 | 128 | let chordText = cof.append("g").selectAll("text") 129 | .data(segments, this.indexer) 130 | .enter() 131 | .append("text") 132 | .attr("x", function (x) { return chordArc.centroid(x)[0]; }) 133 | .attr("y", function (x) { return chordArc.centroid(x)[1] + 8; }) 134 | .text("") 135 | .attr("class", "degree-segment-text"); 136 | 137 | return { 138 | noteSegments: noteSegments, 139 | noteText: noteText, 140 | intervalSegments: intervalSegments, 141 | intervalNotes: intervalNotes, 142 | intervalText: intervalText, 143 | chordSegments: chordSegments, 144 | chordNotes: chordNotes, 145 | chordText: chordText 146 | }; 147 | } 148 | 149 | update(scaleChnaged: events.ScaleChangedEvent, state: NoteCircleState): void { 150 | let data: Segment[] = scaleChnaged.nodes.map(node => { 151 | startAngle: 0, 152 | endAngle: 0, 153 | scaleNote: {}, 154 | index: node.scaleNote.note.index, 155 | node: node 156 | }); 157 | 158 | state.noteSegments 159 | .data(data, this.indexer) 160 | .attr("class", (d, i) => "note-segment " + 161 | (d.node.scaleNote.isScaleNote ? ((i === 0) ? "note-segment-tonic" : "note-segment-scale") : "")); 162 | 163 | state.noteText 164 | .data(data, this.indexer) 165 | .text(d => d.node.scaleNote.note.label); 166 | 167 | state.intervalSegments 168 | .data(data, this.indexer) 169 | .attr("class", d => d.node.scaleNote.isScaleNote ? "degree-segment-selected" : "interval-segment"); 170 | 171 | state.intervalText 172 | .data(data, this.indexer) 173 | .text(d => d.node.intervalName); 174 | 175 | state.intervalNotes 176 | .data(data, this.indexer) 177 | .attr("class", d => d.node.toggle ? "interval-note-selected" : "interval-note") 178 | .style("fill", d => d.node.toggle ? "#" + d.node.chordInterval.colour.toString(16) : "none") 179 | .style("stroke-width", d => d.node.midiToggle ? "20px" : "2px") 180 | .style("stroke", d => d.node.midiToggle ? "OrangeRed" : d.node.toggle ? "black" : "none"); 181 | 182 | state.chordText 183 | .data(data, this.indexer) 184 | .text(d => d.node.scaleNote.chord!.romanNumeral + ""); 185 | 186 | state.chordSegments 187 | .data(data, this.indexer) 188 | .attr("class", d => d.node.scaleNote.isScaleNote ? getChordSegmentClass(d.node.scaleNote.chord!) : "chord-segment"); 189 | 190 | state.chordNotes 191 | .data(data, this.indexer) 192 | .attr("class", d => d.node.isChordRoot ? getChordSegmentClass(d.node.scaleNote.chord!) : "chord-segment-note"); 193 | } 194 | } 195 | 196 | function getChordSegmentClass(chord: music.Chord): string { 197 | if (chord.type === music.ChordType.Diminished) return "chord-segment-dim"; 198 | if (chord.type === music.ChordType.Augmented) return "chord-segment-aug"; 199 | if (chord.type === music.ChordType.Minor) return "chord-segment-minor"; 200 | if (chord.type === music.ChordType.Major) return "chord-segment-major"; 201 | throw "Unexpected ChordType"; 202 | } 203 | 204 | function generateSegments(fifths: number[]): Segment[] { 205 | let count = fifths.length; 206 | let items: Array = []; 207 | let angle = (Math.PI * (2 / count)); 208 | for (let i: number = 0; i < count; i++) { 209 | let itemAngle = (angle * i) - (angle / 2); 210 | items.push({ 211 | startAngle: itemAngle, 212 | endAngle: itemAngle + angle, 213 | index: fifths[i], 214 | node: music.nullNode 215 | }); 216 | } 217 | return items; 218 | } 219 | 220 | function handleNoteClick(segment: Segment, i: number): void { 221 | events.tonicChange.publish({ 222 | noteSpec: replaceDoubleSharpsAndFlatsWithEquivalentNote(segment.node.scaleNote.note) 223 | }); 224 | } 225 | 226 | function replaceDoubleSharpsAndFlatsWithEquivalentNote(noteSpec: music.NoteSpec): music.NoteSpec { 227 | if(Math.abs(noteSpec.offset) > 1) { 228 | let naturalId = noteSpec.natural.id; 229 | let newNaturalId = (noteSpec.offset > 0) 230 | ? naturalId + 1 % 7 231 | : naturalId == 0 ? 6 : naturalId - 1; 232 | let newNatural = music.naturals.filter(x => x.id === newNaturalId)[0]; 233 | return music.createNoteSpec(newNatural.index, noteSpec.index) 234 | } 235 | return noteSpec; 236 | } 237 | 238 | function handleChordClick(segment: Segment, i: number): void { 239 | events.chordChange.publish({ chordIndex: segment.node.scaleNote.note.index }); 240 | } 241 | 242 | function handleIntervalClick(segment: Segment, i: number): void { 243 | events.toggle.publish({ index: segment.node.scaleNote.note.index }); 244 | } 245 | 246 | function rotate(array: number[], offset: number): number[] { 247 | let newArray: number[] = []; 248 | for(let item of array) { 249 | newArray.push((item + offset) % 12); 250 | } 251 | return newArray; 252 | } 253 | 254 | interface Segment { 255 | readonly startAngle: number; 256 | readonly endAngle: number; 257 | readonly index: number; 258 | readonly node: music.Node; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/cookie-module.ts: -------------------------------------------------------------------------------- 1 | namespace cookies { 2 | 3 | let cookieName = "gtr-cof-state-v4"; 4 | 5 | export function init(): void { 6 | events.stateChange.subscribe(bakeCookie2); 7 | } 8 | 9 | function bakeCookie2(stateChange: events.StateChangedEvent): void { 10 | let json = JSON.stringify(stateChange.state); 11 | document.cookie = cookieName + "=" + json + ";"; 12 | } 13 | 14 | export function readCookie2(): state.State { 15 | let result = document.cookie.match(new RegExp(cookieName + '=([^;]+)')); 16 | if(result != null) 17 | { 18 | let state: state.State = JSON.parse(result[1]); 19 | return state; 20 | } 21 | 22 | return null; 23 | } 24 | } -------------------------------------------------------------------------------- /src/events-module.ts: -------------------------------------------------------------------------------- 1 | namespace events { 2 | export class Bus { 3 | private listeners: Array<(x:T)=>void> = []; 4 | private name: string; 5 | 6 | // name should be the name of the exported variable in 'events' that the bus instance is assigned to. 7 | constructor(name: string) { 8 | this.name = name; 9 | } 10 | 11 | public subscribe(listener: (x:T)=>void): void { 12 | this.listeners.push(listener); 13 | } 14 | 15 | // first call should be passed index = -1 16 | public resubscribe(listener: (x:T)=>void, index: number): number { 17 | if(index === -1) { 18 | return this.listeners.push(listener) - 1; 19 | } 20 | this.listeners[index] = listener; 21 | return index; 22 | } 23 | 24 | public publish(event: T): void { 25 | //console.log("Published event: '" + this.name + "'") 26 | for (let listener of this.listeners) { 27 | listener(event); 28 | } 29 | } 30 | } 31 | 32 | function genericName(type: { new(): U; }): string { 33 | return type.constructor.toString(); 34 | } 35 | 36 | export let stateChange: Bus = new Bus("stateChange"); 37 | 38 | export interface StateChangedEvent { 39 | readonly state: state.State; 40 | } 41 | 42 | export let scaleChange: Bus = new Bus("scaleChange"); 43 | 44 | export interface ScaleChangedEvent { 45 | readonly nodes: music.Node[]; 46 | readonly mode: music.Mode; 47 | } 48 | 49 | export let tonicChange: Bus = new Bus("tonicChange"); 50 | 51 | export interface TonicChangedEvent { 52 | readonly noteSpec: music.NoteSpec; 53 | } 54 | 55 | export let modeChange: Bus = new Bus("modeChange"); 56 | 57 | export interface ModeChangedEvent { 58 | readonly mode: music.Mode; 59 | } 60 | 61 | export let chordChange: Bus = new Bus("chordChange"); 62 | 63 | export interface ChordChangeEvent { 64 | readonly chordIndex: number; 65 | } 66 | 67 | export let toggle: Bus = new Bus("toggle"); 68 | 69 | export interface ToggleEvent { 70 | readonly index: number; 71 | } 72 | 73 | export let tuningChange: Bus = new Bus("tuningChange"); 74 | 75 | export interface TuningChangedEvent { 76 | readonly index: number; 77 | } 78 | 79 | export let leftHandedChange: Bus = new Bus("leftHandedChange"); 80 | 81 | export interface LeftHandedFretboardEvent { 82 | readonly isLeftHanded: boolean; 83 | } 84 | 85 | export let flipNutChange: Bus = new Bus("flipNutChange"); 86 | 87 | export interface FlipNutEvent { 88 | readonly isNutFlipped: boolean; 89 | } 90 | 91 | export let fretboardLabelChange: Bus = new Bus("fretboardLabelChange"); 92 | 93 | export interface FretboardLabelChangeEvent { 94 | readonly labelType: FretboardLabelType; 95 | } 96 | 97 | export enum FretboardLabelType { 98 | None, 99 | NoteName, 100 | Interval 101 | } 102 | 103 | export let chordIntervalChange: Bus = new Bus("chordIntervalChange"); 104 | 105 | export interface ChordIntervalChangeEvent { 106 | readonly chordIntervals: number[]; 107 | } 108 | 109 | export let scaleFamilyChange: Bus = new Bus("scaleFamilyChange"); 110 | 111 | export interface ScaleFamilyChangeEvent { 112 | readonly scaleFamily: music.ScaleFamily; 113 | } 114 | 115 | export let midiNote: Bus = new Bus("midiNoteEvent"); 116 | 117 | export interface MidiNoteEvent { 118 | readonly toggledIndexes: number; 119 | } 120 | 121 | export let setCToNoon: Bus = new Bus("setCToNoonEvent"); 122 | 123 | export interface SetCToNoonEvent { 124 | readonly isC: boolean; 125 | } 126 | } -------------------------------------------------------------------------------- /src/gtr-cof.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | menu.init(); 4 | tonics.init(); 5 | modes.init(music.scaleFamily[0]); 6 | chordInterval.init(); 7 | let chromatic = new cof.NoteCircle(d3.select("#chromatic"), music.chromatic(), "Chromatic"); 8 | let circleOfFifths = new cof.NoteCircle(d3.select("#cof"), music.fifths(), "Circle of Fifths"); 9 | gtr.init(); 10 | tuning.init(); 11 | scaleFamily.init(); 12 | settings.init(); 13 | permalink.init(); 14 | state.init(); 15 | cookies.init(); -------------------------------------------------------------------------------- /src/gtr-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace gtr { 3 | 4 | let currentTuning: tuning.Tuning; 5 | let currentState: events.ScaleChangedEvent; 6 | let notes: d3.Selection; 7 | let noteLabels: d3.Selection; 8 | let numberOfFrets = 16; 9 | let fretboardElement: SVGGElement; 10 | let isLeftHanded: boolean = false; 11 | let isNutFlipped: boolean = false; 12 | let fretboardLabelType: events.FretboardLabelType = events.FretboardLabelType.NoteName; 13 | 14 | let stringGap = 40; 15 | let fretGap = 70; 16 | let fretWidth = 5; 17 | let noteRadius = 15; 18 | let pad = 20; 19 | 20 | function indexer(stringNote: StringNote): string { 21 | return stringNote.index + "_" + stringNote.octave; 22 | } 23 | 24 | export function init() { 25 | events.tuningChange.subscribe(handleTuningChange); 26 | events.scaleChange.subscribe(update); 27 | events.leftHandedChange.subscribe(handleLeftHandedChanged); 28 | events.flipNutChange.subscribe(handleFlipNutChanged); 29 | events.fretboardLabelChange.subscribe(handleLabelChange); 30 | } 31 | 32 | function handleTuningChange(tuningChangedEvent: events.TuningChangedEvent): void { 33 | let newTuning = tuning.tunings.find(x => x.index == tuningChangedEvent.index); 34 | updateFretboard(newTuning); 35 | } 36 | 37 | function handleLeftHandedChanged(lhEvent: events.LeftHandedFretboardEvent) { 38 | isLeftHanded = lhEvent.isLeftHanded; 39 | if(currentTuning != null) { 40 | updateFretboard(currentTuning); 41 | } 42 | } 43 | 44 | function setHandedness() 45 | { 46 | if(isLeftHanded) { 47 | fretboardElement.transform.baseVal.getItem(0).setTranslate(1200, 0); 48 | fretboardElement.transform.baseVal.getItem(1).setScale(-1, 1); 49 | noteLabels 50 | .attr("transform", (d, i) => "translate(0, 0) scale(-1, 1)") 51 | .attr("x", (d, i) => -(i * fretGap + pad + 30)) 52 | } else { 53 | fretboardElement.transform.baseVal.getItem(0).setTranslate(0, 0); 54 | fretboardElement.transform.baseVal.getItem(1).setScale(1, 1); 55 | noteLabels 56 | .attr("transform", (d, i) => "translate(0, 0) scale(1, 1)") 57 | .attr("x", (d, i) => (i * fretGap + pad + 30)) 58 | } 59 | } 60 | 61 | function handleFlipNutChanged(fnEvent: events.FlipNutEvent) { 62 | isNutFlipped = fnEvent.isNutFlipped; 63 | if(currentTuning != null) { 64 | updateFretboard(currentTuning); 65 | } 66 | } 67 | 68 | function handleLabelChange(lcEvent: events.FretboardLabelChangeEvent) { 69 | fretboardLabelType = lcEvent.labelType; 70 | setLabels(); 71 | } 72 | 73 | function setLabels() 74 | { 75 | function setNoteName(note: StringNote): string { 76 | return note.node.scaleNote.isScaleNote || note.node.toggle ? note.node.scaleNote.note.label : ""; 77 | } 78 | 79 | function setInterval(note: StringNote): string { 80 | return note.node.scaleNote.isScaleNote || note.node.toggle ? note.node.intervalName : ""; 81 | } 82 | 83 | switch (fretboardLabelType) { 84 | case events.FretboardLabelType.None: 85 | noteLabels.text(""); 86 | break; 87 | case events.FretboardLabelType.NoteName: 88 | noteLabels.text(setNoteName) 89 | break; 90 | case events.FretboardLabelType.Interval: 91 | noteLabels.text(setInterval); 92 | break; 93 | } 94 | } 95 | 96 | function updateFretboard(tuningInfo: tuning.Tuning): void { 97 | 98 | currentTuning = tuningInfo; 99 | let fretData: Array = getFretData(numberOfFrets); 100 | let dots: Array<[number, number]> = tuningInfo.dots; 101 | 102 | d3.selectAll("#gtr > *").remove(); 103 | let svg = d3.select("#gtr"); 104 | svg.append("text") 105 | .attr("class", "mode-text") 106 | .attr("x", 30) 107 | .attr("y", 11) 108 | .text(tuningInfo.tuning + " " 109 | + tuningInfo.description 110 | + (isLeftHanded ? ", Left Handed" : "") 111 | + (isNutFlipped ? ", Nut Flipped" : "")); 112 | let gtr = svg.append("g").attr("transform", "translate(0, 0) scale(1, 1)"); 113 | fretboardElement = gtr.node(); 114 | 115 | // frets 116 | gtr.append("g").selectAll("rect") 117 | .data(fretData) 118 | .enter() 119 | .append("rect") 120 | .attr("x", function (d, i) { return (i + 1) * fretGap + pad - fretWidth; }) 121 | .attr("y", pad + stringGap / 2 - fretWidth) 122 | .attr("width", fretWidth) 123 | .attr("height", stringGap * (tuningInfo.notes.length - 1) + (fretWidth * 2)) 124 | .attr("fill", function (d, i) { return i === 0 ? "black" : "none"; }) 125 | .attr("stroke", "grey") 126 | .attr("stroke-width", 1); 127 | 128 | // dots 129 | gtr.append("g").selectAll("circle") 130 | .data(dots) 131 | .enter() 132 | .append("circle") 133 | .attr("r", 10) 134 | .attr("cx", function (d) { return d[0] * fretGap + pad + 30 + (d[1] * 10); }) 135 | .attr("cy", function (d) { return (tuningInfo.notes.length) * stringGap + pad + 15; }) 136 | .attr("fill", "lightgrey") 137 | .attr("stroke", "none"); 138 | 139 | let strings = gtr.append("g").selectAll("g") 140 | .data(isNutFlipped ? tuningInfo.notes.slice() : tuningInfo.notes.slice().reverse(), (_, i) => i + "") 141 | .enter() 142 | .append("g") 143 | .attr("transform", function (d, i) { return "translate(0, " + ((i * stringGap) + pad) + ")"; }); 144 | 145 | // string lines 146 | strings 147 | .append("line") 148 | .attr("x1", pad + fretGap) 149 | .attr("y1", stringGap / 2) 150 | .attr("x2", pad + (fretGap * numberOfFrets) + 20) 151 | .attr("y2", stringGap / 2) 152 | .attr("stroke", "black") 153 | .attr("stroke-width", 2); 154 | 155 | notes = strings 156 | .selectAll("circle") 157 | .data(function (d) { return allNotesFrom(d, numberOfFrets); }, indexer) 158 | .enter() 159 | .append("circle") 160 | .attr("r", noteRadius) 161 | .attr("cy", stringGap / 2) 162 | .attr("cx", function (d, i) { return i * fretGap + pad + 30 }) 163 | .on("click", d => events.toggle.publish({ index: d.index })); 164 | 165 | noteLabels = strings 166 | .selectAll("text") 167 | .data(function (d) { return allNotesFrom(d, numberOfFrets); }, indexer) 168 | .enter() 169 | .append("text") 170 | .attr("transform", "translate(0, 0) scale(1, 1)") 171 | .attr("text-anchor", "middle") 172 | .attr("x", (d, i) => i * fretGap + pad + 30) 173 | .attr("y", (stringGap / 2) + 5) 174 | .text(""); 175 | 176 | setHandedness(); 177 | 178 | if(currentState != null) { 179 | update(currentState); 180 | } 181 | } 182 | 183 | function update(stateChange: events.ScaleChangedEvent): void { 184 | 185 | let hasToggledNotes = stateChange.nodes.some(x => x.toggle); 186 | 187 | let fill = function (d: StringNote): string { 188 | return d.node.toggle 189 | ? "white" 190 | : d.node.scaleNote.isScaleNote 191 | ? d.node.scaleNote.noteNumber === 0 192 | ? hasToggledNotes ? "white" : "yellow" 193 | : "white" 194 | : "rgba(255, 255, 255, 0.01)"; 195 | }; 196 | 197 | let stroke = function (d: StringNote): string { 198 | return d.node.midiToggle ? "OrangeRed" 199 | : d.node.toggle ? "#" + d.node.chordInterval.colour.toString(16) 200 | : hasToggledNotes ? "none" 201 | : d.node.scaleNote.isScaleNote ? "grey" : "none"; 202 | }; 203 | 204 | let strokeWidth = function (d: StringNote): number { 205 | return d.node.midiToggle ? 10 206 | : d.node.toggle ? 4 207 | : d.node.scaleNote.isScaleNote ? 2 208 | : 0; 209 | }; 210 | 211 | let data = repeatTo(stateChange.nodes, numberOfFrets); 212 | 213 | notes 214 | .data(data, indexer) 215 | .attr("fill", fill) 216 | .attr("stroke", stroke) 217 | .attr("stroke-width", strokeWidth); 218 | 219 | noteLabels.data(data, indexer) 220 | setLabels(); 221 | currentState = stateChange; 222 | } 223 | 224 | function allNotesFrom(index: number, numberOfNotes: number): Array { 225 | let items: Array = []; 226 | 227 | for (let i = 0; i < numberOfNotes; i++) { 228 | items.push({ 229 | octave: Math.floor((i + 1) / 12), 230 | index: (i + index) % 12, 231 | node: music.nullNode 232 | }); 233 | } 234 | 235 | return items; 236 | } 237 | 238 | function getFretData(numberOfFrets: number): Array { 239 | let data: Array = []; 240 | for (let i = 0; i < numberOfFrets; i++) { 241 | data.push(i); 242 | } 243 | return data; 244 | } 245 | 246 | function repeatTo(nodes: music.Node[], count: number): StringNote[] { 247 | let stringNotes: StringNote[] = []; 248 | for(let i=0; i <= Math.floor(count / 12); i++) { 249 | stringNotes = stringNotes.concat(nodes.map(x => { 250 | octave: i, 251 | index: x.scaleNote.note.index, 252 | node: x 253 | })); 254 | } 255 | return stringNotes; 256 | } 257 | 258 | interface StringNote { 259 | readonly octave: number; 260 | readonly index: number; 261 | readonly node: music.Node; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/menu-module.ts: -------------------------------------------------------------------------------- 1 | namespace menu { 2 | export function init() : void { 3 | 4 | let menuItems = document.getElementsByClassName("menu"); 5 | for(let menuItem of menuItems) { 6 | menuItem.addEventListener("click", onMenuClick) 7 | } 8 | 9 | // close open menu when document is clicked outside 10 | document.addEventListener("mouseup", (event: MouseEvent) => { 11 | let targetElement = event.target; 12 | if(targetElement.closest(".dropdown-content") === null && targetElement.closest(".menu") === null) { 13 | let contentElements = document.getElementsByClassName("dropdown-content"); 14 | for(let contentElement of contentElements) { 15 | if(contentElement.classList.contains("dropdown-content-visible")) { 16 | contentElement.classList.remove("dropdown-content-visible"); 17 | } 18 | } 19 | } 20 | }); 21 | } 22 | 23 | function onMenuClick(event: MouseEvent) : void { 24 | let menuElement = event.target; 25 | let currentContentElement = menuElement.parentElement.querySelector(".dropdown-content") 26 | 27 | let contentElements = document.getElementsByClassName("dropdown-content"); 28 | for(let contentElement of contentElements) { 29 | if(contentElement === currentContentElement) { 30 | currentContentElement.classList.toggle("dropdown-content-visible"); 31 | } 32 | else { 33 | contentElement.classList.remove("dropdown-content-visible"); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/midi-module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace midiControl { 4 | 5 | // bit flag for on/off MIDI notes 6 | let currentToggledIndexes: number = 0; 7 | 8 | export function init(): void { 9 | let nav: Navigator = window.navigator; 10 | 11 | if(!nav.requestMIDIAccess) { 12 | console.log("Browser does not support MIDI."); 13 | return; 14 | } 15 | 16 | nav.requestMIDIAccess() 17 | .then((midiAccess) => { 18 | console.log("MIDI Ready!"); 19 | for(let entry of midiAccess.inputs) { 20 | entry[1].onmidimessage = onMidiMessage; 21 | } 22 | }) 23 | .catch((error) => { 24 | console.log("Error accessing MIDI devices: " + error); 25 | }); 26 | } 27 | 28 | function onMidiMessage(midiEvent: WebMidi.MIDIMessageEvent): void { 29 | let data = midiEvent.data; 30 | if(data.length === 3) { 31 | let status = data[0]; 32 | // command is the four most significant bits of the status byte. 33 | let command = status >>> 4; 34 | //let octave = Math.trunc(data[1] / 12); 35 | // MIDI starts with C0 = 0, but guitar dashboard index 0 = A, so add three to the midi note number. 36 | let index = (data[1] + 3) % 12; 37 | if(command === 0x9) { 38 | // MIDI note on. 39 | currentToggledIndexes = currentToggledIndexes | 2**index; 40 | } 41 | if(command === 0x8) { 42 | // MIDI note off. 43 | currentToggledIndexes = currentToggledIndexes & ~(2**index); 44 | } 45 | events.midiNote.publish({ toggledIndexes: currentToggledIndexes }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/mod-module.ts: -------------------------------------------------------------------------------- 1 | namespace mod { 2 | 3 | export class Mod { 4 | 5 | size: number = 0; 6 | items: T[]; 7 | start: number = 0; 8 | 9 | constructor(items: T[]) { 10 | this.items = items; 11 | this.size = items.length; 12 | } 13 | 14 | setStart(start: number): void { 15 | this.start = start % this.size; 16 | } 17 | 18 | itemAt(index: number) : T { 19 | return this.items[(this.start + index) % this.size]; 20 | } 21 | 22 | toArray(): T[] { 23 | let newArray: T[] = []; 24 | for(let i=0; i(items: U[]): [T, U][] { 31 | let theseItems: T[] = this.toArray(); 32 | return zip(theseItems, items); 33 | } 34 | 35 | merge3(items2: U[], items3: V[]): [T, U, V][] { 36 | let theseItems: T[] = this.toArray(); 37 | return zip3(theseItems, items2, items3); 38 | } 39 | } 40 | 41 | export function zip(a:A[], b:B[]): [A,B][] { 42 | if(a.length != b.length) { 43 | throw "Cannot merge arrays of different lengths"; 44 | } 45 | return a.map((x, i) => <[A,B]>[x, b[i]]); 46 | } 47 | 48 | export function zip3(a:A[], b:B[], c:C[]): [A,B,C][] { 49 | if(a.length != b.length || a.length != c.length) { 50 | throw "Cannot merge arrays of different lengths"; 51 | } 52 | return a.map((x, i) => <[A,B,C]>[x, b[i], c[i]]); 53 | } 54 | 55 | export function diff(size: number, a: number, b: number) : number { 56 | let ax = a % size; 57 | let bx = b % size; 58 | if(ax == bx) return 0; 59 | 60 | let d1 = bx - ax; 61 | let d2 = 0; 62 | 63 | if(d1 > 0) { 64 | d2 = -((ax + size) - bx); 65 | } 66 | else { 67 | d2 = (bx + size) - ax; 68 | } 69 | return Math.abs(d1) > Math.abs(d2) ? d2 : d1; 70 | } 71 | } 72 | 73 | let modTest = new mod.Mod([0,1,2,3,4,5]); -------------------------------------------------------------------------------- /src/modes-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace modes { 3 | 4 | let buttons: d3.Selection; 5 | let modes: d3.Selection; 6 | 7 | export function init(scaleFamily: music.ScaleFamily): void { 8 | let svg = d3.select("#modes"); 9 | modes = svg 10 | .append("g") 11 | .attr("transform", "translate(0, 280)"); 12 | 13 | drawButtons(scaleFamily); 14 | 15 | events.modeChange.subscribe(update); 16 | events.scaleFamilyChange.subscribe(handleScaleFamilyChangedEvent); 17 | } 18 | 19 | function drawButtons(scaleFamily: music.ScaleFamily): void { 20 | let pad = 5; 21 | let buttonHeight = 25; 22 | 23 | modes.selectAll("g").remove(); 24 | let gs = modes.selectAll("g").data(scaleFamily.modes, index); 25 | 26 | gs 27 | .exit() 28 | .remove(); 29 | 30 | gs 31 | .enter() 32 | .append("g") 33 | .attr("transform", (d, i) => "translate(0, " + (i * (buttonHeight + pad) + pad) + ")"); 34 | 35 | buttons = gs 36 | .append("rect") 37 | .attr("x", pad) 38 | .attr("y", 0) 39 | .attr("strokeWidth", 2) 40 | .attr("width", 150) 41 | .attr("height", 25) 42 | .attr("class", "mode-button") 43 | .on("click", (d) => events.modeChange.publish({ mode: d })); 44 | 45 | gs 46 | .append("text") 47 | .attr("x", pad + 10) 48 | .attr("y", 17) 49 | .text((x) => x.name) 50 | .attr("class", "mode-text"); 51 | 52 | let defaultMode = scaleFamily.modes.find(x => x.index == scaleFamily.defaultModeIndex); 53 | highlightActiveMode(defaultMode); 54 | } 55 | 56 | function update(modeChange: events.ModeChangedEvent): void { 57 | highlightActiveMode(modeChange.mode); 58 | } 59 | 60 | function highlightActiveMode(mode: music.Mode): void { 61 | let modes: Array = [mode]; 62 | buttons 63 | .data(modes, index) 64 | .attr("class", "mode-button mode-button-selected") 65 | .exit() 66 | .attr("class", "mode-button") 67 | } 68 | 69 | function handleScaleFamilyChangedEvent(scaleFamilyChangedEvent: events.ScaleFamilyChangeEvent) { 70 | drawButtons(scaleFamilyChangedEvent.scaleFamily); 71 | } 72 | 73 | function index(mode: music.Mode): string { 74 | return mode.index.toString(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/music-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace music { 3 | 4 | export enum IntervalType { 5 | Nat, 6 | Maj, 7 | Min, 8 | Aug, 9 | Dim 10 | }; 11 | 12 | export let intervalName: {[key: string]: string} = {}; 13 | intervalName[IntervalType.Nat] = ""; 14 | intervalName[IntervalType.Maj] = "M"; 15 | intervalName[IntervalType.Min] = "m"; 16 | intervalName[IntervalType.Aug] = "A"; 17 | intervalName[IntervalType.Dim] = "d"; 18 | 19 | export interface Interval { 20 | readonly ord: number; 21 | readonly type: IntervalType; 22 | readonly colour: number; 23 | }; 24 | 25 | export let getIntervalName: (x: Interval) => string = interval => intervalName[interval.type] + (interval.ord + 1); 26 | 27 | export let intervals: mod.Mod = new mod.Mod([ 28 | [{ ord: 0, type: IntervalType.Nat, colour: 0xf44b42 }, { ord: 1, type: IntervalType.Dim, colour: 0xf44b42 }], 29 | [{ ord: 1, type: IntervalType.Min, colour: 0xf48942 }, { ord: 0, type: IntervalType.Aug, colour: 0xf48942 }], 30 | [{ ord: 1, type: IntervalType.Maj, colour: 0xf4bf42 }, { ord: 2, type: IntervalType.Dim, colour: 0xf4bf42 }], 31 | [{ ord: 2, type: IntervalType.Min, colour: 0xf4ee42 }, { ord: 1, type: IntervalType.Aug, colour: 0xf4ee42 }], 32 | [{ ord: 2, type: IntervalType.Maj, colour: 0x8cf442 }, { ord: 3, type: IntervalType.Dim, colour: 0x8cf442 }], 33 | [{ ord: 3, type: IntervalType.Nat, colour: 0x42f4bf }, { ord: 2, type: IntervalType.Aug, colour: 0x42f4bf }], 34 | [{ ord: 4, type: IntervalType.Dim, colour: 0x42d4f4 }, { ord: 3, type: IntervalType.Aug, colour: 0x42d4f4 }], 35 | [{ ord: 4, type: IntervalType.Nat, colour: 0x429ef4 }, { ord: 5, type: IntervalType.Dim, colour: 0x429ef4 }], 36 | [{ ord: 5, type: IntervalType.Min, colour: 0xe542f4 }, { ord: 4, type: IntervalType.Aug, colour: 0xe542f4 }], 37 | [{ ord: 5, type: IntervalType.Maj, colour: 0xf44289 }, { ord: 6, type: IntervalType.Dim, colour: 0xf44289 }], 38 | [{ ord: 6, type: IntervalType.Min, colour: 0xff8282 }, { ord: 5, type: IntervalType.Aug, colour: 0xff8282 }], 39 | [{ ord: 6, type: IntervalType.Maj, colour: 0xff82fc }, { ord: 0, type: IntervalType.Dim, colour: 0xff82fc }], 40 | ]); 41 | 42 | export interface ScaleFamily { 43 | readonly index: number; 44 | readonly name: string; 45 | readonly intervals: mod.Mod; 46 | readonly modes: Mode[]; 47 | readonly defaultModeIndex: number; 48 | }; 49 | 50 | export function notesInScaleFamily(scaleFamily: ScaleFamily): number { 51 | return scaleFamily.intervals.items.filter(x => x).length; 52 | } 53 | 54 | let diatonicModes: Mode[] = [ 55 | { name: 'Lydian', index: 5 }, 56 | { name: 'Major / Ionian', index: 0 }, 57 | { name: 'Mixolydian', index: 7 }, 58 | { name: 'Dorian', index: 2 }, 59 | { name: 'N Minor / Aeolian', index: 9 }, 60 | { name: 'Phrygian', index: 4 }, 61 | { name: 'Locrian', index: 11 }, 62 | ]; 63 | 64 | let harmonicMinorModes: Mode[] = [ 65 | { name: 'Lydian ♯2', index: 5 }, 66 | { name: 'Ionian ♯5', index: 0 }, 67 | { name: 'Superlocrian', index: 8 }, 68 | { name: 'Dorian ♯4', index: 2 }, 69 | { name: 'Harmonic Minor', index: 9 }, 70 | { name: 'Phrygian Dominant', index: 4 }, 71 | { name: 'Locrian ♯6', index: 11 }, 72 | ]; 73 | 74 | let jazzMinorModes: Mode[] = [ 75 | { name: 'Lydian Dominant', index: 5 }, 76 | { name: 'Jazz Minor', index: 0 }, 77 | { name: 'Mixolydian ♭6', index: 7 }, 78 | { name: 'Assyrian', index: 2 }, 79 | { name: 'Locrian ♮2', index: 9 }, 80 | { name: 'Lydian Augmented', index: 3 }, 81 | { name: 'Altered scale', index: 11 }, 82 | ]; 83 | 84 | export let scaleFamily: ScaleFamily[] = [ 85 | { index: 0, name: "diatonic", intervals: new mod.Mod([true, false, true, false, true, true, false, true, false, true, false, true]), modes: diatonicModes, defaultModeIndex: 0 }, 86 | { index: 1, name: "harmonic minor", intervals: new mod.Mod([true, false, true, false, true, true, false, false, true, true, false, true]), modes: harmonicMinorModes, defaultModeIndex: 9 }, 87 | { index: 2, name: "jazz minor", intervals: new mod.Mod([true, false, true, true, false, true, false, true, false, true, false, true]), modes: jazzMinorModes, defaultModeIndex: 0 }, 88 | { index: 3, name: "whole tone", intervals: new mod.Mod([true, false, true, false, true, false, true, false, true, false, true, false]), modes: [{ name: 'Whole Tone', index: 0}], defaultModeIndex: 0 }, 89 | { index: 4, name: "diminished", intervals: new mod.Mod([true, false, true, true, false, true, true, false, true, true, false, true]), modes: [{ name: 'Diminished', index: 0}], defaultModeIndex: 0 } 90 | ]; 91 | 92 | // root diatonic scale is major 93 | export let diatonic: mod.Mod = new mod.Mod([true, false, true, false, true, true, false, true, false, true, false, true]); 94 | export let indexList: mod.Mod = new mod.Mod([0,1,2,3,4,5,6,7,8,9,10,11]); 95 | 96 | export interface NoteSpec { 97 | readonly natural: Natural; 98 | readonly index: number; 99 | readonly offset: number; 100 | readonly label: string; 101 | } 102 | 103 | export function createNoteSpec(naturalIndex: number, index: number): NoteSpec { 104 | let natural = naturals.filter(x => x.index === naturalIndex)[0]; 105 | if(! naturals.some(x => x.index === naturalIndex)) { 106 | throw "naturalIndex is not valid: " + naturalIndex; 107 | } 108 | 109 | let offset = mod.diff(12, naturalIndex, index); 110 | if(Math.abs(offset) > 2) { 111 | throw "offset between naturalIndex: " + naturalIndex + ", and index: " + index + ", is invalid: " + offset; 112 | } 113 | 114 | let noteLabel = noteLabels.filter(x => x.offset === offset)[0]; 115 | 116 | return { 117 | natural: natural, 118 | index: index, 119 | offset: offset, 120 | label: natural.label + noteLabel.label 121 | }; 122 | } 123 | 124 | export interface Natural { 125 | id: number, // order of the number in the natural set. 126 | index: number, // number against the fixed chromatic series 127 | label: string // the natural name, e.g: 'A' 128 | } 129 | 130 | // fixed index: 131 | // 0 1 2 3 4 5 6 7 8 9 10 11 132 | // A B C D E F G 133 | export let naturals: Natural[] = [ 134 | { id: 0, index: 0, label: "A" }, 135 | { id: 1, index: 2, label: "B" }, 136 | { id: 2, index: 3, label: "C" }, 137 | { id: 3, index: 5, label: "D" }, 138 | { id: 4, index: 7, label: "E" }, 139 | { id: 5, index: 8, label: "F" }, 140 | { id: 6, index: 10, label: "G" } 141 | ]; 142 | 143 | let naturalList = new mod.Mod(naturals); 144 | 145 | interface NoteName { 146 | readonly name: string; 147 | readonly index: number; 148 | } 149 | 150 | export let noteNames: NoteName[] = [ 151 | { name: "A", index: 0 }, 152 | { name: "A♯", index: 1 }, 153 | { name: "A♭", index: 11 }, 154 | 155 | { name: "B", index: 2 }, 156 | { name: "B♯", index: 3 }, 157 | { name: "B♭", index: 1 }, 158 | 159 | { name: "C", index: 3 }, 160 | { name: "C♯", index: 4 }, 161 | { name: "C♭", index: 2 }, 162 | 163 | { name: "D", index: 5 }, 164 | { name: "D♯", index: 6 }, 165 | { name: "D♭", index: 4 }, 166 | 167 | { name: "E", index: 7 }, 168 | { name: "E♯", index: 8 }, 169 | { name: "E♭", index: 6 }, 170 | 171 | { name: "F", index: 8 }, 172 | { name: "F♯", index: 9 }, 173 | { name: "F♭", index: 7 }, 174 | 175 | { name: "G", index: 10 }, 176 | { name: "G♯", index: 11 }, 177 | { name: "G♭", index: 9 }, 178 | ]; 179 | 180 | interface NoteLabel { 181 | readonly offset: number; 182 | readonly label: string; 183 | } 184 | 185 | let noteLabels: Array = [ 186 | { offset: 0, label: '' }, 187 | { offset: 1, label: '♯' }, 188 | { offset: 2, label: 'x' }, 189 | { offset: -1, label: '♭' }, 190 | { offset: -2, label: '♭♭' }, 191 | ]; 192 | 193 | export interface Mode { 194 | readonly name: string; 195 | readonly index: number; 196 | }; 197 | 198 | export interface ScaleSpec { 199 | noteSpec: NoteSpec; 200 | mode: Mode; 201 | } 202 | 203 | export function createScaleSpec(index:number, naturalIndex:number, modeIndex:number): ScaleSpec { 204 | return { 205 | noteSpec: createNoteSpec(naturalIndex, index), 206 | mode: scaleFamily[0].modes[modeIndex] 207 | }; 208 | } 209 | 210 | export enum ChordType { Major, Minor, Diminished, Augmented }; 211 | 212 | export interface Chord { 213 | readonly romanNumeral: string; 214 | readonly type: ChordType; 215 | } 216 | 217 | export interface ScaleNote { 218 | readonly note: NoteSpec; 219 | readonly interval: Interval; 220 | readonly intervalName: string; 221 | readonly isScaleNote: boolean; 222 | readonly noteNumber: number; 223 | chord?: Chord; 224 | }; 225 | 226 | export interface Node { 227 | readonly scaleNote: ScaleNote; 228 | readonly chordInterval: Interval; 229 | readonly intervalName: string; 230 | readonly isChordRoot: boolean; 231 | readonly toggle: boolean; 232 | readonly midiToggle: boolean; 233 | } 234 | 235 | export let nullNode: Node = { 236 | scaleNote: { 237 | note: { 238 | natural: { 239 | id: 0, 240 | index: 0, 241 | label: "" 242 | }, 243 | index: 0, 244 | offset: 0, 245 | label: "" 246 | }, 247 | interval: { 248 | ord: 0, 249 | type: 0, 250 | colour: 0 251 | }, 252 | intervalName: "", 253 | isScaleNote: false, 254 | noteNumber: 0 255 | }, 256 | chordInterval: { 257 | ord: 0, 258 | type: 0, 259 | colour: 0 260 | }, 261 | intervalName: "", 262 | isChordRoot: false, 263 | toggle: false, 264 | midiToggle: false 265 | }; 266 | 267 | export function generateScaleShim( 268 | noteSpec: NoteSpec, 269 | mode: Mode, 270 | chordIndex: number, 271 | chordIntervals: number[], 272 | toggledIndexes: number, 273 | toggledMidiNotes: number, 274 | scaleFamily: ScaleFamily): Node[] { 275 | 276 | let scale = generateScale(noteSpec, mode, scaleFamily); 277 | mod.zip(scale, generateChordNumbers(scale, mode, scaleFamily.intervals)).forEach(x => x[0].chord = x[1]); 278 | if(chordIndex === -1) { 279 | return generateNodes(scale, mode, scale[0].note.index, chordIntervals, toggledIndexes, toggledMidiNotes, scaleFamily.intervals); 280 | } 281 | else { 282 | return generateNodes(scale, mode, chordIndex, chordIntervals, toggledIndexes, toggledMidiNotes, scaleFamily.intervals, true); 283 | } 284 | } 285 | 286 | export function generateScale(noteSpec: NoteSpec, mode: Mode, scaleFamily: ScaleFamily): ScaleNote[] { 287 | indexList.setStart(noteSpec.index); 288 | naturalList.setStart(noteSpec.natural.id); 289 | scaleFamily.intervals.setStart(mode.index); 290 | intervals.setStart(0); 291 | let workingSet = indexList.merge3(buildScaleCounter(scaleFamily.intervals.toArray()), intervals.toArray()); 292 | let isSevenNoteScale = notesInScaleFamily(scaleFamily) == 7; 293 | 294 | return workingSet.map(item => { 295 | let index = item[0]; 296 | let isScaleNote = item[1][0]; 297 | 298 | let noteNumber:number; 299 | let natural:Natural; 300 | let activeInterval:Interval; 301 | 302 | if(isScaleNote && isSevenNoteScale) { 303 | noteNumber = item[1][1]; 304 | natural = naturalList.itemAt(noteNumber); 305 | activeInterval = item[2].filter(x => x.ord == noteNumber)[0]; 306 | if(activeInterval == null) { 307 | activeInterval = item[2][0]; 308 | } 309 | } 310 | else { 311 | activeInterval = item[2][0]; 312 | noteNumber = isScaleNote ? item[1][1] : activeInterval.ord; 313 | natural = naturalList.itemAt(activeInterval.ord); 314 | } 315 | 316 | // console.log("index: " + index + ", isScaleNote: " + isScaleNote 317 | // + ", noteNumber: " + noteNumber + ", natural.index: " + natural.index 318 | // + ", natural.label: " + natural.label 319 | // + ", interval: " + getIntervalName(activeInterval)) 320 | 321 | return { 322 | note: createNoteSpec(natural.index, index), 323 | interval: activeInterval, 324 | intervalName: getIntervalName(activeInterval), 325 | isScaleNote: isScaleNote, 326 | noteNumber: noteNumber 327 | }; 328 | }); 329 | } 330 | 331 | // generateNodes creates an 'outer' sliding interval ring that can change with 332 | // chord selections. 333 | export function generateNodes( 334 | scaleNotes: ScaleNote[], 335 | mode:Mode, 336 | chordIndex: number, 337 | chordIntervals: number[], 338 | toggledIndexes: number, 339 | toggledMidiNotes: number, 340 | scaleFamily: mod.Mod, 341 | chordSelected: boolean = false 342 | ): Node[] { 343 | let chordIndexOffset = ((chordIndex + 12) - scaleNotes[0].note.index) % 12; 344 | intervals.setStart(12 - chordIndexOffset); 345 | scaleFamily.setStart(mode.index); 346 | let startAt = scaleNotes.filter(x => x.note.index === chordIndex)[0].noteNumber; 347 | let workingSet = intervals.merge3( 348 | scaleNotes, 349 | buildScaleCounter(scaleFamily.toArray(), startAt)); 350 | 351 | return workingSet.map(item => { 352 | let chordIntervalCandidates = item[0]; 353 | let scaleNote = item[1]; 354 | let scaleCounter = item[2]; 355 | let activeInterval = scaleNote.isScaleNote 356 | ? chordIntervalCandidates.filter(x => x.ord === scaleCounter[1])[0] 357 | : chordIntervalCandidates[0]; 358 | if(activeInterval == null) { 359 | activeInterval = chordIntervalCandidates[0]; 360 | } 361 | 362 | // if(chordSelected) { 363 | // console.log("chordIndex: " + chordIndex + 364 | // ", scaleNote.isScaleNote: " + scaleNote.isScaleNote + 365 | // ", scaleNote.notenumber: " + scaleNote.noteNumber + 366 | // ", scaleCounter: " + scaleCounter + 367 | // ", activeInterval: " + getIntervalName(activeInterval) + 368 | // ", toggle: " + calculateToggle(activeInterval, scaleNote, chordSelected, toggledIndexes, chordIntervals)); 369 | // } 370 | 371 | return { 372 | scaleNote: scaleNote, 373 | chordInterval: activeInterval, 374 | intervalName: getIntervalName(activeInterval), 375 | isChordRoot: chordSelected && activeInterval.ord === 0 && activeInterval.type === 0, 376 | toggle: calculateToggle(activeInterval, scaleNote, chordSelected, toggledIndexes, chordIntervals), 377 | midiToggle: (toggledMidiNotes & (2**scaleNote.note.index)) != 0 378 | }; 379 | }); 380 | } 381 | 382 | function buildScaleCounter(diatonic: boolean[], startAt:number = 0): [boolean, number][] { 383 | let noteCount = diatonic.filter(x => x).length; 384 | let i=(noteCount - startAt) % noteCount; 385 | return diatonic.map(isNote => { 386 | if(isNote) { 387 | let value = <[boolean, number]>[true, i]; 388 | i = (i+1) % noteCount; 389 | return value; 390 | } 391 | return <[boolean, number]>[false, 0]; 392 | }); 393 | } 394 | 395 | let romanNumeral: Array = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii']; 396 | 397 | export function generateChordNumbers(scaleNotes: ScaleNote[], mode: Mode, scaleFamily: mod.Mod): Chord[] { 398 | return scaleNotes.map((scaleNote, i) => { 399 | if(scaleNote.isScaleNote) { 400 | let roman = romanNumeral[scaleNote.noteNumber]; 401 | let nodes = generateNodes(scaleNotes, mode, scaleNote.note.index, [], 0, 0, scaleFamily); 402 | let diminished = ""; 403 | let type: ChordType = ChordType.Minor; 404 | // does it have a diminished 5th? 405 | if(nodes.some(x => x.scaleNote.isScaleNote && x.chordInterval.ord === 4 && x.chordInterval.type === IntervalType.Dim)) { 406 | diminished = "°"; 407 | type = ChordType.Diminished; 408 | } 409 | // does it have an augmented 5th? 410 | else if(nodes.some(x => x.scaleNote.isScaleNote && x.chordInterval.ord === 4 && x.chordInterval.type === IntervalType.Aug)) { 411 | diminished = "+"; 412 | type = ChordType.Augmented; 413 | } 414 | // does it have a major 3rd? 415 | else if(nodes.some(x => x.scaleNote.isScaleNote && x.chordInterval.ord === 2 && x.chordInterval.type === IntervalType.Maj)) { 416 | roman = roman.toLocaleUpperCase(); 417 | type = ChordType.Major; 418 | } 419 | return { 420 | romanNumeral: roman + diminished, 421 | type: type 422 | }; 423 | } 424 | 425 | return { 426 | romanNumeral: "", 427 | type: ChordType.Major 428 | }; 429 | }); 430 | } 431 | 432 | export function calculateToggle( 433 | activeInterval: Interval, 434 | scaleNote: ScaleNote, 435 | chordSelected: boolean, 436 | toggledIndexes: number, 437 | chordIntervals: number[] 438 | ): boolean { 439 | if(toggledIndexes === 0) { 440 | return chordSelected && scaleNote.isScaleNote && chordIntervals.some(x => activeInterval.ord === x); 441 | } 442 | return (toggledIndexes & (2**scaleNote.note.index)) != 0; 443 | } 444 | 445 | export function fifths(): Array { 446 | let indexes: Array = []; 447 | let current: number = 0; 448 | for (let i: number = 0; i < 12; i++) { 449 | indexes.push(current); 450 | current = (current + 7) % 12; 451 | } 452 | return indexes; 453 | } 454 | 455 | export function chromatic(): Array { 456 | let indexes: Array = []; 457 | for (let i: number = 0; i < 12; i++) { 458 | indexes.push(i); 459 | } 460 | return indexes; 461 | } 462 | } -------------------------------------------------------------------------------- /src/permalink-module.ts: -------------------------------------------------------------------------------- 1 | namespace permalink { 2 | 3 | let currentState: state.State = null; 4 | 5 | export function init(): void { 6 | events.stateChange.subscribe(x => currentState = x.state); 7 | } 8 | 9 | export function populatePermalinkText(): void { 10 | let permalink = generatePermalink(); 11 | let inputbox = document.getElementById("permalink-text") as HTMLInputElement 12 | inputbox.value = permalink; 13 | inputbox.focus; 14 | inputbox.select; 15 | inputbox.setSelectionRange(0, 99999); 16 | document.execCommand("copy"); 17 | } 18 | 19 | // create querystring from state 20 | export function generatePermalink(): string { 21 | if(currentState === null) { 22 | throw "No stateChange event published before querystring requested"; 23 | } 24 | 25 | let params = new URLSearchParams(); 26 | 27 | // only copy state that's different from default 28 | Object.keys(currentState).forEach(key => { 29 | if(currentState[key] !== state.defaultState[key]) { 30 | params.append(key, currentState[key]); 31 | } 32 | }); 33 | 34 | return `${location.protocol}//${location.host}${location.pathname}?${params.toString()}`; 35 | } 36 | 37 | // update state from querystring 38 | export function getState(existingState: state.State): state.State { 39 | 40 | let queryString = location.search; 41 | let params = new URLSearchParams(queryString); 42 | 43 | Object.keys(existingState).forEach(x => { 44 | let value = params.get(x); 45 | if(value == null) return; 46 | 47 | switch (typeof existingState[x]) { 48 | case 'boolean': 49 | existingState[x] = (value === "true"); 50 | break; 51 | case 'number': 52 | existingState[x] = parseInt(value); 53 | break; 54 | case 'object': 55 | existingState[x] = JSON.parse("[" + value + "]"); 56 | break; 57 | case 'string': 58 | existingState[x] = value; 59 | break; 60 | } 61 | 62 | console.log(`${x} -> ${value}, ${typeof existingState[x]}, ${existingState[x]}`); 63 | }); 64 | 65 | return existingState; 66 | } 67 | 68 | // test function 69 | export function getCurrentState(): void { 70 | let newState = getState(currentState); 71 | } 72 | } -------------------------------------------------------------------------------- /src/scale-family-module.ts: -------------------------------------------------------------------------------- 1 | namespace scaleFamily { 2 | 3 | export function init() { 4 | d3.select("#scale-dropdown") 5 | .selectAll("div") 6 | .data(music.scaleFamily) 7 | .enter() 8 | .append("div") 9 | .attr("class", "dropdown-content-item") 10 | .on("click", x => raiseScaleFamilyChangedEvent(x)) 11 | .text(x => x.name); 12 | } 13 | 14 | function raiseScaleFamilyChangedEvent(scaleFamily: music.ScaleFamily): void{ 15 | events.scaleFamilyChange.publish({ 16 | scaleFamily: scaleFamily 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /src/settings-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace settings { 3 | 4 | export function init(): void { 5 | events.leftHandedChange.subscribe(e => { 6 | let checkbox = document.getElementById("left-handed-checkbox") 7 | checkbox.checked = e.isLeftHanded; 8 | }); 9 | events.flipNutChange.subscribe(e => { 10 | let checkbox = document.getElementById("flip-nut-checkbox") 11 | checkbox.checked = e.isNutFlipped; 12 | }); 13 | events.setCToNoon.subscribe(e => { 14 | let checkbox = document.getElementById("set-c-to-noon-checkbox") 15 | checkbox.checked = e.isC; 16 | }); 17 | events.fretboardLabelChange.subscribe(e => { 18 | let selected = "fb-note-text" + String(e.labelType); 19 | let radio = document.getElementById(selected); 20 | radio.checked = true; 21 | }); 22 | } 23 | 24 | export function onLeftHandedClick(e:HTMLInputElement) { 25 | events.leftHandedChange.publish({ isLeftHanded: e.checked }); 26 | } 27 | 28 | export function onFlipNut(e:HTMLInputElement) { 29 | events.flipNutChange.publish( { isNutFlipped: e.checked }); 30 | } 31 | 32 | export function onSetCToNoon(e:HTMLInputElement) { 33 | events.setCToNoon.publish({ isC: e.checked }); 34 | } 35 | 36 | export function onFbNoteTextClick(e:HTMLInputElement) { 37 | events.fretboardLabelChange.publish({ labelType: parseInt(e.value) }) 38 | } 39 | } -------------------------------------------------------------------------------- /src/state-module.ts: -------------------------------------------------------------------------------- 1 | namespace state { 2 | 3 | export interface State { 4 | index: number; 5 | naturalIndex: number; 6 | chordIndex: number; 7 | chordIntervals: number[]; 8 | toggledIndexes: number; 9 | scaleFamilyIndex: number; 10 | modeIndex: number; 11 | midiToggledIndexes: number; 12 | isLeftHanded: boolean; 13 | isNutFlipped: boolean; 14 | fretboardLabelType: events.FretboardLabelType; 15 | circleIsCNoon: boolean; 16 | tuningIndex: number; 17 | } 18 | 19 | // default initial state 20 | export const defaultState: State = { 21 | index: 3, // C 22 | naturalIndex: 3, // C 23 | chordIndex: -1, // no chord 24 | chordIntervals: [0, 2, 4], // standard triad 25 | toggledIndexes: 0, // index bitflag 26 | scaleFamilyIndex: 0, // diatornic 27 | modeIndex: 0, // major 28 | midiToggledIndexes: 0, 29 | isLeftHanded: false, 30 | isNutFlipped: false, 31 | fretboardLabelType: events.FretboardLabelType.NoteName, 32 | circleIsCNoon: true, 33 | tuningIndex: 0, 34 | } 35 | 36 | let current: State = { 37 | index: defaultState.index, 38 | naturalIndex: defaultState.naturalIndex, 39 | chordIndex: defaultState.chordIndex, 40 | chordIntervals: defaultState.chordIntervals, 41 | toggledIndexes: defaultState.toggledIndexes, 42 | scaleFamilyIndex: defaultState.scaleFamilyIndex, 43 | modeIndex: defaultState.modeIndex, 44 | midiToggledIndexes: defaultState.midiToggledIndexes, 45 | isLeftHanded: defaultState.isLeftHanded, 46 | isNutFlipped: defaultState.isNutFlipped, 47 | fretboardLabelType: defaultState.fretboardLabelType, 48 | circleIsCNoon: defaultState.circleIsCNoon, 49 | tuningIndex: defaultState.tuningIndex, 50 | }; 51 | 52 | export function init() { 53 | 54 | try{ 55 | let cookieState = cookies.readCookie2(); 56 | if(cookieState !== null) { 57 | current = cookieState; 58 | } 59 | } 60 | catch(e) { 61 | // ignore the invalid cookie: 62 | } 63 | 64 | // update current state based on querystring. 65 | current = permalink.getState(current); 66 | 67 | // lets remember this while we reset everything. 68 | let tempChordIndex = current.chordIndex; 69 | let tempToggledIndexes = current.toggledIndexes; 70 | 71 | let scaleFamily = music.scaleFamily.find(x => x.index == current.scaleFamilyIndex); 72 | if(!scaleFamily) { 73 | throw "scaleFamily is " + scaleFamily + ", current.scaleFamilyIndex = " + current.scaleFamilyIndex; 74 | } 75 | let mode = scaleFamily.modes.find(x => x.index == current.modeIndex); 76 | if(!mode) { 77 | throw "mode is " + mode + "current.modeIndex" + current.modeIndex; 78 | } 79 | 80 | // publish scale and mode 81 | events.scaleFamilyChange.publish({ scaleFamily: scaleFamily }); 82 | events.modeChange.publish({ mode: mode }); 83 | events.chordIntervalChange.publish( { chordIntervals: current.chordIntervals }); 84 | 85 | // subscriptions 86 | events.tonicChange.subscribe(tonicChanged); 87 | events.modeChange.subscribe(modeChanged); 88 | events.chordChange.subscribe(chordChanged); 89 | events.toggle.subscribe(toggle); 90 | events.chordIntervalChange.subscribe(chordIntervalChanged); 91 | events.scaleFamilyChange.subscribe(scaleFamilyChanged); 92 | events.midiNote.subscribe(midiNote); 93 | 94 | // publish tonic and chord 95 | events.tonicChange.publish({ noteSpec: music.createNoteSpec(current.naturalIndex, current.index) }); 96 | events.chordChange.publish({ chordIndex: tempChordIndex }); 97 | // restore toggles 98 | current.toggledIndexes = tempToggledIndexes; 99 | updateScale(); 100 | 101 | // publish settings 102 | events.leftHandedChange.publish({ isLeftHanded: current.isLeftHanded }); 103 | events.flipNutChange.publish( { isNutFlipped: current.isNutFlipped }); 104 | events.fretboardLabelChange.publish({ labelType: current.fretboardLabelType }) 105 | events.setCToNoon.publish({ isC: current.circleIsCNoon }); 106 | events.tuningChange.publish({ index: current.tuningIndex }); 107 | 108 | // subscribe to settings changes 109 | events.leftHandedChange.subscribe(leftHandedChange); 110 | events.flipNutChange.subscribe(flipNutChange); 111 | events.fretboardLabelChange.subscribe(fretboardLabelChange) 112 | events.setCToNoon.subscribe(setCToNoon); 113 | events.tuningChange.subscribe(tuningChange); 114 | } 115 | 116 | function tonicChanged(tonicChangedEvent: events.TonicChangedEvent): void { 117 | current.index = tonicChangedEvent.noteSpec.index; 118 | current.naturalIndex = tonicChangedEvent.noteSpec.natural.index; 119 | current.chordIndex = -1; 120 | updateScale(); 121 | } 122 | 123 | function modeChanged(modeChangedEvent: events.ModeChangedEvent): void { 124 | current.modeIndex = modeChangedEvent.mode.index; 125 | current.chordIndex = -1; 126 | updateScale(); 127 | } 128 | 129 | function chordChanged(chordChangedEvent: events.ChordChangeEvent): void { 130 | if(chordChangedEvent.chordIndex === current.chordIndex) { 131 | current.chordIndex = -1 132 | } 133 | else { 134 | current.chordIndex = chordChangedEvent.chordIndex; 135 | } 136 | current.toggledIndexes = 0; 137 | updateScale(); 138 | } 139 | 140 | function toggle(toggleEvent: events.ToggleEvent): void { 141 | current.toggledIndexes = current.toggledIndexes ^ 2**toggleEvent.index; 142 | updateScale(); 143 | } 144 | 145 | function chordIntervalChanged(chordIntervalChangedEvent: events.ChordIntervalChangeEvent): void { 146 | current.chordIntervals = chordIntervalChangedEvent.chordIntervals; 147 | current.toggledIndexes = 0; 148 | updateScale(); 149 | } 150 | 151 | function scaleFamilyChanged(scaleFamilyChangedEvent: events.ScaleFamilyChangeEvent): void { 152 | current.scaleFamilyIndex = scaleFamilyChangedEvent.scaleFamily.index; 153 | current.modeIndex = scaleFamilyChangedEvent.scaleFamily.defaultModeIndex; 154 | current.chordIndex = -1 155 | updateScale(); 156 | } 157 | 158 | function midiNote(midiNoteEvent: events.MidiNoteEvent): void { 159 | current.midiToggledIndexes = midiNoteEvent.toggledIndexes; 160 | updateScale(); 161 | } 162 | 163 | // setttings event handlers 164 | 165 | function leftHandedChange(leftHandedChangeEvent: events.LeftHandedFretboardEvent): void { 166 | current.isLeftHanded = leftHandedChangeEvent.isLeftHanded; 167 | publishStateChange(); 168 | } 169 | 170 | function flipNutChange(flipNutChangeEvent: events.FlipNutEvent): void { 171 | current.isNutFlipped = flipNutChangeEvent.isNutFlipped; 172 | publishStateChange(); 173 | } 174 | 175 | function fretboardLabelChange(fretboardLabelChangeEvent: events.FretboardLabelChangeEvent): void { 176 | current.fretboardLabelType = fretboardLabelChangeEvent.labelType; 177 | publishStateChange(); 178 | } 179 | 180 | function setCToNoon(setCToNoonEvent: events.SetCToNoonEvent): void { 181 | current.circleIsCNoon = setCToNoonEvent.isC; 182 | publishStateChange(); 183 | } 184 | 185 | function tuningChange(tuningChangedEvent: events.TuningChangedEvent): void { 186 | current.tuningIndex = tuningChangedEvent.index; 187 | publishStateChange(); 188 | } 189 | 190 | function updateScale(): void { 191 | 192 | let scaleFamily = music.scaleFamily.find(x => x.index == current.scaleFamilyIndex); 193 | if(!scaleFamily) { 194 | throw "scaleFamily is " + scaleFamily + ", current.scaleFamilyIndex = " + current.scaleFamilyIndex; 195 | } 196 | let mode = scaleFamily.modes.find(x => x.index == current.modeIndex); 197 | if(!mode) { 198 | throw "mode is " + mode + "current.modeIndex" + current.modeIndex; 199 | } 200 | let noteSpec = music.createNoteSpec(current.naturalIndex, current.index); 201 | 202 | let nodes = music.generateScaleShim( 203 | noteSpec, 204 | mode, 205 | current.chordIndex, 206 | current.chordIntervals, 207 | current.toggledIndexes, 208 | current.midiToggledIndexes, 209 | scaleFamily); 210 | 211 | // update togges, because a chord may have been generated. 212 | current.toggledIndexes = nodes 213 | .filter(x => x.toggle) 214 | .map(x => x.scaleNote.note.index) 215 | .reduce((a, b) => a + 2**b, 0); 216 | 217 | events.scaleChange.publish({ 218 | nodes: nodes, 219 | mode: mode 220 | }); 221 | 222 | publishStateChange(); 223 | } 224 | 225 | function publishStateChange(): void { 226 | events.stateChange.publish({ 227 | state: current 228 | }); 229 | } 230 | } -------------------------------------------------------------------------------- /src/tonics-module.ts: -------------------------------------------------------------------------------- 1 | 2 | namespace tonics { 3 | 4 | let buttons: d3.Selection; 5 | 6 | interface ButtonData { 7 | readonly noteSpec: music.NoteSpec; 8 | }; 9 | 10 | function bg(natural: music.Natural): Array { 11 | let flatIndex = natural.index == 0 ? 11 : natural.index - 1; 12 | let sharpIndex = (natural.index + 1) % 12; 13 | return [ 14 | { noteSpec: music.createNoteSpec(natural.index, flatIndex) }, 15 | { noteSpec: music.createNoteSpec(natural.index, natural.index) }, 16 | { noteSpec: music.createNoteSpec(natural.index, sharpIndex) } 17 | ]; 18 | } 19 | 20 | export function init(): void { 21 | let pad = 5; 22 | let buttonHeight = 25; 23 | let svg = d3.select("#modes"); 24 | 25 | let tonics = svg.append("g"); 26 | 27 | let gs = tonics.selectAll("g") 28 | .data(music.naturals) 29 | .enter() 30 | .append("g") 31 | .attr("transform", function (d, i) { return "translate(0, " + (i * (buttonHeight + pad) + pad) + ")"; }) 32 | .selectAll("g") 33 | .data(d => bg(d), indexer) 34 | .enter() 35 | .append("g") 36 | .attr("transform", function (d, i) { return "translate(" + (i * 55) + ", 0)"; }); 37 | 38 | buttons = gs 39 | .append("rect") 40 | .attr("x", pad) 41 | .attr("y", 0) 42 | .attr("strokeWidth", 2) 43 | .attr("width", 40) 44 | .attr("height", 25) 45 | .attr("class", d => isSameNoteAsNatural(d.noteSpec) ? "tonic-button tonic-button-grey" : "tonic-button") 46 | .on("click", d => events.tonicChange.publish({ noteSpec: d.noteSpec })); 47 | 48 | gs 49 | .append("text") 50 | .attr("x", pad + 10) 51 | .attr("y", 17) 52 | .text(function (x) { return x.noteSpec.label; }) 53 | .attr("class", "tonic-text"); 54 | 55 | events.tonicChange.subscribe(listener); 56 | } 57 | 58 | function listener(tonicChanged: events.TonicChangedEvent): void { 59 | let ds: Array = [{ 60 | noteSpec: tonicChanged.noteSpec 61 | }]; 62 | buttons 63 | .data(ds, indexer) 64 | .attr("class", "tonic-button tonic-button-selected") 65 | .exit() 66 | .attr("class", d => isSameNoteAsNatural(d.noteSpec) ? "tonic-button tonic-button-grey" : "tonic-button"); 67 | } 68 | 69 | function indexer(d: ButtonData): string { 70 | return d.noteSpec.label; 71 | } 72 | 73 | function isSameNoteAsNatural(noteSpec: music.NoteSpec): boolean { 74 | return music.naturals.some(x => x.index === noteSpec.index && x.index != noteSpec.natural.index); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/tuning-module.ts: -------------------------------------------------------------------------------- 1 | namespace tuning { 2 | 3 | let guitarDots: Array<[number, number]> = [ 4 | [3, 0], // [fret, position] 5 | [5, 0], 6 | [7, 0], 7 | [9, 0], 8 | [12, -1], 9 | [12, 1], 10 | [15, 0] 11 | ]; 12 | 13 | // Viola/violin for beginners. 14 | let violaDots: Array<[number, number]> = [ 15 | [2, 0], // 1st finger 16 | [4, 0], // 2nd finger 17 | [5, 0], // 3rd finger 18 | [7, 0], // 4th finger 19 | [12, -1], 20 | [12, 1] 21 | ]; 22 | 23 | interface TuningInfo { 24 | readonly tuning: string; 25 | readonly dots: Array<[number, number]>; 26 | readonly description: string; 27 | } 28 | 29 | export interface Tuning { 30 | readonly index: number; 31 | readonly tuning: string; 32 | readonly dots: Array<[number, number]>; 33 | readonly description: string; 34 | readonly notes: Array; 35 | } 36 | 37 | let tuningInfos: Array = [ 38 | { tuning: "EADGBE", dots: guitarDots, description: "Guitar Standard" }, 39 | { tuning: "EADGCF", dots: guitarDots, description: "All Fourths" }, 40 | { tuning: "CGDAEB", dots: guitarDots, description: "All Fifths" }, 41 | { tuning: "BFBFBF", dots: guitarDots, description: "Augmented Fourths" }, 42 | { tuning: "DADGBE", dots: guitarDots, description: "Guitar Drop D" }, 43 | { tuning: "DADGAD", dots: guitarDots, description: "Celtic" }, 44 | { tuning: "CGDAEG", dots: guitarDots, description: "Guitar Fripp NST" }, 45 | { tuning: "BEADGBE", dots: guitarDots, description: "Guitar 7 string" }, 46 | { tuning: "DABEAB", dots: guitarDots, description: "Guitar Portuguese" }, 47 | { tuning: "DGDGBD", dots: guitarDots, description: "Guitar Open G" }, 48 | { tuning: "EADGDG", dots: guitarDots, description: "Guitar Convert" }, 49 | { tuning: "E♭A♭D♭G♭B♭E♭", dots: guitarDots, description: "Guitar E♭ (Hendrix)" }, 50 | 51 | { tuning: "BEADF♯B", dots: guitarDots, description: "Baritone B" }, 52 | { tuning: "ADGCEA", dots: guitarDots, description: "Baritone A" }, 53 | 54 | { tuning: "EADG", dots: guitarDots, description: "Bass Standard" }, 55 | { tuning: "DADG", dots: guitarDots, description: "Bass Drop D" }, 56 | { tuning: "EADGC", dots: guitarDots, description: "Bass 5 Strings Standard High" }, 57 | { tuning: "BEADG", dots: guitarDots, description: "Bass 5 Strings Standard Low" }, 58 | { tuning: "BEADGC", dots: guitarDots, description: "Bass 6 Strings Standard" }, 59 | { tuning: "BEADGCF", dots: guitarDots, description: "Bass 7 Strings Standard" }, 60 | 61 | { tuning: "DGBD", dots: guitarDots, description: "Banjo" }, 62 | { tuning: "DGBD", dots: guitarDots, description: "Cavaquinho"}, 63 | { tuning: "GCEA", dots: guitarDots, description: "Ukulele C" }, 64 | { tuning: "CGDA", dots: violaDots, description: "Cello" }, 65 | { tuning: "GDAE", dots: violaDots, description: "Violin" }, 66 | { tuning: "CGDA", dots: violaDots, description: "Viola" }, 67 | ] 68 | 69 | export let tunings: Array = []; 70 | 71 | export function parseTuning(tuning: string) : Array { 72 | let tokens: Array = []; 73 | let result: Array = []; 74 | 75 | let tokenIndex = 0; 76 | let lastWasChar = false; 77 | 78 | for(let i:number =0; i < tuning.length; i++) { 79 | let noteChar = tuning.charAt(i); 80 | if("ABCDEFG".indexOf(noteChar) >= 0) { 81 | tokens[tokenIndex] = noteChar; 82 | tokenIndex++; 83 | lastWasChar = true; 84 | } 85 | else if("♯♭".indexOf(noteChar) >= 0 && lastWasChar) { 86 | tokens[tokenIndex-1] = tokens[tokenIndex-1] + noteChar; 87 | lastWasChar = false; 88 | } 89 | else { 90 | throw "Invalid tuning char"; 91 | } 92 | } 93 | 94 | for(let token of tokens){ 95 | let noteName = music.noteNames.filter(x => x.name === token); 96 | if(noteName.length != 1) { 97 | throw "Invalid token"; 98 | } 99 | result.push(noteName[0].index); 100 | } 101 | 102 | return result; 103 | } 104 | 105 | export function init() { 106 | 107 | let index: number = 0; 108 | for(let info of tuningInfos) { 109 | let tuning: Tuning = { 110 | index: index, 111 | tuning: info.tuning, 112 | dots: info.dots, 113 | description: info.description, 114 | notes: parseTuning(info.tuning) 115 | }; 116 | tunings.push(tuning); 117 | index++; 118 | } 119 | 120 | d3.select("#tuning-dropdown") 121 | .selectAll("div") 122 | .data(tunings) 123 | .enter() 124 | .append("div") 125 | .attr("class", "dropdown-content-item") 126 | .on("click", x => raiseTuningChangedEvent(x)) 127 | .text(x => x.tuning + " " + x.description); 128 | 129 | raiseTuningChangedEvent(tunings[0]); 130 | } 131 | 132 | function raiseTuningChangedEvent(tuning: Tuning): void{ 133 | events.tuningChange.publish({ 134 | index: tuning.index 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "none", 5 | "sourceMap": true, 6 | "outDir": "docs", 7 | "outFile": "docs/gtr-cof.js", 8 | "types": [] 9 | }, 10 | "files": [ 11 | "src/menu-module.ts", 12 | "src/mod-module.ts", 13 | "src/events-module.ts", 14 | "src/cookie-module.ts", 15 | "src/music-module.ts", 16 | "src/state-module.ts", 17 | "src/cof-module.ts", 18 | "src/tonics-module.ts", 19 | "src/chord-interval-module.ts", 20 | "src/modes-module.ts", 21 | "src/gtr-module.ts", 22 | "src/tuning-module.ts", 23 | "src/settings-module.ts", 24 | "src/scale-family-module.ts", 25 | "src/midi-module.ts", 26 | "src/permalink-module.ts", 27 | "src/gtr-cof.ts" 28 | ] 29 | } --------------------------------------------------------------------------------