├── .gitignore ├── LICENSE.md ├── README.md ├── assets ├── config.codekit ├── css │ └── style.css ├── favicon.ico ├── fonts │ ├── Vera-webfont.woff │ ├── Vera.ttf │ ├── courier_prime_code-webfont.woff │ └── courier_prime_code-webfont.woff2 ├── img │ ├── icon.png │ ├── listen.png │ ├── listen.svg │ ├── listen_dark.svg │ ├── pressed.png │ ├── pressed.svg │ ├── pressed_dark.svg │ ├── switch_off.svg │ ├── switch_on.svg │ ├── toggle-minus.png │ ├── toggle-minus.svg │ ├── toggle-plus.png │ └── toggle-plus.svg └── sass │ ├── .sass-cache │ ├── 351e40b2ba7f0e6bb51eb64ff568952c6994d0cc │ │ ├── _color-picker.scssc │ │ ├── _control.scssc │ │ └── _range-slider.scssc │ ├── a21e1ca9377d1d7ec39004ebd8227e082064c748 │ │ ├── _buttons.scssc │ │ ├── _global-variables.scssc │ │ └── _responsive.scssc │ └── f45f0ef2d7303c23fa214de2852f85111b601964 │ │ ├── _color-schemes.scssc │ │ ├── _foundation.scssc │ │ ├── _layout.scssc │ │ ├── _legacy.scssc │ │ ├── _modules.scssc │ │ ├── _type.scssc │ │ └── style.scssc │ ├── _color-schemes.scss │ ├── _foundation.scss │ ├── _layout.scss │ ├── _legacy.scss │ ├── _modules.scss │ ├── _type.scss │ ├── foundation │ ├── _buttons.scss │ ├── _global-variables.scss │ └── _responsive.scss │ ├── modules │ ├── _color-picker.scss │ ├── _control.scss │ └── _range-slider.scss │ └── style.scss ├── dist └── bundle.js ├── fonts ├── Vera-webfont.woff └── Vera.ttf ├── package-lock.json ├── package.json ├── src ├── builder.js ├── colorpicker.js ├── controls.js ├── index.js ├── retrieve.js ├── settings.js ├── types.js └── userinput.js ├── test ├── builder.js └── extract.js ├── web ├── server.js └── views │ └── pages │ ├── index.ejs │ └── range-slider.css └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | /output 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018 VIDVOX, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OSCQueryApp 2 | 3 | An HTML, Javascript, css application for retrieving OSCQuery responses (in json) and rendering an appropriate webapp. 4 | 5 | ### Proposal 6 | 7 | See [https://github.com/mrRay/OSCQueryProposal](https://github.com/mrRay/OSCQueryProposal) 8 | 9 | ### Static deployment 10 | 11 | 1. Copy web/views/pages/index.ejs to index.html 12 | 2. In index.html, replace `<%= host_url %>` with the OSCQuery server, which serves the json feed. (TODO: How to improve this?) 13 | 3. Serve this modified `index.html` 14 | 4. Serve dist/bundle.js at the path `/bundle.js` 15 | 5. Serve assets/css/style.css at the path `/css/style.css` 16 | 6. (No need to serve assets/img, the images get built into the bundle.js) 17 | 18 | ### Building for development 19 | 20 | 1. Install npm 21 | 2. `npm install` - use npm to install dependencies 22 | 3. `npm run build` - execute webpack to build output/bundle.js 23 | 24 | When development is complete, copy output/bundle.js to dist/bundle.js. 25 | 26 | ### Running node.js development server 27 | 28 | 1. `export SERVER_URL=http://127.0.0.1:2345` (OSCQuery Server) 29 | 2. `cd web` 30 | 3. `node server.js` 31 | 32 | Open your browser and go nativate to http://localhost:5050 33 | 34 | ### Tests 35 | 36 | `npm run test` 37 | 38 | ### CORS Security 39 | 40 | Javascript running in-browser cannot make cross-origin requests unless the server being contacted sets the HTTP header "Access-Control-Allow-Origin: *". 41 | 42 | If the server does not set this header, as a temporary workaround, you can install this [Chrome extension](https://chrome.google.com/webstore/detail/nlfbmbojpeacfghkpbjhddihlkkiljbi). 43 | 44 | Otherwise, the json request will be blocked by Chrome's security policy. 45 | 46 | ### License 47 | 48 | This project is licensed under the MIT License and is free to use. See the included license document for more information. -------------------------------------------------------------------------------- /assets/config.codekit: -------------------------------------------------------------------------------- 1 | { 2 | "CodeKitInfo": "This is a CodeKit 2.x project configuration file. It is designed to sync project settings across multiple machines. MODIFYING THE CONTENTS OF THIS FILE IS A POOR LIFE DECISION. If you do so, you will likely cause CodeKit to crash. This file is not useful unless accompanied by the project that created it in CodeKit 2. This file is not backwards-compatible with CodeKit 1.x. For more information, see: http:\/\/incident57.com\/codekit", 3 | "creatorBuild": "19127", 4 | "files": { 5 | "\/css\/style.css": { 6 | "fileType": 16, 7 | "ignore": 1, 8 | "ignoreWasSetByUser": 0, 9 | "inputAbbreviatedPath": "\/css\/style.css", 10 | "outputAbbreviatedPath": "No Output Path", 11 | "outputPathIsOutsideProject": 0, 12 | "outputPathIsSetByUser": 0 13 | }, 14 | "\/img\/icon.png": { 15 | "fileType": 32768, 16 | "ignore": 0, 17 | "ignoreWasSetByUser": 0, 18 | "initialSize": 5337, 19 | "inputAbbreviatedPath": "\/img\/icon.png", 20 | "outputAbbreviatedPath": "\/img\/icon.png", 21 | "outputPathIsOutsideProject": 0, 22 | "outputPathIsSetByUser": 0, 23 | "processed": 0 24 | }, 25 | "\/img\/listen.png": { 26 | "fileType": 32768, 27 | "ignore": 0, 28 | "ignoreWasSetByUser": 0, 29 | "initialSize": 979, 30 | "inputAbbreviatedPath": "\/img\/listen.png", 31 | "outputAbbreviatedPath": "\/img\/listen.png", 32 | "outputPathIsOutsideProject": 0, 33 | "outputPathIsSetByUser": 0, 34 | "processed": 0 35 | }, 36 | "\/img\/pressed.png": { 37 | "fileType": 32768, 38 | "ignore": 0, 39 | "ignoreWasSetByUser": 0, 40 | "initialSize": 938, 41 | "inputAbbreviatedPath": "\/img\/pressed.png", 42 | "outputAbbreviatedPath": "\/img\/pressed.png", 43 | "outputPathIsOutsideProject": 0, 44 | "outputPathIsSetByUser": 0, 45 | "processed": 0 46 | }, 47 | "\/img\/toggle-minus.png": { 48 | "fileType": 32768, 49 | "ignore": 0, 50 | "ignoreWasSetByUser": 0, 51 | "initialSize": 223, 52 | "inputAbbreviatedPath": "\/img\/toggle-minus.png", 53 | "outputAbbreviatedPath": "\/img\/toggle-minus.png", 54 | "outputPathIsOutsideProject": 0, 55 | "outputPathIsSetByUser": 0, 56 | "processed": 0 57 | }, 58 | "\/img\/toggle-plus.png": { 59 | "fileType": 32768, 60 | "ignore": 0, 61 | "ignoreWasSetByUser": 0, 62 | "initialSize": 248, 63 | "inputAbbreviatedPath": "\/img\/toggle-plus.png", 64 | "outputAbbreviatedPath": "\/img\/toggle-plus.png", 65 | "outputPathIsOutsideProject": 0, 66 | "outputPathIsSetByUser": 0, 67 | "processed": 0 68 | }, 69 | "\/sass\/_color-schemes.scss": { 70 | "createSourceMap": 0, 71 | "debugStyle": 0, 72 | "decimalPrecision": 10, 73 | "fileType": 4, 74 | "ignore": 1, 75 | "ignoreWasSetByUser": 0, 76 | "inputAbbreviatedPath": "\/sass\/_color-schemes.scss", 77 | "outputAbbreviatedPath": "\/css\/_color-schemes.css", 78 | "outputPathIsOutsideProject": 0, 79 | "outputPathIsSetByUser": 0, 80 | "outputStyle": 0, 81 | "shouldRunAutoprefixer": 0, 82 | "shouldRunBless": 0, 83 | "useLibsass": 0 84 | }, 85 | "\/sass\/_foundation.scss": { 86 | "createSourceMap": 0, 87 | "debugStyle": 0, 88 | "decimalPrecision": 10, 89 | "fileType": 4, 90 | "ignore": 1, 91 | "ignoreWasSetByUser": 0, 92 | "inputAbbreviatedPath": "\/sass\/_foundation.scss", 93 | "outputAbbreviatedPath": "\/css\/_foundation.css", 94 | "outputPathIsOutsideProject": 0, 95 | "outputPathIsSetByUser": 0, 96 | "outputStyle": 0, 97 | "shouldRunAutoprefixer": 0, 98 | "shouldRunBless": 0, 99 | "useLibsass": 0 100 | }, 101 | "\/sass\/_layout.scss": { 102 | "createSourceMap": 0, 103 | "debugStyle": 0, 104 | "decimalPrecision": 10, 105 | "fileType": 4, 106 | "ignore": 1, 107 | "ignoreWasSetByUser": 0, 108 | "inputAbbreviatedPath": "\/sass\/_layout.scss", 109 | "outputAbbreviatedPath": "\/css\/_layout.css", 110 | "outputPathIsOutsideProject": 0, 111 | "outputPathIsSetByUser": 0, 112 | "outputStyle": 0, 113 | "shouldRunAutoprefixer": 0, 114 | "shouldRunBless": 0, 115 | "useLibsass": 0 116 | }, 117 | "\/sass\/_legacy.scss": { 118 | "createSourceMap": 0, 119 | "debugStyle": 0, 120 | "decimalPrecision": 10, 121 | "fileType": 4, 122 | "ignore": 1, 123 | "ignoreWasSetByUser": 0, 124 | "inputAbbreviatedPath": "\/sass\/_legacy.scss", 125 | "outputAbbreviatedPath": "\/css\/_legacy.css", 126 | "outputPathIsOutsideProject": 0, 127 | "outputPathIsSetByUser": 0, 128 | "outputStyle": 0, 129 | "shouldRunAutoprefixer": 0, 130 | "shouldRunBless": 0, 131 | "useLibsass": 0 132 | }, 133 | "\/sass\/_modules.scss": { 134 | "createSourceMap": 0, 135 | "debugStyle": 0, 136 | "decimalPrecision": 10, 137 | "fileType": 4, 138 | "ignore": 1, 139 | "ignoreWasSetByUser": 0, 140 | "inputAbbreviatedPath": "\/sass\/_modules.scss", 141 | "outputAbbreviatedPath": "\/css\/_modules.css", 142 | "outputPathIsOutsideProject": 0, 143 | "outputPathIsSetByUser": 0, 144 | "outputStyle": 0, 145 | "shouldRunAutoprefixer": 0, 146 | "shouldRunBless": 0, 147 | "useLibsass": 0 148 | }, 149 | "\/sass\/_type.scss": { 150 | "createSourceMap": 0, 151 | "debugStyle": 0, 152 | "decimalPrecision": 10, 153 | "fileType": 4, 154 | "ignore": 1, 155 | "ignoreWasSetByUser": 0, 156 | "inputAbbreviatedPath": "\/sass\/_type.scss", 157 | "outputAbbreviatedPath": "\/css\/_type.css", 158 | "outputPathIsOutsideProject": 0, 159 | "outputPathIsSetByUser": 0, 160 | "outputStyle": 0, 161 | "shouldRunAutoprefixer": 0, 162 | "shouldRunBless": 0, 163 | "useLibsass": 0 164 | }, 165 | "\/sass\/foundation\/_buttons.scss": { 166 | "createSourceMap": 0, 167 | "debugStyle": 0, 168 | "decimalPrecision": 10, 169 | "fileType": 4, 170 | "ignore": 1, 171 | "ignoreWasSetByUser": 0, 172 | "inputAbbreviatedPath": "\/sass\/foundation\/_buttons.scss", 173 | "outputAbbreviatedPath": "\/sass\/css\/_buttons.css", 174 | "outputPathIsOutsideProject": 0, 175 | "outputPathIsSetByUser": 0, 176 | "outputStyle": 0, 177 | "shouldRunAutoprefixer": 0, 178 | "shouldRunBless": 0, 179 | "useLibsass": 0 180 | }, 181 | "\/sass\/foundation\/_global-variables.scss": { 182 | "createSourceMap": 0, 183 | "debugStyle": 0, 184 | "decimalPrecision": 10, 185 | "fileType": 4, 186 | "ignore": 1, 187 | "ignoreWasSetByUser": 0, 188 | "inputAbbreviatedPath": "\/sass\/foundation\/_global-variables.scss", 189 | "outputAbbreviatedPath": "\/sass\/css\/_global-variables.css", 190 | "outputPathIsOutsideProject": 0, 191 | "outputPathIsSetByUser": 0, 192 | "outputStyle": 0, 193 | "shouldRunAutoprefixer": 0, 194 | "shouldRunBless": 0, 195 | "useLibsass": 0 196 | }, 197 | "\/sass\/foundation\/_responsive.scss": { 198 | "createSourceMap": 0, 199 | "debugStyle": 0, 200 | "decimalPrecision": 10, 201 | "fileType": 4, 202 | "ignore": 1, 203 | "ignoreWasSetByUser": 0, 204 | "inputAbbreviatedPath": "\/sass\/foundation\/_responsive.scss", 205 | "outputAbbreviatedPath": "\/sass\/css\/_responsive.css", 206 | "outputPathIsOutsideProject": 0, 207 | "outputPathIsSetByUser": 0, 208 | "outputStyle": 0, 209 | "shouldRunAutoprefixer": 0, 210 | "shouldRunBless": 0, 211 | "useLibsass": 0 212 | }, 213 | "\/sass\/modules\/_color-picker.scss": { 214 | "createSourceMap": 0, 215 | "debugStyle": 0, 216 | "decimalPrecision": 10, 217 | "fileType": 4, 218 | "ignore": 1, 219 | "ignoreWasSetByUser": 0, 220 | "inputAbbreviatedPath": "\/sass\/modules\/_color-picker.scss", 221 | "outputAbbreviatedPath": "\/sass\/css\/_color-picker.css", 222 | "outputPathIsOutsideProject": 0, 223 | "outputPathIsSetByUser": 0, 224 | "outputStyle": 0, 225 | "shouldRunAutoprefixer": 0, 226 | "shouldRunBless": 0, 227 | "useLibsass": 0 228 | }, 229 | "\/sass\/modules\/_control.scss": { 230 | "createSourceMap": 0, 231 | "debugStyle": 0, 232 | "decimalPrecision": 10, 233 | "fileType": 4, 234 | "ignore": 1, 235 | "ignoreWasSetByUser": 0, 236 | "inputAbbreviatedPath": "\/sass\/modules\/_control.scss", 237 | "outputAbbreviatedPath": "\/sass\/css\/_control.css", 238 | "outputPathIsOutsideProject": 0, 239 | "outputPathIsSetByUser": 0, 240 | "outputStyle": 0, 241 | "shouldRunAutoprefixer": 0, 242 | "shouldRunBless": 0, 243 | "useLibsass": 0 244 | }, 245 | "\/sass\/modules\/_range-slider.scss": { 246 | "createSourceMap": 0, 247 | "debugStyle": 0, 248 | "decimalPrecision": 10, 249 | "fileType": 4, 250 | "ignore": 1, 251 | "ignoreWasSetByUser": 0, 252 | "inputAbbreviatedPath": "\/sass\/modules\/_range-slider.scss", 253 | "outputAbbreviatedPath": "\/sass\/css\/_range-slider.css", 254 | "outputPathIsOutsideProject": 0, 255 | "outputPathIsSetByUser": 0, 256 | "outputStyle": 0, 257 | "shouldRunAutoprefixer": 0, 258 | "shouldRunBless": 0, 259 | "useLibsass": 0 260 | }, 261 | "\/sass\/style.scss": { 262 | "createSourceMap": 0, 263 | "debugStyle": 0, 264 | "decimalPrecision": 10, 265 | "fileType": 4, 266 | "ignore": 0, 267 | "ignoreWasSetByUser": 0, 268 | "inputAbbreviatedPath": "\/sass\/style.scss", 269 | "outputAbbreviatedPath": "\/css\/style.css", 270 | "outputPathIsOutsideProject": 0, 271 | "outputPathIsSetByUser": 0, 272 | "outputStyle": 0, 273 | "shouldRunAutoprefixer": 0, 274 | "shouldRunBless": 0, 275 | "useLibsass": 0 276 | } 277 | }, 278 | "hooks": [ 279 | ], 280 | "lastSavedByUser": "Wiley Wiggins", 281 | "manualImportLinks": { 282 | }, 283 | "projectAttributes": { 284 | "bowerAbbreviatedPath": "", 285 | "displayValue": "assets", 286 | "displayValueWasSetByUser": 1, 287 | "iconImageName": "pencil_blue" 288 | }, 289 | "projectSettings": { 290 | "alwaysUseExternalServer": 0, 291 | "animateCSSInjections": 1, 292 | "autoApplyPSLanguageSettingsStyle": 0, 293 | "autoprefixerBrowserString": "> 1%, last 2 versions, Firefox ESR, Opera 12.1", 294 | "autoSyncProjectSettingsFile": 1, 295 | "browserRefreshDelay": 0, 296 | "coffeeAutoOutputPathEnabled": 1, 297 | "coffeeAutoOutputPathFilenamePattern": "*.js", 298 | "coffeeAutoOutputPathRelativePath": "", 299 | "coffeeAutoOutputPathReplace1": "", 300 | "coffeeAutoOutputPathReplace2": "", 301 | "coffeeAutoOutputPathStyle": 0, 302 | "coffeeCreateSourceMap": 0, 303 | "coffeeLintFlags2": { 304 | "arrow_spacing": { 305 | "active": 0, 306 | "flagValue": -1 307 | }, 308 | "camel_case_classes": { 309 | "active": 1, 310 | "flagValue": -1 311 | }, 312 | "colon_assignment_spacing": { 313 | "active": 0, 314 | "flagValue": 1 315 | }, 316 | "cyclomatic_complexity": { 317 | "active": 0, 318 | "flagValue": 10 319 | }, 320 | "duplicate_key": { 321 | "active": 1, 322 | "flagValue": -1 323 | }, 324 | "empty_constructor_needs_parens": { 325 | "active": 0, 326 | "flagValue": -1 327 | }, 328 | "ensure_comprehensions": { 329 | "active": 1, 330 | "flagValue": -1 331 | }, 332 | "indentation": { 333 | "active": 1, 334 | "flagValue": 2 335 | }, 336 | "line_endings": { 337 | "active": 0, 338 | "flagValue": 0 339 | }, 340 | "max_line_length": { 341 | "active": 0, 342 | "flagValue": 150 343 | }, 344 | "missing_fat_arrows": { 345 | "active": 0, 346 | "flagValue": -1 347 | }, 348 | "newlines_after_classes": { 349 | "active": 0, 350 | "flagValue": 3 351 | }, 352 | "no_backticks": { 353 | "active": 1, 354 | "flagValue": -1 355 | }, 356 | "no_debugger": { 357 | "active": 1, 358 | "flagValue": -1 359 | }, 360 | "no_empty_functions": { 361 | "active": 0, 362 | "flagValue": -1 363 | }, 364 | "no_empty_param_list": { 365 | "active": 0, 366 | "flagValue": -1 367 | }, 368 | "no_implicit_braces": { 369 | "active": 1, 370 | "flagValue": -1 371 | }, 372 | "no_implicit_parens": { 373 | "active": 0, 374 | "flagValue": -1 375 | }, 376 | "no_interpolation_in_single_quotes": { 377 | "active": 0, 378 | "flagValue": -1 379 | }, 380 | "no_nested_string_interpolation": { 381 | "active": 1, 382 | "flagValue": -1 383 | }, 384 | "no_plusplus": { 385 | "active": 0, 386 | "flagValue": -1 387 | }, 388 | "no_private_function_fat_arrows": { 389 | "active": 1, 390 | "flagValue": -1 391 | }, 392 | "no_stand_alone_at": { 393 | "active": 1, 394 | "flagValue": -1 395 | }, 396 | "no_tabs": { 397 | "active": 1, 398 | "flagValue": -1 399 | }, 400 | "no_this": { 401 | "active": 0, 402 | "flagValue": -1 403 | }, 404 | "no_throwing_strings": { 405 | "active": 1, 406 | "flagValue": -1 407 | }, 408 | "no_trailing_semicolons": { 409 | "active": 1, 410 | "flagValue": -1 411 | }, 412 | "no_trailing_whitespace": { 413 | "active": 1, 414 | "flagValue": -1 415 | }, 416 | "no_unnecessary_double_quotes": { 417 | "active": 0, 418 | "flagValue": -1 419 | }, 420 | "no_unnecessary_fat_arrows": { 421 | "active": 1, 422 | "flagValue": -1 423 | }, 424 | "non_empty_constructor_needs_parens": { 425 | "active": 0, 426 | "flagValue": -1 427 | }, 428 | "prefer_english_operator": { 429 | "active": 0, 430 | "flagValue": -1 431 | }, 432 | "space_operators": { 433 | "active": 0, 434 | "flagValue": -1 435 | }, 436 | "spacing_after_comma": { 437 | "active": 1, 438 | "flagValue": -1 439 | } 440 | }, 441 | "coffeeMinifyOutput": 1, 442 | "coffeeOutputStyle": 0, 443 | "coffeeSyntaxCheckerStyle": 1, 444 | "externalServerAddress": "http:\/\/localhost:8888", 445 | "externalServerPreviewPathAddition": "", 446 | "genericWebpageFileExtensionsString": "html, htm, shtml, shtm, xhtml, php, jsp, asp, aspx, erb, ctp", 447 | "hamlAutoOutputPathEnabled": 1, 448 | "hamlAutoOutputPathFilenamePattern": "*.html", 449 | "hamlAutoOutputPathRelativePath": "", 450 | "hamlAutoOutputPathReplace1": "", 451 | "hamlAutoOutputPathReplace2": "", 452 | "hamlAutoOutputPathStyle": 0, 453 | "hamlEscapeHTMLCharacters": 0, 454 | "hamlNoEscapeInAttributes": 0, 455 | "hamlOutputFormat": 2, 456 | "hamlOutputStyle": 0, 457 | "hamlUseCDATA": 0, 458 | "hamlUseDoubleQuotes": 0, 459 | "hamlUseUnixNewlines": 0, 460 | "jadeAutoOutputPathEnabled": 1, 461 | "jadeAutoOutputPathFilenamePattern": "*.html", 462 | "jadeAutoOutputPathRelativePath": "", 463 | "jadeAutoOutputPathReplace1": "", 464 | "jadeAutoOutputPathReplace2": "", 465 | "jadeAutoOutputPathStyle": 0, 466 | "jadeCompileDebug": 1, 467 | "jadeOutputStyle": 0, 468 | "javascriptAutoOutputPathEnabled": 1, 469 | "javascriptAutoOutputPathFilenamePattern": "*-min.js", 470 | "javascriptAutoOutputPathRelativePath": "\/min", 471 | "javascriptAutoOutputPathReplace1": "", 472 | "javascriptAutoOutputPathReplace2": "", 473 | "javascriptAutoOutputPathStyle": 2, 474 | "javascriptCreateSourceMap": 1, 475 | "javascriptOutputStyle": 1, 476 | "javascriptSyntaxCheckerStyle": 1, 477 | "jsCheckerReservedNamesString": "", 478 | "jsHintFlags2": { 479 | "asi": { 480 | "active": 0, 481 | "flagValue": -1 482 | }, 483 | "bitwise": { 484 | "active": 1, 485 | "flagValue": -1 486 | }, 487 | "boss": { 488 | "active": 0, 489 | "flagValue": -1 490 | }, 491 | "browser": { 492 | "active": 1, 493 | "flagValue": -1 494 | }, 495 | "browserify": { 496 | "active": 0, 497 | "flagValue": -1 498 | }, 499 | "camelcase": { 500 | "active": 0, 501 | "flagValue": -1 502 | }, 503 | "couch": { 504 | "active": 0, 505 | "flagValue": -1 506 | }, 507 | "curly": { 508 | "active": 1, 509 | "flagValue": -1 510 | }, 511 | "debug": { 512 | "active": 0, 513 | "flagValue": -1 514 | }, 515 | "devel": { 516 | "active": 0, 517 | "flagValue": -1 518 | }, 519 | "dojo": { 520 | "active": 0, 521 | "flagValue": -1 522 | }, 523 | "elision": { 524 | "active": 1, 525 | "flagValue": -1 526 | }, 527 | "eqeqeq": { 528 | "active": 1, 529 | "flagValue": -1 530 | }, 531 | "eqnull": { 532 | "active": 0, 533 | "flagValue": -1 534 | }, 535 | "es3": { 536 | "active": 0, 537 | "flagValue": -1 538 | }, 539 | "esnext": { 540 | "active": 0, 541 | "flagValue": -1 542 | }, 543 | "evil": { 544 | "active": 0, 545 | "flagValue": -1 546 | }, 547 | "expr": { 548 | "active": 0, 549 | "flagValue": -1 550 | }, 551 | "forin": { 552 | "active": 0, 553 | "flagValue": -1 554 | }, 555 | "freeze": { 556 | "active": 1, 557 | "flagValue": -1 558 | }, 559 | "funcscope": { 560 | "active": 0, 561 | "flagValue": -1 562 | }, 563 | "futurehostile": { 564 | "active": 0, 565 | "flagValue": -1 566 | }, 567 | "globalstrict": { 568 | "active": 0, 569 | "flagValue": -1 570 | }, 571 | "immed": { 572 | "active": 0, 573 | "flagValue": -1 574 | }, 575 | "indent": { 576 | "active": 0, 577 | "flagValue": 4 578 | }, 579 | "iterator": { 580 | "active": 0, 581 | "flagValue": -1 582 | }, 583 | "jasmine": { 584 | "active": 0, 585 | "flagValue": -1 586 | }, 587 | "jquery": { 588 | "active": 1, 589 | "flagValue": -1 590 | }, 591 | "lastsemic": { 592 | "active": 0, 593 | "flagValue": -1 594 | }, 595 | "latedef": { 596 | "active": 1, 597 | "flagValue": -1 598 | }, 599 | "laxbreak": { 600 | "active": 0, 601 | "flagValue": -1 602 | }, 603 | "laxcomma": { 604 | "active": 0, 605 | "flagValue": -1 606 | }, 607 | "loopfunc": { 608 | "active": 0, 609 | "flagValue": -1 610 | }, 611 | "maxcomplexity": { 612 | "active": 0, 613 | "flagValue": 10 614 | }, 615 | "maxdepth": { 616 | "active": 0, 617 | "flagValue": 3 618 | }, 619 | "maxlen": { 620 | "active": 0, 621 | "flagValue": 150 622 | }, 623 | "maxparams": { 624 | "active": 0, 625 | "flagValue": 3 626 | }, 627 | "maxstatements": { 628 | "active": 0, 629 | "flagValue": 4 630 | }, 631 | "mocha": { 632 | "active": 0, 633 | "flagValue": -1 634 | }, 635 | "mootools": { 636 | "active": 0, 637 | "flagValue": -1 638 | }, 639 | "moz": { 640 | "active": 0, 641 | "flagValue": -1 642 | }, 643 | "multistr": { 644 | "active": 0, 645 | "flagValue": -1 646 | }, 647 | "newcap": { 648 | "active": 1, 649 | "flagValue": -1 650 | }, 651 | "noarg": { 652 | "active": 1, 653 | "flagValue": -1 654 | }, 655 | "nocomma": { 656 | "active": 0, 657 | "flagValue": -1 658 | }, 659 | "node": { 660 | "active": 0, 661 | "flagValue": -1 662 | }, 663 | "noempty": { 664 | "active": 0, 665 | "flagValue": -1 666 | }, 667 | "nonbsp": { 668 | "active": 0, 669 | "flagValue": -1 670 | }, 671 | "nonew": { 672 | "active": 1, 673 | "flagValue": -1 674 | }, 675 | "nonstandard": { 676 | "active": 0, 677 | "flagValue": -1 678 | }, 679 | "notypeof": { 680 | "active": 1, 681 | "flagValue": -1 682 | }, 683 | "noyield": { 684 | "active": 0, 685 | "flagValue": -1 686 | }, 687 | "onecase": { 688 | "active": 0, 689 | "flagValue": -1 690 | }, 691 | "phantom": { 692 | "active": 0, 693 | "flagValue": -1 694 | }, 695 | "plusplus": { 696 | "active": 0, 697 | "flagValue": -1 698 | }, 699 | "proto": { 700 | "active": 0, 701 | "flagValue": -1 702 | }, 703 | "prototypejs": { 704 | "active": 0, 705 | "flagValue": -1 706 | }, 707 | "qunit": { 708 | "active": 0, 709 | "flagValue": -1 710 | }, 711 | "regexp": { 712 | "active": 1, 713 | "flagValue": -1 714 | }, 715 | "rhino": { 716 | "active": 0, 717 | "flagValue": -1 718 | }, 719 | "scripturl": { 720 | "active": 0, 721 | "flagValue": -1 722 | }, 723 | "shadow": { 724 | "active": 0, 725 | "flagValue": -1 726 | }, 727 | "shelljs": { 728 | "active": 0, 729 | "flagValue": -1 730 | }, 731 | "singleGroups": { 732 | "active": 0, 733 | "flagValue": -1 734 | }, 735 | "strict": { 736 | "active": 0, 737 | "flagValue": -1 738 | }, 739 | "sub": { 740 | "active": 0, 741 | "flagValue": -1 742 | }, 743 | "supernew": { 744 | "active": 0, 745 | "flagValue": -1 746 | }, 747 | "typed": { 748 | "active": 0, 749 | "flagValue": -1 750 | }, 751 | "undef": { 752 | "active": 1, 753 | "flagValue": -1 754 | }, 755 | "unused": { 756 | "active": 1, 757 | "flagValue": -1 758 | }, 759 | "varstmt": { 760 | "active": 0, 761 | "flagValue": -1 762 | }, 763 | "withstmt": { 764 | "active": 0, 765 | "flagValue": -1 766 | }, 767 | "worker": { 768 | "active": 0, 769 | "flagValue": -1 770 | }, 771 | "wsh": { 772 | "active": 0, 773 | "flagValue": -1 774 | }, 775 | "yui": { 776 | "active": 0, 777 | "flagValue": -1 778 | } 779 | }, 780 | "jsLintFlags2": { 781 | "bitwise": { 782 | "active": 0, 783 | "flagValue": -1 784 | }, 785 | "browser": { 786 | "active": 1, 787 | "flagValue": -1 788 | }, 789 | "couch": { 790 | "active": 0, 791 | "flagValue": -1 792 | }, 793 | "devel": { 794 | "active": 0, 795 | "flagValue": -1 796 | }, 797 | "es6": { 798 | "active": 0, 799 | "flagValue": -1 800 | }, 801 | "eval": { 802 | "active": 0, 803 | "flagValue": -1 804 | }, 805 | "for": { 806 | "active": 0, 807 | "flagValue": -1 808 | }, 809 | "maxlen": { 810 | "active": 0, 811 | "flagValue": 150 812 | }, 813 | "node": { 814 | "active": 0, 815 | "flagValue": -1 816 | }, 817 | "this": { 818 | "active": 0, 819 | "flagValue": -1 820 | }, 821 | "white": { 822 | "active": 0, 823 | "flagValue": -1 824 | } 825 | }, 826 | "jsonAutoOutputPathEnabled": 0, 827 | "jsonAutoOutputPathFilenamePattern": "*-min.json", 828 | "jsonAutoOutputPathRelativePath": "", 829 | "jsonAutoOutputPathReplace1": "", 830 | "jsonAutoOutputPathReplace2": "", 831 | "jsonAutoOutputPathStyle": 0, 832 | "jsonOrderOutput": 0, 833 | "jsonOutputStyle": 1, 834 | "kitAutoOutputPathEnabled": 1, 835 | "kitAutoOutputPathFilenamePattern": "*.html", 836 | "kitAutoOutputPathRelativePath": "", 837 | "kitAutoOutputPathReplace1": "", 838 | "kitAutoOutputPathReplace2": "", 839 | "kitAutoOutputPathStyle": 0, 840 | "lessAllowInsecureImports": 0, 841 | "lessAutoOutputPathEnabled": 1, 842 | "lessAutoOutputPathFilenamePattern": "*.css", 843 | "lessAutoOutputPathRelativePath": "..\/css", 844 | "lessAutoOutputPathReplace1": "less", 845 | "lessAutoOutputPathReplace2": "css", 846 | "lessAutoOutputPathStyle": 2, 847 | "lessCreateSourceMap": 0, 848 | "lessDisableJavascript": 0, 849 | "lessIeCompatibility": 1, 850 | "lessOutputStyle": 0, 851 | "lessRelativeURLS": 0, 852 | "lessStrictImports": 0, 853 | "lessStrictMath": 0, 854 | "lessStrictUnits": 0, 855 | "markdownAutoOutputPathEnabled": 1, 856 | "markdownAutoOutputPathFilenamePattern": "*.html", 857 | "markdownAutoOutputPathRelativePath": "", 858 | "markdownAutoOutputPathReplace1": "", 859 | "markdownAutoOutputPathReplace2": "", 860 | "markdownAutoOutputPathStyle": 0, 861 | "markdownCriticStyle": 0, 862 | "markdownEnableFootnotes": 1, 863 | "markdownEnableLabels": 1, 864 | "markdownEnableSmartQuotes": 1, 865 | "markdownEscapeLineBreaks": 0, 866 | "markdownMaskEmailAddresses": 1, 867 | "markdownOutputFormat": 0, 868 | "markdownOutputStyle": 0, 869 | "markdownParseMetadata": 1, 870 | "markdownProcessHTML": 0, 871 | "markdownRandomFootnoteNumbers": 0, 872 | "markdownUseCompatibilityMode": 0, 873 | "reloadFileURLs": 0, 874 | "sassAutoOutputPathEnabled": 1, 875 | "sassAutoOutputPathFilenamePattern": "*.css", 876 | "sassAutoOutputPathRelativePath": "..\/css", 877 | "sassAutoOutputPathReplace1": "sass", 878 | "sassAutoOutputPathReplace2": "css", 879 | "sassAutoOutputPathStyle": 2, 880 | "sassCreateSourceMap": 0, 881 | "sassDebugStyle": 0, 882 | "sassDecimalPrecision": 10, 883 | "sassOutputStyle": 0, 884 | "sassUseLibsass": 0, 885 | "shouldRunAutoprefixer": 0, 886 | "shouldRunBless": 0, 887 | "skippedItemsString": ".svn, .git, .hg, log, _logs, _cache, cache, logs, node_modules", 888 | "slimAutoOutputPathEnabled": 1, 889 | "slimAutoOutputPathFilenamePattern": "*.html", 890 | "slimAutoOutputPathRelativePath": "", 891 | "slimAutoOutputPathReplace1": "", 892 | "slimAutoOutputPathReplace2": "", 893 | "slimAutoOutputPathStyle": 0, 894 | "slimCompileOnly": 0, 895 | "slimLogicless": 0, 896 | "slimOutputFormat": 0, 897 | "slimOutputStyle": 1, 898 | "slimRailsCompatible": 0, 899 | "stylusAutoOutputPathEnabled": 1, 900 | "stylusAutoOutputPathFilenamePattern": "*.css", 901 | "stylusAutoOutputPathRelativePath": "..\/css", 902 | "stylusAutoOutputPathReplace1": "stylus", 903 | "stylusAutoOutputPathReplace2": "css", 904 | "stylusAutoOutputPathStyle": 2, 905 | "stylusCreateSourceMap": 0, 906 | "stylusDebugStyle": 0, 907 | "stylusImportCSS": 0, 908 | "stylusOutputStyle": 0, 909 | "stylusResolveRelativeURLS": 0, 910 | "typescriptAutoOutputPathEnabled": 1, 911 | "typescriptAutoOutputPathFilenamePattern": "*.js", 912 | "typescriptAutoOutputPathRelativePath": "\/js", 913 | "typescriptAutoOutputPathReplace1": "", 914 | "typescriptAutoOutputPathReplace2": "", 915 | "typescriptAutoOutputPathStyle": 2, 916 | "typescriptCreateDeclarationFile": 0, 917 | "typescriptCreateSourceMap": 0, 918 | "typescriptJSXMode": 0, 919 | "typescriptMinifyOutput": 0, 920 | "typescriptModuleResolutionType": 0, 921 | "typescriptModuleType": 2, 922 | "typescriptNoImplicitAny": 0, 923 | "typescriptPreserveConstEnums": 0, 924 | "typescriptRemoveComments": 0, 925 | "typescriptSuppressImplicitAnyIndexErrors": 0, 926 | "typescriptTargetECMAVersion": 0, 927 | "uglifyDefinesString": "", 928 | "uglifyFlags2": { 929 | "ascii-only": { 930 | "active": 0, 931 | "flagValue": -1 932 | }, 933 | "bare-returns": { 934 | "active": 0, 935 | "flagValue": -1 936 | }, 937 | "booleans": { 938 | "active": 1, 939 | "flagValue": -1 940 | }, 941 | "bracketize": { 942 | "active": 0, 943 | "flagValue": -1 944 | }, 945 | "cascade": { 946 | "active": 1, 947 | "flagValue": -1 948 | }, 949 | "comments": { 950 | "active": 1, 951 | "flagValue": -1 952 | }, 953 | "comparisons": { 954 | "active": 1, 955 | "flagValue": -1 956 | }, 957 | "compress": { 958 | "active": 1, 959 | "flagValue": -1 960 | }, 961 | "conditionals": { 962 | "active": 1, 963 | "flagValue": -1 964 | }, 965 | "dead_code": { 966 | "active": 0, 967 | "flagValue": -1 968 | }, 969 | "drop_console": { 970 | "active": 0, 971 | "flagValue": -1 972 | }, 973 | "drop_debugger": { 974 | "active": 1, 975 | "flagValue": -1 976 | }, 977 | "eval": { 978 | "active": 0, 979 | "flagValue": -1 980 | }, 981 | "evaluate": { 982 | "active": 1, 983 | "flagValue": -1 984 | }, 985 | "hoist_funs": { 986 | "active": 1, 987 | "flagValue": -1 988 | }, 989 | "hoist_vars": { 990 | "active": 0, 991 | "flagValue": -1 992 | }, 993 | "if_return": { 994 | "active": 1, 995 | "flagValue": -1 996 | }, 997 | "indent-level": { 998 | "active": 0, 999 | "flagValue": 4 1000 | }, 1001 | "indent-start": { 1002 | "active": 0, 1003 | "flagValue": 0 1004 | }, 1005 | "inline-script": { 1006 | "active": 0, 1007 | "flagValue": -1 1008 | }, 1009 | "join_vars": { 1010 | "active": 1, 1011 | "flagValue": -1 1012 | }, 1013 | "keep_fargs": { 1014 | "active": 0, 1015 | "flagValue": -1 1016 | }, 1017 | "keep_fnames": { 1018 | "active": 0, 1019 | "flagValue": -1 1020 | }, 1021 | "loops": { 1022 | "active": 1, 1023 | "flagValue": -1 1024 | }, 1025 | "mangle": { 1026 | "active": 1, 1027 | "flagValue": -1 1028 | }, 1029 | "max-line-len": { 1030 | "active": 1, 1031 | "flagValue": 32000 1032 | }, 1033 | "negate_iife": { 1034 | "active": 1, 1035 | "flagValue": -1 1036 | }, 1037 | "properties": { 1038 | "active": 1, 1039 | "flagValue": -1 1040 | }, 1041 | "pure_getters": { 1042 | "active": 0, 1043 | "flagValue": -1 1044 | }, 1045 | "quote-keys": { 1046 | "active": 0, 1047 | "flagValue": -1 1048 | }, 1049 | "screw-ie8": { 1050 | "active": 0, 1051 | "flagValue": -1 1052 | }, 1053 | "semicolons": { 1054 | "active": 1, 1055 | "flagValue": -1 1056 | }, 1057 | "sequences": { 1058 | "active": 1, 1059 | "flagValue": -1 1060 | }, 1061 | "sort": { 1062 | "active": 0, 1063 | "flagValue": -1 1064 | }, 1065 | "space-colon": { 1066 | "active": 1, 1067 | "flagValue": -1 1068 | }, 1069 | "toplevel": { 1070 | "active": 0, 1071 | "flagValue": -1 1072 | }, 1073 | "unsafe": { 1074 | "active": 0, 1075 | "flagValue": -1 1076 | }, 1077 | "unused": { 1078 | "active": 0, 1079 | "flagValue": -1 1080 | }, 1081 | "warnings": { 1082 | "active": 0, 1083 | "flagValue": -1 1084 | }, 1085 | "width": { 1086 | "active": 1, 1087 | "flagValue": 80 1088 | } 1089 | }, 1090 | "uglifyReservedNamesString": "$", 1091 | "websiteRelativeRoot": "" 1092 | }, 1093 | "settingsFileVersion": "2" 1094 | } -------------------------------------------------------------------------------- /assets/css/style.css: -------------------------------------------------------------------------------- 1 | input[type=button], .button { 2 | -webkit-appearance: none; 3 | display: inline-block; 4 | padding: 10px; 5 | background-color: #666; 6 | border: none; 7 | border-radius: 5px; 8 | box-sizing: border-box; 9 | color: #ececec; 10 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.5); 11 | font-size: 1em; } 12 | input[type=button]:active input[type=button].enabled, .button:active input[type=button].enabled, input[type=button]:active .button.enabled, .button:active .button.enabled { 13 | background-color: #00a896; 14 | box-shadow: inset 0px 3px 2px 0px rgba(0, 0, 0, 0.28); } 15 | body.light input[type=button], body.light .button { 16 | background-color: #159386; } 17 | body.light input[type=button]:active, body.light input[type=button].enabled, body.light .button:active, body.light .button.enabled { 18 | background-color: #00a896; 19 | box-shadow: inset 0px 3px 2px 0px rgba(0, 0, 0, 0.28); } 20 | body.dark input[type=button], body input[type=button], body.dark .button, body .button { 21 | background-color: #b8901a; } 22 | body.dark input[type=button]:active, body input[type=button]:active, body.dark input[type=button].enabled, body input[type=button].enabled, body.dark .button:active, body .button:active, body.dark .button.enabled, body .button.enabled { 23 | background-color: #d29d00; 24 | box-shadow: inset 0px 3px 2px 0px rgba(0, 0, 0, 0.28); } 25 | 26 | span.listen-button img, span.listen-button svg { 27 | height: 20px; } 28 | body.dark span.listen-button img *, body span.listen-button img *, body.dark span.listen-button svg *, body span.listen-button svg * { 29 | stroke: #93a1a1; } 30 | 31 | .svg-show svg, .svg-hide svg, .svg-listen svg, .svg-ignore svg { 32 | height: 20px; } 33 | .svg-show svg *, .svg-hide svg *, .svg-listen svg *, .svg-ignore svg * { 34 | stroke: #586e75; } 35 | body.dark .svg-show svg *, body .svg-show svg *, body.dark .svg-hide svg *, body .svg-hide svg *, body.dark .svg-listen svg *, body .svg-listen svg *, body.dark .svg-ignore svg *, body .svg-ignore svg * { 36 | stroke: #93a1a1; } 37 | 38 | .svg-listen, .svg-ignore { 39 | margin-left: 10px; } 40 | 41 | #choice-dark-mode, #choice-light-mode { 42 | float: right; } 43 | 44 | @font-face { 45 | font-family: 'bitstream_vera_sansroman'; 46 | src: url("/fonts/Vera-webfont.woff") format("woff"), url("/fonts/Vera.ttf") format("truetype"); 47 | font-weight: normal; 48 | font-style: normal; } 49 | @font-face { 50 | font-family: 'courier_prime_coderegular'; 51 | src: url("/fonts/courier_prime_code-webfont.woff2") format("woff2"), url("/fonts/courier_prime_code-webfont.woff") format("woff"); 52 | font-weight: normal; 53 | font-style: normal; } 54 | body { 55 | font-family: "bitstream_vera_sansroman", Arial, sans-serif; 56 | -webkit-tap-highlight-color: transparent !important; } 57 | 58 | input[type="text"] { 59 | font-family: "courier_prime_coderegular", monospace; } 60 | 61 | .logo { 62 | max-width: 50px; } 63 | 64 | .dir-container { 65 | padding: 4px; 66 | box-sizing: border-box; 67 | border-radius: 20px; 68 | margin-top: 20px; 69 | border: 2px solid; 70 | font-size: 120%; } 71 | @media screen and (min-width: 866px) { 72 | .dir-container { 73 | padding: 10px; } } 74 | .dir-container header { 75 | grid-area: header; } 76 | .dir-container header span { 77 | padding-left: 10px; } 78 | .dir-container > div { 79 | display: block; } 80 | @media screen and (min-width: 866px) { 81 | .dir-container > div { 82 | font-size: 80%; 83 | display: grid; 84 | grid-template-areas: "header header"; } } 85 | @media screen and (min-width: 1600px) { 86 | .dir-container > div { 87 | font-size: 80%; 88 | display: grid; 89 | grid-template-areas: "header header header"; } } 90 | .dir-container span { 91 | display: inline-block; } 92 | 93 | .node { 94 | display: flex; 95 | margin: 4px; 96 | padding: 4px; 97 | border-radius: 10px; } 98 | @media screen and (min-width: 866px) { 99 | .node { 100 | margin: 10px; 101 | padding: 10px; } } 102 | .node > div { 103 | width: 100%; } 104 | .node > div .control { 105 | display: flex; 106 | justify-content: center; } 107 | .node > div .control.kind_slider { 108 | display: block; } 109 | .node > div .control.kind_slider .rangeSlider__horizontal { 110 | margin-top: 20px; } 111 | .node > div .control input { 112 | flex: 1; } 113 | @media screen and (min-width: 866px) { 114 | .node > div { 115 | display: grid; 116 | grid-template-areas: "name . path " " . . description" "controls controls controls"; } } 117 | .node .group { 118 | grid-area: controls; 119 | display: flex; 120 | flex-wrap: wrap; } 121 | .node .dir-name { 122 | grid-area: name; } 123 | .node .full-path { 124 | grid-column-start: 3; 125 | text-align: right; 126 | grid-area: path; } 127 | .node .description { 128 | grid-column-start: 3; 129 | text-align: right; 130 | grid-area: description; } 131 | .node .control { 132 | flex: 1; 133 | display: flex; } 134 | 135 | .node .control-name, .node .full-path, .node .description { 136 | font-size: 12px; } 137 | @media screen and (min-width: 866px) { 138 | .node .control-name, .node .full-path, .node .description { 139 | font-size: 16px; } } 140 | .node .control { 141 | padding: 10px; 142 | box-sizing: border-box; 143 | border-radius: 10px; 144 | margin: 4px; 145 | box-shadow: 0px 6px 0px 0px rgba(70, 75, 117, 0.7); 146 | color: #fff; } 147 | @media screen and (min-width: 866px) { 148 | .node .control { 149 | margin: 20px; } } 150 | .node .control input[type="text"] { 151 | font-size: 20px; 152 | line-height: 26px; 153 | padding: 5px; 154 | box-sizing: border-box; 155 | max-width: 220px; } 156 | @media screen and (min-width: 866px) { 157 | .node .control input[type="text"] { 158 | max-width: none; } } 159 | .node .control .curr-range-val { 160 | font-size: 10px; 161 | align-self: center; } 162 | .node .control span.curr-val { 163 | margin-left: 10px; } 164 | .node .kind_char { 165 | flex: none; 166 | margin-left: auto; 167 | margin-right: auto; } 168 | .node .kind_dropdown { 169 | flex: none; } 170 | .node .kind_slider { 171 | flex: 1; 172 | min-width: 200px; } 173 | .node .rangeSlider__horizontal { 174 | flex: 1; 175 | flex-grow: 4; 176 | align-self: center; 177 | margin-right: 20px; } 178 | .node select { 179 | font-size: 20px; } 180 | .node .kind_color { 181 | flex: none; 182 | margin: 0; 183 | margin-left: auto; 184 | margin-right: auto; } 185 | .node .kind_color .layout_default.picker_wrapper { 186 | width: 18em; } 187 | @media screen and (min-width: 866px) { 188 | .node .kind_color .layout_default.picker_wrapper { 189 | width: 15em; } 190 | .node .kind_color .layout_default.picker_wrapper .picker_sample { 191 | order: 0; } } 192 | .node .kind_color .layout_default.picker_wrapper .picker_done { 193 | display: none; } 194 | 195 | .rangeSlider, .rangeSlider__fill { 196 | display: block; 197 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 198 | border-radius: 10px; } 199 | 200 | .rangeSlider { 201 | position: relative; 202 | background: #7f8c8d; } 203 | 204 | .rangeSlider__horizontal { 205 | height: 20px; 206 | width: 100%; } 207 | 208 | .rangeSlider__vertical { 209 | height: 100%; 210 | width: 20px; } 211 | 212 | .rangeSlider--disabled { 213 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 214 | opacity: 0.4; } 215 | 216 | .rangeSlider__fill { 217 | background: #16a085; 218 | position: absolute; 219 | z-index: 2; } 220 | 221 | .rangeSlider__fill__horizontal { 222 | height: 100%; 223 | top: 0; 224 | left: 0; } 225 | 226 | .rangeSlider__fill__vertical { 227 | width: 100%; 228 | bottom: 0; 229 | left: 0; } 230 | 231 | .rangeSlider__handle { 232 | border: 1px solid #ccc; 233 | cursor: pointer; 234 | display: inline-block; 235 | width: 40px; 236 | height: 40px; 237 | position: absolute; 238 | z-index: 3; 239 | background: white linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1)); 240 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); 241 | border-radius: 50%; } 242 | 243 | .rangeSlider__handle__horizontal { 244 | top: -10px; } 245 | 246 | .rangeSlider__handle__vertical { 247 | left: -10px; 248 | bottom: 0; } 249 | 250 | .rangeSlider__handle:after { 251 | content: ""; 252 | display: block; 253 | width: 18px; 254 | height: 18px; 255 | margin: auto; 256 | position: absolute; 257 | top: 0; 258 | right: 0; 259 | bottom: 0; 260 | left: 0; 261 | background-image: linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0)); 262 | border-radius: 50%; } 263 | 264 | .rangeSlider__handle:active { 265 | background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12)); } 266 | 267 | input[type="range"]:focus + .rangeSlider .rangeSlider__handle { 268 | box-shadow: 0 0 8px rgba(142, 68, 173, 0.9); } 269 | 270 | .rangeSlider__buffer { 271 | z-index: 1; 272 | position: absolute; 273 | top: 3px; 274 | height: 14px; 275 | background: #2c3e50; 276 | border-radius: 10px; } 277 | 278 | body.dark, body { 279 | background-color: #262626; 280 | color: #839496; } 281 | body.dark *, body * { 282 | border-color: #93a1a1; } 283 | 284 | body.light { 285 | background-color: #fffdf6; 286 | color: #657b83; } 287 | body.light * { 288 | border-color: #586e75; } 289 | 290 | body.light .node { 291 | background-color: rgba(0, 0, 0, 0.1); } 292 | body.dark .node, body .node { 293 | background-color: rgba(255, 255, 255, 0.1); } 294 | 295 | body.light .node .control { 296 | background-color: rgba(0, 0, 0, 0.2); } 297 | body.dark .node .control, body .node .control { 298 | background-color: rgba(255, 255, 255, 0.2); } 299 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/favicon.ico -------------------------------------------------------------------------------- /assets/fonts/Vera-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/fonts/Vera-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/fonts/Vera.ttf -------------------------------------------------------------------------------- /assets/fonts/courier_prime_code-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/fonts/courier_prime_code-webfont.woff -------------------------------------------------------------------------------- /assets/fonts/courier_prime_code-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/fonts/courier_prime_code-webfont.woff2 -------------------------------------------------------------------------------- /assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/img/icon.png -------------------------------------------------------------------------------- /assets/img/listen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/img/listen.png -------------------------------------------------------------------------------- /assets/img/listen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 12 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /assets/img/listen_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 12 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /assets/img/pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/img/pressed.png -------------------------------------------------------------------------------- /assets/img/pressed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | -------------------------------------------------------------------------------- /assets/img/pressed_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | -------------------------------------------------------------------------------- /assets/img/switch_off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /assets/img/switch_on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | Untitled 10 | Created with Sketch. 11 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /assets/img/toggle-minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/img/toggle-minus.png -------------------------------------------------------------------------------- /assets/img/toggle-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | Group 9 | Created with Sketch. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/img/toggle-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/img/toggle-plus.png -------------------------------------------------------------------------------- /assets/img/toggle-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | Group 9 | Created with Sketch. 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/sass/.sass-cache/351e40b2ba7f0e6bb51eb64ff568952c6994d0cc/_color-picker.scssc: -------------------------------------------------------------------------------- 1 | 3.4.21 (Selective Steve) 2 | 692ef7eb982d75461caacb403f09ea7358c1c949 3 | o:Sass::Tree::RootNode :@children[o:Sass::Tree::CommentNode : @value[I"f/* #colorPicker { 4 | * position: absolute; 5 | * display: none; 6 | * width: 200px; 7 | * background-color: #ccc; 8 | * padding: 8px; 9 | * } 10 | * 11 | * #picker-wrapper { 12 | * position: relative; 13 | * float: left; 14 | * width: 160px; 15 | * } 16 | * 17 | * #slider-wrapper { 18 | * position: relative; 19 | * float: left; 20 | * width: 20px; 21 | * margin-left: 12px; 22 | * } 23 | * 24 | * .error { 25 | * color: red; 26 | * font-weight: bold; 27 | * } 28 | * 29 | * #colorPicker-clear { 30 | * clear: both; 31 | * } 32 | * 33 | * .color-control { 34 | * width: 60px; 35 | * height: 40px; 36 | * margin: 3px; 37 | * border: solid 3px #ccc; 38 | * } 39 | * input[type="color"] { 40 | * height: 50px; 41 | * } */:ET: 42 | @type: silent;[: 43 | @linei:@source_rangeo:Sass::Source::Range :@start_poso:Sass::Source::Position; i: @offseti: @end_poso;; i,;i 44 | : 45 | @fileI"T/Users/wwiggins/Development/oscqueryhtml/assets/sass/modules/_color-picker.scss; T:@importero: Sass::Importers::Filesystem: 46 | @rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@real_rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@same_name_warningso:Set: 47 | @hash}F: @options{:@templateI"d// #colorPicker { 48 | // position: absolute; 49 | // display: none; 50 | // width: 200px; 51 | // background-color: #ccc; 52 | // padding: 8px; 53 | // } 54 | // 55 | // #picker-wrapper { 56 | // position: relative; 57 | // float: left; 58 | // width: 160px; 59 | // } 60 | // 61 | // #slider-wrapper { 62 | // position: relative; 63 | // float: left; 64 | // width: 20px; 65 | // margin-left: 12px; 66 | // } 67 | // 68 | // .error { 69 | // color: red; 70 | // font-weight: bold; 71 | // } 72 | // 73 | // #colorPicker-clear { 74 | // clear: both; 75 | // } 76 | // 77 | // .color-control { 78 | // width: 60px; 79 | // height: 40px; 80 | // margin: 3px; 81 | // border: solid 3px #ccc; 82 | // } 83 | // input[type="color"] { 84 | // height: 50px; 85 | // } 86 | ; T; i; o; ;o;; i;i;o;; i;i;@;@:@has_childrenT;@ -------------------------------------------------------------------------------- /assets/sass/.sass-cache/351e40b2ba7f0e6bb51eb64ff568952c6994d0cc/_control.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/351e40b2ba7f0e6bb51eb64ff568952c6994d0cc/_control.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/351e40b2ba7f0e6bb51eb64ff568952c6994d0cc/_range-slider.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/351e40b2ba7f0e6bb51eb64ff568952c6994d0cc/_range-slider.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_buttons.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_buttons.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_global-variables.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_global-variables.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_responsive.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/a21e1ca9377d1d7ec39004ebd8227e082064c748/_responsive.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_color-schemes.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_color-schemes.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_foundation.scssc: -------------------------------------------------------------------------------- 1 | 3.4.21 (Selective Steve) 2 | 292cce7f5f4c483db679d7ea896f77be94483659 3 | o:Sass::Tree::RootNode :@children[o:Sass::Tree::ImportNode :@imported_filenameI" foundation/global-variables:ET;[:@template0: 4 | @linei:@source_rangeo:Sass::Source::Range :@start_poso:Sass::Source::Position; i: @offseti: @end_poso;; i;i+: 5 | @fileI"J/Users/wwiggins/Development/oscqueryhtml/assets/sass/_foundation.scss; T:@importero: Sass::Importers::Filesystem: 6 | @rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@real_rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@same_name_warningso:Set: 7 | @hash}F: @options{:@imported_file0o; ;I"foundation/buttons; T;[; 8 | 0; i; o; ;o;; i;i;o;; i;i";@ ;@;@;0o; ;I"foundation/responsive; T;[; 9 | 0; i; o; ;o;; i;i;o;; i;i%;@ ;@;@;0; 10 | I"k@import "foundation/global-variables"; 11 | @import "foundation/buttons"; 12 | @import "foundation/responsive"; 13 | ; T; i; o; ;o;; i;i;o;; i;i;@ ;@:@has_childrenT;@ -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_layout.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_layout.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_legacy.scssc: -------------------------------------------------------------------------------- 1 | 3.4.21 (Selective Steve) 2 | f58a36ee95227541a638e460f70bcd55578d128d 3 | o:Sass::Tree::RootNode :@children[o:Sass::Tree::CommentNode : @value[I"t/* This used to be where the initial dev styles went, but you can use it to add override styles for testing. */:ET: 4 | @type: silent;[: 5 | @linei:@source_rangeo:Sass::Source::Range :@start_poso:Sass::Source::Position; i: @offseti: @end_poso;; i;ir: 6 | @fileI"F/Users/wwiggins/Development/oscqueryhtml/assets/sass/_legacy.scss; T:@importero: Sass::Importers::Filesystem: 7 | @rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@real_rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@same_name_warningso:Set: 8 | @hash}F: @options{:@templateI"r// This used to be where the initial dev styles went, but you can use it to add override styles for testing. 9 | ; T; i; o; ;o;; i;i;o;; i;i;@;@:@has_childrenT;@ -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_modules.scssc: -------------------------------------------------------------------------------- 1 | 3.4.21 (Selective Steve) 2 | c31de0ca0985fbd2b2b1945c2e3b1bc2b3ae55be 3 | o:Sass::Tree::RootNode :@children[o:Sass::Tree::ImportNode :@imported_filenameI"modules/control:ET;[:@template0: 4 | @linei:@source_rangeo:Sass::Source::Range :@start_poso:Sass::Source::Position; i: @offseti: @end_poso;; i;i: 5 | @fileI"G/Users/wwiggins/Development/oscqueryhtml/assets/sass/_modules.scss; T:@importero: Sass::Importers::Filesystem: 6 | @rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@real_rootI"9/Users/wwiggins/Development/oscqueryhtml/assets/sass; T:@same_name_warningso:Set: 7 | @hash}F: @options{:@imported_file0o; ;I"modules/color-picker; T;[; 8 | 0; i; o; ;o;; i;i;o;; i;i$;@ ;@;@;0o; ;I"modules/range-slider; T;[; 9 | 0; i; o; ;o;; i;i;o;; i;i$;@ ;@;@;0; 10 | I"`@import "modules/control"; 11 | @import "modules/color-picker"; 12 | @import "modules/range-slider"; 13 | ; T; i; o; ;o;; i;i;o;; i;i;@ ;@:@has_childrenT;@ -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_type.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/_type.scssc -------------------------------------------------------------------------------- /assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/style.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/assets/sass/.sass-cache/f45f0ef2d7303c23fa214de2852f85111b601964/style.scssc -------------------------------------------------------------------------------- /assets/sass/_color-schemes.scss: -------------------------------------------------------------------------------- 1 | //color variables are in foundation/global-variables 2 | 3 | body.dark { 4 | background-color: $background-color-dark; 5 | color: $content-color-dark; 6 | * { 7 | border-color: $content-color-dark-emphasis; 8 | } 9 | } 10 | 11 | body.light { 12 | background-color: $background-color-light; 13 | color: $content-color-light; 14 | * { 15 | border-color: $content-color-light-emphasis; 16 | } 17 | } 18 | 19 | .node { 20 | body.light & { 21 | background-color: $node-background-color-light; 22 | } 23 | body.dark & { 24 | background-color: $node-background-color-dark; 25 | } 26 | } 27 | 28 | 29 | .node .control { 30 | body.light & { 31 | background-color: rgba(0, 0, 0, 0.2); 32 | } 33 | body.dark & { 34 | background-color: rgba(255, 255, 255, 0.2); 35 | } 36 | } 37 | 38 | 39 | // you can force a color scheme here 40 | body { 41 | @extend body.dark; 42 | } 43 | -------------------------------------------------------------------------------- /assets/sass/_foundation.scss: -------------------------------------------------------------------------------- 1 | @import "foundation/global-variables"; 2 | @import "foundation/buttons"; 3 | @import "foundation/responsive"; 4 | -------------------------------------------------------------------------------- /assets/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | max-width: 50px; 3 | } 4 | 5 | 6 | .dir-container { 7 | padding: 4px; 8 | @include media-query($on-palm) { 9 | padding: $unit; 10 | } 11 | box-sizing: border-box; 12 | border-radius: $unit * 2; 13 | margin-top: $unit * 2; 14 | border: 2px solid; 15 | font-size: 120%; 16 | header { 17 | grid-area: header; 18 | span { 19 | padding-left: $unit; 20 | } 21 | } 22 | > div { 23 | display: block; 24 | @include media-query($on-palm) { 25 | font-size: 80%; 26 | display: grid; 27 | grid-template-areas: "header header" 28 | 29 | } 30 | @include media-query($bigscreen) { 31 | font-size: 80%; 32 | display: grid; 33 | grid-template-areas: "header header header" 34 | 35 | } 36 | } 37 | span { 38 | display: inline-block; 39 | } 40 | } 41 | 42 | .node { 43 | display: flex; 44 | margin: 4px; 45 | padding: 4px; 46 | border-radius: $unit; 47 | @include media-query($on-palm) { 48 | margin: $unit; 49 | padding: $unit; 50 | } 51 | > div { 52 | width: 100%; 53 | .control { 54 | display: flex; 55 | justify-content: center; 56 | &.kind_slider { 57 | display: block; 58 | .rangeSlider__horizontal { 59 | margin-top: 20px; 60 | } 61 | } 62 | //align-items: center; 63 | 64 | input { 65 | flex: 1; 66 | } 67 | } 68 | @include media-query($on-palm) { 69 | display: grid; 70 | grid-template-areas: "name . path " 71 | " . . description" 72 | "controls controls controls" 73 | 74 | } 75 | } 76 | .group { 77 | grid-area: controls; 78 | display: flex; 79 | flex-wrap: wrap; 80 | } 81 | .dir-name { 82 | grid-area: name; 83 | } 84 | .full-path { 85 | grid-column-start: 3; 86 | text-align: right; 87 | grid-area: path; 88 | } 89 | .description { 90 | grid-column-start: 3; 91 | text-align: right; 92 | grid-area: description; 93 | } 94 | .control { 95 | flex: 1; 96 | display: flex; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /assets/sass/_legacy.scss: -------------------------------------------------------------------------------- 1 | // This used to be where the initial dev styles went, but you can use it to add override styles for testing. 2 | -------------------------------------------------------------------------------- /assets/sass/_modules.scss: -------------------------------------------------------------------------------- 1 | @import "modules/control"; 2 | @import "modules/color-picker"; 3 | @import "modules/range-slider"; 4 | -------------------------------------------------------------------------------- /assets/sass/_type.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'bitstream_vera_sansroman'; 3 | src: url('/fonts/Vera-webfont.woff') format('woff'), 4 | url('/fonts/Vera.ttf') format('truetype'); 5 | font-weight: normal; 6 | font-style: normal; 7 | 8 | } 9 | 10 | @font-face { 11 | font-family: 'courier_prime_coderegular'; 12 | src: url('/fonts/courier_prime_code-webfont.woff2') format('woff2'), 13 | url('/fonts/courier_prime_code-webfont.woff') format('woff'); 14 | font-weight: normal; 15 | font-style: normal; 16 | 17 | } 18 | 19 | body { 20 | font-family: $base-font-family; 21 | -webkit-tap-highlight-color: transparent !important; 22 | } 23 | 24 | input[type="text"] { 25 | font-family: $monospace-font-family; 26 | } 27 | -------------------------------------------------------------------------------- /assets/sass/foundation/_buttons.scss: -------------------------------------------------------------------------------- 1 | $button-default-background-color: #666; 2 | $button-light-background-color: $teal-color; 3 | $button-dark-background-color: $yellow-color; 4 | $button-default-text-color: $white-color; 5 | $button-default-active-color: $teal-color; 6 | 7 | @mixin button-base-style { 8 | -webkit-appearance: none; 9 | display: inline-block; 10 | padding: $unit; 11 | background-color: $button-default-background-color; 12 | border: none; 13 | border-radius: $vv-inner-border-radius; 14 | box-sizing: border-box; 15 | color: $button-default-text-color; 16 | text-shadow: 0 1px 1px rgba(0, 0, 0, .5); 17 | font-size: 1em; 18 | &:active &.enabled { 19 | background-color: $button-default-active-color; 20 | box-shadow: inset 0px 3px 2px 0px rgba(0,0,0,0.28); 21 | } 22 | body.light & { 23 | background-color: desaturate($button-light-background-color, 25%); 24 | &:active, &.enabled { 25 | background-color: saturate($button-light-background-color, 25%); 26 | box-shadow: inset 0px 3px 2px 0px rgba(0,0,0,0.28); 27 | } 28 | } 29 | body.dark & { 30 | background-color: desaturate($button-dark-background-color, 25%); 31 | &:active, &.enabled { 32 | background-color: saturate($button-dark-background-color, 25%); 33 | box-shadow: inset 0px 3px 2px 0px rgba(0,0,0,0.28); 34 | } 35 | } 36 | } 37 | 38 | input[type=button], .button { 39 | @include button-base-style; 40 | } 41 | 42 | span.listen-button { 43 | img, svg { 44 | height: 20px; 45 | 46 | body.dark & { 47 | * { 48 | stroke: $content-color-dark-emphasis; 49 | } 50 | } 51 | } 52 | } 53 | .svg-show, .svg-hide, .svg-listen, .svg-ignore { 54 | svg { 55 | height: 20px; 56 | * { 57 | stroke: $content-color-light-emphasis; 58 | } 59 | body.dark & { 60 | * { 61 | stroke: $content-color-dark-emphasis; 62 | } 63 | } 64 | } 65 | } 66 | 67 | .svg-listen, .svg-ignore { 68 | margin-left: $unit; 69 | } 70 | 71 | #choice-dark-mode, #choice-light-mode { 72 | float: right; 73 | } 74 | -------------------------------------------------------------------------------- /assets/sass/foundation/_global-variables.scss: -------------------------------------------------------------------------------- 1 | $base-font-family: 'bitstream_vera_sansroman', Arial, sans-serif; 2 | $monospace-font-family: 'courier_prime_coderegular', monospace; 3 | 4 | $background-color-dark: #262626; 5 | $node-background-color-dark: rgba(255, 255, 255, 0.1); 6 | $content-color-dark: #839496; 7 | $content-color-dark-emphasis: #93a1a1; 8 | 9 | $background-color-light: #fffdf6; 10 | $node-background-color-light: rgba(0, 0, 0, 0.1); 11 | $content-color-light: #657b83; 12 | $content-color-light-emphasis: #586e75; 13 | 14 | $control-data-text-color: #fff; 15 | 16 | $yellow-color: #d29d00; 17 | $magenta-color: #ad0084; 18 | $teal-color: #00a896; 19 | $blue-color: #0055a8; 20 | $white-color: #ececec; 21 | 22 | $unit: 10px; 23 | 24 | $vv-base-border-radius: $unit; 25 | $vv-inner-border-radius: $vv-base-border-radius /2; 26 | -------------------------------------------------------------------------------- /assets/sass/foundation/_responsive.scss: -------------------------------------------------------------------------------- 1 | $on-palm: 866px; 2 | $on-laptop: 1200px; 3 | $bigscreen: 1600px; 4 | 5 | 6 | 7 | @mixin media-query($device) { 8 | @media screen and (min-width: $device) { 9 | @content; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/sass/modules/_color-picker.scss: -------------------------------------------------------------------------------- 1 | // #colorPicker { 2 | // position: absolute; 3 | // display: none; 4 | // width: 200px; 5 | // background-color: #ccc; 6 | // padding: 8px; 7 | // } 8 | // 9 | // #picker-wrapper { 10 | // position: relative; 11 | // float: left; 12 | // width: 160px; 13 | // } 14 | // 15 | // #slider-wrapper { 16 | // position: relative; 17 | // float: left; 18 | // width: 20px; 19 | // margin-left: 12px; 20 | // } 21 | // 22 | // .error { 23 | // color: red; 24 | // font-weight: bold; 25 | // } 26 | // 27 | // #colorPicker-clear { 28 | // clear: both; 29 | // } 30 | // 31 | // .color-control { 32 | // width: 60px; 33 | // height: 40px; 34 | // margin: 3px; 35 | // border: solid 3px #ccc; 36 | // } 37 | // input[type="color"] { 38 | // height: 50px; 39 | // } 40 | -------------------------------------------------------------------------------- /assets/sass/modules/_control.scss: -------------------------------------------------------------------------------- 1 | .node { 2 | .control-name, .full-path, .description { 3 | font-size: 12px; 4 | @include media-query($on-palm) { 5 | font-size: 16px; 6 | } 7 | } 8 | .control { 9 | padding: $unit; 10 | box-sizing: border-box; 11 | border-radius: $vv-base-border-radius; 12 | margin: 4px; 13 | @include media-query($on-palm) { 14 | margin: $unit * 2; 15 | } 16 | box-shadow: 0px 6px 0px 0px rgba(70,75,117,0.7); 17 | color: $control-data-text-color; 18 | input[type="text"] { 19 | font-size: 20px; 20 | line-height: 26px; 21 | padding: 5px; 22 | box-sizing: border-box; 23 | max-width: 220px; 24 | @include media-query($on-palm) { 25 | max-width: none; 26 | } 27 | } 28 | .curr-range-val { 29 | font-size: 10px; 30 | align-self: center; 31 | } 32 | span.curr-val { 33 | margin-left: $unit; 34 | } 35 | } 36 | .kind_char { 37 | flex: none; 38 | margin-left: auto; 39 | margin-right: auto; 40 | } 41 | 42 | .kind_dropdown{ 43 | flex: none; 44 | } 45 | 46 | .kind_slider { 47 | flex: 1; 48 | min-width: 200px; 49 | } 50 | .rangeSlider__horizontal { 51 | flex: 1; 52 | flex-grow: 4; 53 | align-self: center; 54 | margin-right: 20px; 55 | } 56 | select { 57 | font-size: 20px; 58 | } 59 | .kind_color { 60 | flex: none; 61 | 62 | margin: 0; 63 | margin-left: auto; 64 | margin-right: auto; 65 | .layout_default.picker_wrapper { 66 | width: 18em; 67 | @include media-query($on-palm) { 68 | width: 15em; 69 | .picker_sample { 70 | order: 0; 71 | } 72 | } 73 | .picker_done { 74 | display: none; 75 | } 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /assets/sass/modules/_range-slider.scss: -------------------------------------------------------------------------------- 1 | .rangeSlider, .rangeSlider__fill { 2 | display: block; 3 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); 4 | border-radius: 10px; 5 | } 6 | 7 | .rangeSlider { 8 | position: relative; 9 | background: #7f8c8d; 10 | } 11 | 12 | .rangeSlider__horizontal { 13 | height: 20px; 14 | width: 100%; 15 | } 16 | 17 | .rangeSlider__vertical { 18 | height: 100%; 19 | width: 20px; 20 | } 21 | 22 | .rangeSlider--disabled { 23 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 24 | opacity: 0.4; 25 | } 26 | 27 | .rangeSlider__fill { 28 | background: #16a085; 29 | position: absolute; 30 | z-index: 2; 31 | } 32 | 33 | .rangeSlider__fill__horizontal { 34 | height: 100%; 35 | top: 0; 36 | left: 0; 37 | } 38 | 39 | .rangeSlider__fill__vertical { 40 | width: 100%; 41 | bottom: 0; 42 | left: 0; 43 | } 44 | 45 | .rangeSlider__handle { 46 | border: 1px solid #ccc; 47 | cursor: pointer; 48 | display: inline-block; 49 | width: 40px; 50 | height: 40px; 51 | position: absolute; 52 | z-index: 3; 53 | background: white linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1)); 54 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.3); 55 | border-radius: 50%; 56 | } 57 | 58 | .rangeSlider__handle__horizontal { 59 | top: -10px; 60 | } 61 | 62 | .rangeSlider__handle__vertical { 63 | left: -10px; 64 | bottom: 0; 65 | } 66 | 67 | .rangeSlider__handle:after { 68 | content: ""; 69 | display: block; 70 | width: 18px; 71 | height: 18px; 72 | margin: auto; 73 | position: absolute; 74 | top: 0; 75 | right: 0; 76 | bottom: 0; 77 | left: 0; 78 | background-image: linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0)); 79 | border-radius: 50%; 80 | } 81 | 82 | .rangeSlider__handle:active { 83 | background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12)); 84 | } 85 | 86 | input[type="range"]:focus + .rangeSlider .rangeSlider__handle { 87 | box-shadow: 0 0 8px rgba(142, 68, 173, 0.9); 88 | } 89 | 90 | .rangeSlider__buffer { 91 | z-index: 1; 92 | position: absolute; 93 | top: 3px; 94 | height: 14px; 95 | background: #2c3e50; 96 | border-radius: 10px; 97 | } 98 | -------------------------------------------------------------------------------- /assets/sass/style.scss: -------------------------------------------------------------------------------- 1 | @import 2 | "foundation", 3 | "type", 4 | "layout", 5 | "modules", 6 | "color-schemes", 7 | "legacy" 8 | 9 | ; 10 | -------------------------------------------------------------------------------- /fonts/Vera-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/fonts/Vera-webfont.woff -------------------------------------------------------------------------------- /fonts/Vera.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/fonts/Vera.ttf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osc-tool", 3 | "version": "1.0.0", 4 | "description": "Process OSC query responses", 5 | "private": true, 6 | "scripts": { 7 | "test": "mocha-webpack -r jsdom-global/register \"test/**/*.js\"", 8 | "build": "webpack", 9 | "dev": "webpack --watch" 10 | }, 11 | "author": "Dustin Long ", 12 | "license": "ISC", 13 | "dependencies": { 14 | "base64-image-loader": "^1.2.1", 15 | "ejs": "^2.6.1", 16 | "express": "^4.16.3", 17 | "osc": "^2.2.3", 18 | "rangeslider-pure": "^0.4.5-1", 19 | "vanilla-picker": "^2.2.1", 20 | "whatwg-fetch": "^2.0.4" 21 | }, 22 | "devDependencies": { 23 | "jsdom": "^11.11.0", 24 | "jsdom-global": "^3.0.2", 25 | "mocha": "^5.2.0", 26 | "mocha-webpack": "^2.0.0-beta.0", 27 | "osc-js": "^1.2.2", 28 | "svg-inline-loader": "^0.8.0", 29 | "webpack": "^4.16.1", 30 | "webpack-cli": "^2.1.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/builder.js: -------------------------------------------------------------------------------- 1 | const toggleMinusSvg = require("svg-inline-loader?classPrefix=_minus!../assets/img/toggle-minus.svg"); 2 | const togglePlusSvg = require("svg-inline-loader?classPrefix=_plus!../assets/img/toggle-plus.svg"); 3 | 4 | const types = require('./types.js'); 5 | 6 | const DEFAULT_COLOR_ELEM_VALUE = '#4466ff'; 7 | 8 | // Build controls recursively based upon the json, and append to the parent. 9 | function buildContentsAddToContainer(contents, parentContainer, cfg) { 10 | let dirNames = Object.keys(contents); 11 | dirNames.sort(); 12 | for (let j = 0; j < dirNames.length; j++) { 13 | let nodeName = dirNames[j]; 14 | let dirObj = contents[dirNames[j]]; 15 | // Container for this node. 16 | let directoryElem = document.createElement('div'); 17 | let id = generateId(); 18 | let html = '
'; 19 | // If this has CONTENTS, build a directory node with toggle controls. 20 | // If it has neither CONTENTS nor TYPE, treat it as an empty directory. 21 | if (dirObj.CONTENTS || (!dirObj.CONTENTS && !dirObj.TYPE)) { 22 | html += createTogglerHtml(id, dirNames[j]); 23 | directoryElem.className = 'dir-container'; 24 | } 25 | html += '
'; 26 | // Main body. This is the element that toggle shows or hides. 27 | html += '
'; 29 | html += '
'; 30 | directoryElem.innerHTML = html; 31 | let nodeContainer = directoryElem.querySelector('#control_body_' + id); 32 | // If this has TYPE, build control(s) from the details. 33 | if (dirObj.TYPE) { 34 | directoryElem.classList.add('node'); 35 | buildControlElements(nodeContainer, nodeName, dirObj); 36 | } 37 | // If this has CONTENTS, recursively handle the inner contents. 38 | if (dirObj.CONTENTS) { 39 | buildContentsAddToContainer(dirObj.CONTENTS, nodeContainer); 40 | directoryElem.appendChild(nodeContainer); 41 | } 42 | parentContainer.appendChild(directoryElem); 43 | } 44 | } 45 | 46 | // Create html for toggle controls that show and hide directory contents. 47 | function createTogglerHtml(id, name) { 48 | let html = ''; 49 | // Toggle button when this is collapsed, will show the node. 50 | html += ''; 55 | // Toggle button when this is expanded, will hide the node. 56 | html += '
'; 57 | html += '' + toggleMinusSvg + ''; 58 | html += '' + E(name) + ''; 59 | html += '
'; 60 | return html; 61 | } 62 | 63 | function shortDisplay(text) { 64 | if (text.length > 28) { 65 | return (text.substring(0, 20) + '...' + 66 | text.substring(text.length - 6, text.length)); 67 | } 68 | return text; 69 | } 70 | 71 | // Add control nodes. Iterate the type field, adding one node per kind of type. 72 | function buildControlElements(containerElem, name, details, cfg) { 73 | // Handle the case where a directory is also a control. 74 | let existingName = containerElem.parentNode.querySelector('.dir-name'); 75 | if (!existingName) { 76 | createAppendElem(containerElem, 'span', 'control-name', 77 | shortDisplay(name)); 78 | } 79 | createAppendElem(containerElem, 'span', 'full-path', 80 | shortDisplay(details.FULL_PATH)); 81 | createAppendElem(containerElem, 'span', 'description', details.DESCRIPTION); 82 | let groupElem = document.createElement('div'); 83 | groupElem.className = 'group'; 84 | // Traverse the input. 85 | let selector = [0]; 86 | let pos = 0; 87 | for (let i = 0; i < details.TYPE.length; i++) { 88 | let type = details.TYPE[i]; 89 | if (type == '[') { 90 | selector.push(0); 91 | continue; 92 | } else if (type == ']') { 93 | selector.pop(); 94 | } else { 95 | details.name = name; 96 | let html = buildSingleControl(details, type, selector, pos, cfg); 97 | if (html) { 98 | let id = generateId(); 99 | let kind = types.extractControlKind(type, html); 100 | let elem = document.createElement('div'); 101 | elem.id = 'control_body_' + id; 102 | elem.className = 'control kind_' + kind; 103 | elem.innerHTML = html; 104 | groupElem.appendChild(elem); 105 | } 106 | pos += 1; 107 | } 108 | selector[selector.length - 1]++; 109 | } 110 | containerElem.appendChild(groupElem); 111 | } 112 | 113 | function buildSingleControl(details, type, selector, pos, cfg) { 114 | var html = ''; 115 | var getter = null; 116 | var setter = null; 117 | cfg = cfg || {}; 118 | if (type == 'c') { 119 | // Char 120 | if (details.RANGE && applySelector(details.RANGE, selector, 'VALS')) { 121 | var options = applySelector(details.RANGE, selector, 'VALS'); 122 | var value = applyPos(details.VALUE, pos) || ''; 123 | html += ''; 133 | getter = 'value'; 134 | } else { 135 | var value = applyPos(details.VALUE, pos) || ''; 136 | html += ''; 138 | getter = 'value'; 139 | } 140 | } else if (type == 'r') { 141 | // Color 142 | var value = applyPos(details.VALUE, pos); 143 | if (value && value[0] === '#') { 144 | // Remove alpha channel to just get '#rrggbb'. 145 | value = value.substr(0, 7); 146 | } else if (value) { 147 | value = convertOSCColorToHex(value); 148 | } else { 149 | value = DEFAULT_COLOR_ELEM_VALUE; 150 | } 151 | if (cfg.supportHtml5Color) { 152 | html += ''; 153 | } else { 154 | html += ('
'); 156 | } 157 | getter = 'color'; 158 | setter = 'color'; 159 | } else if (type == 'd') { 160 | // Double 161 | if (details.RANGE && applySelector(details.RANGE, selector, 'VALS')) { 162 | var options = applySelector(details.RANGE, selector, 'VALS'); 163 | var value = applyPos(details.VALUE, pos) || 0; 164 | html += ''; 174 | getter = 'value'; 175 | } else if (details.RANGE) { 176 | var min = applySelector(details.RANGE, selector, 'MIN') || 0; 177 | var max = applySelector(details.RANGE, selector, 'MAX') || 1; 178 | var value = applyPos(details.VALUE, pos) || 0; 179 | html += buildCurrRangeValue(value, min, max); 180 | html += ''; 182 | getter = 'parseFloat'; 183 | setter = 'float'; 184 | } else { 185 | var value = applyPos(details.VALUE, pos) || 0; 186 | html += buildCurrRangeValue(value); 187 | html += ''; 188 | getter = 'parseFloat'; 189 | setter = 'float'; 190 | } 191 | } else if (type == 'F') { 192 | // False 193 | var value = applyPos(details.VALUE, pos) || false; 194 | html += ''; 215 | } 216 | html += ''; 217 | getter = 'value'; 218 | } else if (details.RANGE) { 219 | var min = applySelector(details.RANGE, selector, 'MIN') || 0; 220 | var max = applySelector(details.RANGE, selector, 'MAX') || 1; 221 | var value = applyPos(details.VALUE, pos) || 0; 222 | html += buildCurrRangeValue(value, min, max); 223 | html += ''; 225 | getter = 'parseFloat'; 226 | setter = 'float'; 227 | } else { 228 | var value = applyPos(details.VALUE, pos) || 0; 229 | html += buildCurrRangeValue(value); 230 | html += ''; 231 | getter = 'parseFloat'; 232 | setter = 'float'; 233 | } 234 | } else if (type == 'I') { 235 | // Infinity 236 | html += ''; 237 | setter = 'button'; 238 | } else if (type == 'i') { 239 | // Integer 240 | if (details.RANGE && applySelector(details.RANGE, selector, 'VALS')) { 241 | var options = applySelector(details.RANGE, selector, 'VALS'); 242 | var value = applyPos(details.VALUE, pos) || 0; 243 | if (options.length == 1) { 244 | value = options[0]; 245 | html += ''; 247 | getter = 'parseSingle'; 248 | setter = 'button'; 249 | } else if (options.length == 2) { 250 | first = options[0]; 251 | second = options[1]; 252 | value = applyPos(details.VALUE, pos) || first; 253 | html += ''; 260 | getter = 'parseIntToggle'; 261 | setter = 'setToggle'; 262 | } else { 263 | html += ''; 273 | getter = 'value'; 274 | } 275 | } else if (details.RANGE) { 276 | var min = applySelector(details.RANGE, selector, 'MIN'); 277 | var max = applySelector(details.RANGE, selector, 'MAX'); 278 | if (min == null || max == null) { 279 | return ('Invalid node: RANGE needs ' + 280 | 'MIN,MAX or VALS'); 281 | } 282 | var value = applyPos(details.VALUE, pos) || 0; 283 | if (max - min == 0) { 284 | value = min; 285 | html += ''; 287 | getter = 'parseSingle'; 288 | setter = 'button'; 289 | } else if (max - min == 1) { 290 | value = applyPos(details.VALUE, pos) || min; 291 | html += ''; 298 | getter = 'parseIntToggle'; 299 | setter = 'setToggle'; 300 | } else { 301 | html += buildCurrRangeValue(value, min, max); 302 | html += ''; 304 | getter = 'parseInt'; 305 | setter = 'int'; 306 | } 307 | } else { 308 | var value = applyPos(details.VALUE, pos) || 0; 309 | html += buildCurrRangeValue(value); 310 | html += ''; 311 | getter = 'parseInt'; 312 | setter = 'int'; 313 | } 314 | } else if (type == 'h') { 315 | // Longlong 316 | if (details.RANGE && applySelector(details.RANGE, selector, 'VALS')) { 317 | var options = applySelector(details.RANGE, selector, 'VALS'); 318 | var value = applyPos(details.VALUE, pos) || 0; 319 | html += ''; 329 | getter = 'parseInt64'; 330 | setter = 'int64'; 331 | } else if (details.RANGE) { 332 | var min = applySelector(details.RANGE, selector, 'MIN') || 0; 333 | var max = applySelector(details.RANGE, selector, 'MAX') || 1; 334 | var value = applyPos(details.VALUE, pos) || 0; 335 | html += buildCurrRangeValue(value, min, max); 336 | html += ''; 338 | getter = 'parseInt64'; 339 | setter = 'int64'; 340 | } else { 341 | var value = applyPos(details.VALUE, pos) || 0; 342 | html += buildCurrRangeValue(value); 343 | html += ''; 344 | getter = 'parseInt64'; 345 | setter = 'int64'; 346 | } 347 | } else if (type == 'm') { 348 | // MIDI 349 | return null; 350 | } else if (type == 'N') { 351 | // Null 352 | html += ''; 353 | setter = 'button'; 354 | } else if (type == 's') { 355 | // String 356 | if (details.RANGE && applySelector(details.RANGE, selector, 'VALS')) { 357 | var options = applySelector(details.RANGE, selector, 'VALS'); 358 | var value = applyPos(details.VALUE, pos) || ''; 359 | html += ''; 369 | getter = 'value'; 370 | } else { 371 | var value = applyPos(details.VALUE, pos) || ''; 372 | html += ''; 373 | getter = 'value'; 374 | } 375 | } else if (type == 'T') { 376 | // True 377 | var value = applyPos(details.VALUE, pos); 378 | // NOTE: Can't use `func() || true` because `false` is possible value. 379 | if (value === null) { value = true; } 380 | html += ''; 393 | } 394 | html += ''; 410 | } else { 411 | html += '' + E(value) + ''; 412 | html += ' (' + E(min) + '-' + 413 | E(max) + ')'; 414 | } 415 | html += ''; 416 | return html; 417 | } 418 | 419 | // Add an element to the parent, with the tag, class, and text content. 420 | function createAppendElem(parentElem, tagName, className, text) { 421 | let elem = document.createElement(tagName); 422 | elem.className = className; 423 | elem.textContent = text; 424 | parentElem.appendChild(elem); 425 | } 426 | 427 | var g_idGen = 0; 428 | 429 | function generateId() { 430 | let result = g_idGen; 431 | g_idGen++; 432 | return result; 433 | } 434 | 435 | function applySelector(obj, selector, key) { 436 | if (!obj) { 437 | return null; 438 | } 439 | if (!Array.isArray(obj)) { 440 | return obj[key]; 441 | } 442 | for (let n = 0; n < selector.length; n++) { 443 | let i = selector[n]; 444 | obj = obj[i]; 445 | if (!obj) { 446 | return null; 447 | } 448 | } 449 | return obj[key]; 450 | } 451 | 452 | function applyPos(obj, pos) { 453 | if (!obj) { 454 | return null; 455 | } 456 | if (Array.isArray(obj)) { 457 | return obj[pos]; 458 | } 459 | return obj; 460 | } 461 | 462 | function textToHexColor(elem) { 463 | return '#' + num2Hex(elem['r']) + num2Hex(elem['g']) + num2Hex(elem['b']); 464 | } 465 | 466 | function num2Hex(num) { 467 | let hex = Number(Math.floor(num)).toString(16); 468 | if (hex.length < 2) { 469 | hex = '0' + hex; 470 | } 471 | return hex; 472 | } 473 | 474 | function convertOSCColorToHex(c) { 475 | return '#' + num2Hex(c[0]*255) + num2Hex(c[1]*255) + num2Hex(c[2]*255); 476 | } 477 | 478 | function E(text) { 479 | if (text === 0) { 480 | return "0"; 481 | } 482 | if (!text) { 483 | return ""; 484 | } 485 | return text.toString().replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 487 | } 488 | 489 | function E_bool(bool) { 490 | return bool ? "true" : "false"; 491 | } 492 | 493 | module.exports = { 494 | buildContentsAddToContainer: buildContentsAddToContainer, 495 | buildControlElements: buildControlElements, 496 | buildSingleControl: buildSingleControl, 497 | textToHexColor: textToHexColor, 498 | createTogglerHtml: createTogglerHtml, 499 | shortDisplay: shortDisplay, 500 | generateId: generateId, 501 | } 502 | -------------------------------------------------------------------------------- /src/colorpicker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ColorPicker - pure JavaScript color picker without using images, external CSS or 1px divs. 3 | * Copyright © 2011 David Durman, All rights reserved. 4 | */ 5 | (function(window, document, undefined) { 6 | 7 | var type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML"), 8 | picker, slide, hueOffset = 15, svgNS = 'http://www.w3.org/2000/svg'; 9 | 10 | // This HTML snippet is inserted into the innerHTML property of the passed color picker element 11 | // when the no-hassle call to ColorPicker() is used, i.e. ColorPicker(function(hex, hsv, rgb) { ... }); 12 | 13 | var colorpickerHTMLSnippet = [ 14 | 15 | '
', 16 | '
', 17 | '
', 18 | '
', 19 | '
', 20 | '
', 21 | '
', 22 | '
' 23 | 24 | ].join(''); 25 | 26 | /** 27 | * Return mouse position relative to the element el. 28 | */ 29 | function mousePosition(evt) { 30 | // IE: 31 | if (window.event && window.event.contentOverflow !== undefined) { 32 | return { x: window.event.offsetX, y: window.event.offsetY }; 33 | } 34 | // Webkit: 35 | if (evt.offsetX !== undefined && evt.offsetY !== undefined) { 36 | return { x: evt.offsetX, y: evt.offsetY }; 37 | } 38 | // Firefox: 39 | var wrapper = evt.target.parentNode.parentNode; 40 | return { x: evt.layerX - wrapper.offsetLeft, y: evt.layerY - wrapper.offsetTop }; 41 | } 42 | 43 | /** 44 | * Create SVG element. 45 | */ 46 | function $(el, attrs, children) { 47 | el = document.createElementNS(svgNS, el); 48 | for (var key in attrs) 49 | el.setAttribute(key, attrs[key]); 50 | if (Object.prototype.toString.call(children) != '[object Array]') children = [children]; 51 | var i = 0, len = (children[0] && children.length) || 0; 52 | for (; i < len; i++) 53 | el.appendChild(children[i]); 54 | return el; 55 | } 56 | 57 | /** 58 | * Create slide and picker markup depending on the supported technology. 59 | */ 60 | if (type == 'SVG') { 61 | 62 | slide = $('svg', { xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '100%', height: '100%' }, 63 | [ 64 | $('defs', {}, 65 | $('linearGradient', { id: 'gradient-hsv', x1: '0%', y1: '100%', x2: '0%', y2: '0%'}, 66 | [ 67 | $('stop', { offset: '0%', 'stop-color': '#FF0000', 'stop-opacity': '1' }), 68 | $('stop', { offset: '13%', 'stop-color': '#FF00FF', 'stop-opacity': '1' }), 69 | $('stop', { offset: '25%', 'stop-color': '#8000FF', 'stop-opacity': '1' }), 70 | $('stop', { offset: '38%', 'stop-color': '#0040FF', 'stop-opacity': '1' }), 71 | $('stop', { offset: '50%', 'stop-color': '#00FFFF', 'stop-opacity': '1' }), 72 | $('stop', { offset: '63%', 'stop-color': '#00FF40', 'stop-opacity': '1' }), 73 | $('stop', { offset: '75%', 'stop-color': '#0BED00', 'stop-opacity': '1' }), 74 | $('stop', { offset: '88%', 'stop-color': '#FFFF00', 'stop-opacity': '1' }), 75 | $('stop', { offset: '100%', 'stop-color': '#FF0000', 'stop-opacity': '1' }) 76 | ] 77 | ) 78 | ), 79 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-hsv)'}) 80 | ] 81 | ); 82 | 83 | picker = $('svg', { xmlns: 'http://www.w3.org/2000/svg', version: '1.1', width: '100%', height: '100%' }, 84 | [ 85 | $('defs', {}, 86 | [ 87 | $('linearGradient', { id: 'gradient-black', x1: '0%', y1: '100%', x2: '0%', y2: '0%'}, 88 | [ 89 | $('stop', { offset: '0%', 'stop-color': '#000000', 'stop-opacity': '1' }), 90 | $('stop', { offset: '100%', 'stop-color': '#CC9A81', 'stop-opacity': '0' }) 91 | ] 92 | ), 93 | $('linearGradient', { id: 'gradient-white', x1: '0%', y1: '100%', x2: '100%', y2: '100%'}, 94 | [ 95 | $('stop', { offset: '0%', 'stop-color': '#FFFFFF', 'stop-opacity': '1' }), 96 | $('stop', { offset: '100%', 'stop-color': '#CC9A81', 'stop-opacity': '0' }) 97 | ] 98 | ) 99 | ] 100 | ), 101 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-white)'}), 102 | $('rect', { x: '0', y: '0', width: '100%', height: '100%', fill: 'url(#gradient-black)'}) 103 | ] 104 | ); 105 | 106 | } else if (type == 'VML') { 107 | slide = [ 108 | '
', 109 | '', 110 | '', 111 | '', 112 | '
' 113 | ].join(''); 114 | 115 | picker = [ 116 | '
', 117 | '', 118 | '', 119 | '', 120 | '', 121 | '', 122 | '', 123 | '
' 124 | ].join(''); 125 | 126 | if (!document.namespaces['v']) 127 | document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); 128 | } 129 | 130 | /** 131 | * Convert HSV representation to RGB HEX string. 132 | * Credits to http://www.raphaeljs.com 133 | */ 134 | function hsv2rgb(hsv) { 135 | var R, G, B, X, C; 136 | var h = (hsv.h % 360) / 60; 137 | 138 | C = hsv.v * hsv.s; 139 | X = C * (1 - Math.abs(h % 2 - 1)); 140 | R = G = B = hsv.v - C; 141 | 142 | h = ~~h; 143 | R += [C, X, 0, 0, X, C][h]; 144 | G += [X, C, C, X, 0, 0][h]; 145 | B += [0, 0, X, C, C, X][h]; 146 | 147 | var r = Math.floor(R * 255); 148 | var g = Math.floor(G * 255); 149 | var b = Math.floor(B * 255); 150 | return { r: r, g: g, b: b, hex: "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1) }; 151 | } 152 | 153 | /** 154 | * Convert RGB representation to HSV. 155 | * r, g, b can be either in <0,1> range or <0,255> range. 156 | * Credits to http://www.raphaeljs.com 157 | */ 158 | function rgb2hsv(rgb) { 159 | 160 | var r = rgb.r; 161 | var g = rgb.g; 162 | var b = rgb.b; 163 | 164 | if (rgb.r > 1 || rgb.g > 1 || rgb.b > 1) { 165 | r /= 255; 166 | g /= 255; 167 | b /= 255; 168 | } 169 | 170 | var H, S, V, C; 171 | V = Math.max(r, g, b); 172 | C = V - Math.min(r, g, b); 173 | H = (C == 0 ? null : 174 | V == r ? (g - b) / C + (g < b ? 6 : 0) : 175 | V == g ? (b - r) / C + 2 : 176 | (r - g) / C + 4); 177 | H = (H % 6) * 60; 178 | S = C == 0 ? 0 : C / V; 179 | return { h: H, s: S, v: V }; 180 | } 181 | 182 | /** 183 | * Return click event handler for the slider. 184 | * Sets picker background color and calls ctx.callback if provided. 185 | */ 186 | function slideListener(ctx, slideElement, pickerElement) { 187 | return function(evt) { 188 | evt = evt || window.event; 189 | var mouse = mousePosition(evt); 190 | ctx.h = mouse.y / slideElement.offsetHeight * 360 + hueOffset; 191 | var pickerColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 }); 192 | var c = hsv2rgb({ h: ctx.h, s: ctx.s, v: ctx.v }); 193 | pickerElement.style.backgroundColor = pickerColor.hex; 194 | ctx.callback && ctx.callback(c.hex, { h: ctx.h - hueOffset, s: ctx.s, v: ctx.v }, { r: c.r, g: c.g, b: c.b }, undefined, mouse); 195 | } 196 | }; 197 | 198 | /** 199 | * Return click event handler for the picker. 200 | * Calls ctx.callback if provided. 201 | */ 202 | function pickerListener(ctx, pickerElement) { 203 | return function(evt) { 204 | evt = evt || window.event; 205 | var mouse = mousePosition(evt), 206 | width = pickerElement.offsetWidth, 207 | height = pickerElement.offsetHeight; 208 | 209 | ctx.s = mouse.x / width; 210 | ctx.v = (height - mouse.y) / height; 211 | var c = hsv2rgb(ctx); 212 | ctx.callback && ctx.callback(c.hex, { h: ctx.h - hueOffset, s: ctx.s, v: ctx.v }, { r: c.r, g: c.g, b: c.b }, mouse); 213 | } 214 | }; 215 | 216 | var uniqID = 0; 217 | 218 | /** 219 | * ColorPicker. 220 | * @param {DOMElement} slideElement HSV slide element. 221 | * @param {DOMElement} pickerElement HSV picker element. 222 | * @param {Function} callback Called whenever the color is changed provided chosen color in RGB HEX format as the only argument. 223 | */ 224 | function ColorPicker(slideElement, pickerElement, callback) { 225 | 226 | if (!(this instanceof ColorPicker)) return new ColorPicker(slideElement, pickerElement, callback); 227 | 228 | this.h = 0; 229 | this.s = 1; 230 | this.v = 1; 231 | 232 | if (!callback) { 233 | // call of the form ColorPicker(element, funtion(hex, hsv, rgb) { ... }), i.e. the no-hassle call. 234 | 235 | var element = slideElement; 236 | element.innerHTML = colorpickerHTMLSnippet; 237 | 238 | this.slideElement = element.getElementsByClassName('slide')[0]; 239 | this.pickerElement = element.getElementsByClassName('picker')[0]; 240 | var slideIndicator = element.getElementsByClassName('slide-indicator')[0]; 241 | var pickerIndicator = element.getElementsByClassName('picker-indicator')[0]; 242 | 243 | ColorPicker.fixIndicators(slideIndicator, pickerIndicator); 244 | 245 | this.callback = function(hex, hsv, rgb, pickerCoordinate, slideCoordinate) { 246 | 247 | ColorPicker.positionIndicators(slideIndicator, pickerIndicator, slideCoordinate, pickerCoordinate); 248 | 249 | pickerElement(hex, hsv, rgb); 250 | }; 251 | 252 | } else { 253 | 254 | this.callback = callback; 255 | this.pickerElement = pickerElement; 256 | this.slideElement = slideElement; 257 | } 258 | 259 | if (type == 'SVG') { 260 | 261 | // Generate uniq IDs for linearGradients so that we don't have the same IDs within one document. 262 | // Then reference those gradients in the associated rectangles. 263 | 264 | var slideClone = slide.cloneNode(true); 265 | var pickerClone = picker.cloneNode(true); 266 | 267 | var hsvGradient = slideClone.getElementsByTagName('linearGradient')[0]; 268 | 269 | var hsvRect = slideClone.getElementsByTagName('rect')[0]; 270 | 271 | hsvGradient.id = 'gradient-hsv-' + uniqID; 272 | hsvRect.setAttribute('fill', 'url(#' + hsvGradient.id + ')'); 273 | 274 | var blackAndWhiteGradients = [pickerClone.getElementsByTagName('linearGradient')[0], pickerClone.getElementsByTagName('linearGradient')[1]]; 275 | var whiteAndBlackRects = pickerClone.getElementsByTagName('rect'); 276 | 277 | blackAndWhiteGradients[0].id = 'gradient-black-' + uniqID; 278 | blackAndWhiteGradients[1].id = 'gradient-white-' + uniqID; 279 | 280 | whiteAndBlackRects[0].setAttribute('fill', 'url(#' + blackAndWhiteGradients[1].id + ')'); 281 | whiteAndBlackRects[1].setAttribute('fill', 'url(#' + blackAndWhiteGradients[0].id + ')'); 282 | 283 | this.slideElement.appendChild(slideClone); 284 | this.pickerElement.appendChild(pickerClone); 285 | 286 | uniqID++; 287 | 288 | } else { 289 | 290 | this.slideElement.innerHTML = slide; 291 | this.pickerElement.innerHTML = picker; 292 | } 293 | 294 | addEventListener(this.slideElement, 'click', slideListener(this, this.slideElement, this.pickerElement)); 295 | addEventListener(this.pickerElement, 'click', pickerListener(this, this.pickerElement)); 296 | 297 | enableDragging(this, this.slideElement, slideListener(this, this.slideElement, this.pickerElement)); 298 | enableDragging(this, this.pickerElement, pickerListener(this, this.pickerElement)); 299 | }; 300 | 301 | function addEventListener(element, event, listener) { 302 | 303 | if (element.attachEvent) { 304 | 305 | element.attachEvent('on' + event, listener); 306 | 307 | } else if (element.addEventListener) { 308 | 309 | element.addEventListener(event, listener, false); 310 | } 311 | } 312 | 313 | /** 314 | * Enable drag&drop color selection. 315 | * @param {object} ctx ColorPicker instance. 316 | * @param {DOMElement} element HSV slide element or HSV picker element. 317 | * @param {Function} listener Function that will be called whenever mouse is dragged over the element with event object as argument. 318 | */ 319 | function enableDragging(ctx, element, listener) { 320 | 321 | var mousedown = false; 322 | 323 | addEventListener(element, 'mousedown', function(evt) { mousedown = true; }); 324 | addEventListener(element, 'mouseup', function(evt) { mousedown = false; }); 325 | addEventListener(element, 'mouseout', function(evt) { mousedown = false; }); 326 | addEventListener(element, 'mousemove', function(evt) { 327 | 328 | if (mousedown) { 329 | 330 | listener(evt); 331 | } 332 | }); 333 | } 334 | 335 | 336 | ColorPicker.hsv2rgb = function(hsv) { 337 | var rgbHex = hsv2rgb(hsv); 338 | delete rgbHex.hex; 339 | return rgbHex; 340 | }; 341 | 342 | ColorPicker.hsv2hex = function(hsv) { 343 | return hsv2rgb(hsv).hex; 344 | }; 345 | 346 | ColorPicker.rgb2hsv = rgb2hsv; 347 | 348 | ColorPicker.rgb2hex = function(rgb) { 349 | return hsv2rgb(rgb2hsv(rgb)).hex; 350 | }; 351 | 352 | ColorPicker.hex2hsv = function(hex) { 353 | return rgb2hsv(ColorPicker.hex2rgb(hex)); 354 | }; 355 | 356 | ColorPicker.hex2rgb = function(hex) { 357 | return { r: parseInt(hex.substr(1, 2), 16), g: parseInt(hex.substr(3, 2), 16), b: parseInt(hex.substr(5, 2), 16) }; 358 | }; 359 | 360 | /** 361 | * Sets color of the picker in hsv/rgb/hex format. 362 | * @param {object} ctx ColorPicker instance. 363 | * @param {object} hsv Object of the form: { h: , s: , v: }. 364 | * @param {object} rgb Object of the form: { r: , g: , b: }. 365 | * @param {string} hex String of the form: #RRGGBB. 366 | */ 367 | function setColor(ctx, hsv, rgb, hex) { 368 | ctx.h = hsv.h % 360; 369 | ctx.s = hsv.s; 370 | ctx.v = hsv.v; 371 | 372 | var c = hsv2rgb(ctx); 373 | 374 | var mouseSlide = { 375 | y: (ctx.h * ctx.slideElement.offsetHeight) / 360, 376 | x: 0 // not important 377 | }; 378 | 379 | var pickerHeight = ctx.pickerElement.offsetHeight; 380 | 381 | var mousePicker = { 382 | x: ctx.s * ctx.pickerElement.offsetWidth, 383 | y: pickerHeight - ctx.v * pickerHeight 384 | }; 385 | 386 | ctx.pickerElement.style.backgroundColor = hsv2rgb({ h: ctx.h, s: 1, v: 1 }).hex; 387 | ctx.callback && ctx.callback(hex || c.hex, { h: ctx.h, s: ctx.s, v: ctx.v }, rgb || { r: c.r, g: c.g, b: c.b }, mousePicker, mouseSlide); 388 | 389 | return ctx; 390 | }; 391 | 392 | /** 393 | * Sets color of the picker in hsv format. 394 | * @param {object} hsv Object of the form: { h: , s: , v: }. 395 | */ 396 | ColorPicker.prototype.setHsv = function(hsv) { 397 | return setColor(this, hsv); 398 | }; 399 | 400 | /** 401 | * Sets color of the picker in rgb format. 402 | * @param {object} rgb Object of the form: { r: , g: , b: }. 403 | */ 404 | ColorPicker.prototype.setRgb = function(rgb) { 405 | return setColor(this, rgb2hsv(rgb), rgb); 406 | }; 407 | 408 | /** 409 | * Sets color of the picker in hex format. 410 | * @param {string} hex Hex color format #RRGGBB. 411 | */ 412 | ColorPicker.prototype.setHex = function(hex) { 413 | return setColor(this, ColorPicker.hex2hsv(hex), undefined, hex); 414 | }; 415 | 416 | /** 417 | * Helper to position indicators. 418 | * @param {HTMLElement} slideIndicator DOM element representing the indicator of the slide area. 419 | * @param {HTMLElement} pickerIndicator DOM element representing the indicator of the picker area. 420 | * @param {object} mouseSlide Coordinates of the mouse cursor in the slide area. 421 | * @param {object} mousePicker Coordinates of the mouse cursor in the picker area. 422 | */ 423 | ColorPicker.positionIndicators = function(slideIndicator, pickerIndicator, mouseSlide, mousePicker) { 424 | 425 | if (mouseSlide) { 426 | slideIndicator.style.top = (mouseSlide.y - slideIndicator.offsetHeight/2) + 'px'; 427 | } 428 | if (mousePicker) { 429 | pickerIndicator.style.top = (mousePicker.y - pickerIndicator.offsetHeight/2) + 'px'; 430 | pickerIndicator.style.left = (mousePicker.x - pickerIndicator.offsetWidth/2) + 'px'; 431 | } 432 | }; 433 | 434 | /** 435 | * Helper to fix indicators - this is recommended (and needed) for dragable color selection (see enabledDragging()). 436 | */ 437 | ColorPicker.fixIndicators = function(slideIndicator, pickerIndicator) { 438 | 439 | pickerIndicator.style.pointerEvents = 'none'; 440 | slideIndicator.style.pointerEvents = 'none'; 441 | }; 442 | 443 | window.ColorPicker = ColorPicker; 444 | 445 | })(window, window.document); 446 | -------------------------------------------------------------------------------- /src/controls.js: -------------------------------------------------------------------------------- 1 | // Get the value of an html control, and return it as an OSC argument. 2 | function getControlArg(controlElem) { 3 | let inputElem = controlElem.querySelector('input'); 4 | if (!inputElem) { 5 | inputElem = controlElem.querySelector('select'); 6 | } 7 | let detailsElem = controlElem.querySelector('.details'); 8 | let fullPath = detailsElem.attributes['data-full-path'].value; 9 | let dataType = detailsElem.attributes['data-type'].value; 10 | let getter = detailsElem.attributes['data-getter']; 11 | let arg = null; 12 | if (!getter) { 13 | return {type: dataType}; 14 | } else if (getter.value == 'value') { 15 | return {type: dataType, value: inputElem.value }; 16 | } else if (getter.value == 'parseInt') { 17 | return {type: dataType, value: parseInt(inputElem.value, 10) }; 18 | } else if (getter.value == 'parseInt64') { 19 | let num = parseInt(inputElem.value); 20 | let radix = 0x100000000; 21 | let high = Math.floor(num / radix); 22 | let low = num % radix; 23 | return {type: dataType, value: {high: high, low: low}}; 24 | } else if (getter.value == 'parseFloat') { 25 | return {type: dataType, value: parseFloat(inputElem.value) }; 26 | } else if (getter.value == 'parseSingle') { 27 | let first = inputElem.attributes['data-first'].value; 28 | return {type: dataType, value: parseInt(first, 10) }; 29 | } else if (getter.value == 'boolToggle') { 30 | return {type: inputElem.value == 'true' ? 'T' : 'F'}; 31 | } else if (getter.value == 'parseIntToggle') { 32 | let value = null; 33 | let dataFirst = inputElem.attributes['data-first'] 34 | let dataSecond = inputElem.attributes['data-second'] 35 | if (dataFirst.value == inputElem.value) { 36 | value = dataFirst.value; 37 | } else { 38 | value = dataSecond.value; 39 | } 40 | return {type: dataType, value: parseInt(value, 10) }; 41 | } else if (getter.value == 'sendCheckbox') { 42 | let value; 43 | if (inputElem.checked) { 44 | value = parseInt(inputElem.attributes['data-second'].value, 10); 45 | } else { 46 | value = parseInt(inputElem.attributes['data-first'].value, 10); 47 | } 48 | return {type: dataType, value: value}; 49 | } else if (getter.value == 'color') { 50 | if (!inputElem) { 51 | // Only for color elements in browsers that don't support the 52 | // html5 color input. 53 | inputElem = controlElem.querySelector('.color-control'); 54 | } 55 | var color = inputElem.value; 56 | var r = parseInt(color.substr(1, 2), 16); 57 | var g = parseInt(color.substr(3, 2), 16); 58 | var b = parseInt(color.substr(5, 2), 16); 59 | return {type: dataType, value: {r:r, g:g, b:b, a:1} }; 60 | } 61 | } 62 | 63 | // Set the value of an html control, based upon the type it represents. 64 | function runSetter(controlElem, type, value) { 65 | if (type == 'int') { 66 | let currValElem = controlElem.querySelector('.curr-val'); 67 | currValElem.textContent = value; 68 | } else if (type == 'int64') { 69 | let currValElem = controlElem.querySelector('.curr-val'); 70 | if (value.hasOwnProperty('high') && value.hasOwnProperty('low')) { 71 | let radix = 0x100000000; 72 | value = value.high * radix + value.low; 73 | } 74 | currValElem.textContent = value; 75 | } else if (type == 'float') { 76 | let currValElem = controlElem.querySelector('.curr-val'); 77 | currValElem.textContent = Math.round(value * 1000) / 1000; 78 | } else if (type == 'setToggleBeforeGetControlArg') { 79 | let buttonElem = controlElem.querySelector('input'); 80 | let dataFirst = buttonElem.attributes['data-first'] 81 | let dataSecond = buttonElem.attributes['data-second'] 82 | let isEnabled; 83 | if (dataFirst && dataSecond) { 84 | if (dataFirst.value == value) { 85 | value = dataSecond.value; 86 | isEnabled = false; 87 | } else { 88 | value = dataFirst.value; 89 | isEnabled = true; 90 | } 91 | } else { 92 | if (value === false || value == 'false') { 93 | value = 'true'; 94 | isEnabled = true; 95 | } else { 96 | value = 'false'; 97 | isEnabled = false; 98 | } 99 | } 100 | buttonElem.value = value; 101 | if (isEnabled) { 102 | buttonElem.classList.add('enabled'); 103 | } else { 104 | buttonElem.classList.remove('enabled'); 105 | } 106 | } else if (type == 'setToggle') { 107 | // do nothing 108 | } else if (type == 'button') { 109 | // do nothing 110 | } 111 | } 112 | 113 | module.exports = { 114 | getControlArg: getControlArg, 115 | runSetter: runSetter, 116 | } 117 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const osc = require('osc'); 2 | const oscTransports = require('osc-transports'); 3 | const oscWebsocketClient = require('osc-websocket-client'); 4 | 5 | const builder = require('./builder.js'); 6 | const controls = require('./controls.js'); 7 | const settings = require('./settings.js'); 8 | const userinput = require('./userinput.js'); 9 | const types = require('./types.js'); 10 | const retrieve = require('./retrieve.js'); 11 | 12 | // Polyfilled controls. 13 | const vanillaColorPicker = require('vanilla-picker'); 14 | const rangeSlider = require('rangeslider-pure'); 15 | 16 | // Image assets. 17 | const logoBase64 = require("base64-image-loader!../assets/img/icon.png"); 18 | const listenButtonSvg = require("svg-inline-loader?classPrefix=_listen!../assets/img/listen.svg"); 19 | const ignoreButtonSvg = require("svg-inline-loader?classPrefix=_ignore!../assets/img/pressed.svg"); 20 | 21 | global.g_allControlStruct = null; 22 | global.g_hostInfo = {}; 23 | global.g_extensions = null; 24 | global.g_isListenEnabled = false; 25 | global.g_serverUrl = null; 26 | 27 | function $(selector) { 28 | return document.querySelector(selector); 29 | } 30 | 31 | function objectGetValue(obj, i) { 32 | return obj[Object.keys(obj)[i]]; 33 | } 34 | 35 | function storeHostInfo(hostInfo) { 36 | global.g_hostInfo = hostInfo; 37 | global.g_extensions = hostInfo.EXTENSIONS; 38 | } 39 | 40 | var g_supportHtml5Color = false; 41 | 42 | function detectColorPicker() { 43 | let input = document.createElement('input'); 44 | input.setAttribute('type', 'color'); 45 | input.setAttribute('value', '$'); 46 | // Currently, always use third-party control. 47 | g_supportHtml5Color = false; 48 | } 49 | 50 | // Build all controls from json object, from the top-level. 51 | function buildFromQueryResult(result) { 52 | let mainContentsElem = $('#mainContents'); 53 | { 54 | let logoHolderElem = document.createElement('div'); 55 | let logoElem = document.createElement('img'); 56 | logoElem.className = 'logo'; 57 | logoElem.src = logoBase64; 58 | logoHolderElem.appendChild(logoElem); 59 | mainContents.appendChild(logoHolderElem); 60 | } 61 | { 62 | let refreshMessageElem = document.createElement('div'); 63 | refreshMessageElem.id = 'refresh-butter'; 64 | refreshMessageElem.style.display = 'none'; 65 | refreshMessageElem.style.backgroundColor = '#ffff88'; 66 | refreshMessageElem.style.position = 'absolute'; 67 | refreshMessageElem.style.top = '2px'; 68 | refreshMessageElem.style.left = '2px'; 69 | refreshMessageElem.style.textAlign = 'center'; 70 | refreshMessageElem.style.width = '40%'; 71 | refreshMessageElem.textContent = 'Changes found, refreshing...'; 72 | document.body.appendChild(refreshMessageElem); 73 | } 74 | let contents = result.CONTENTS; 75 | if (!contents) { 76 | let noControlsElem = document.createElement('div'); 77 | noControlsElem.textContent = 'No controls detected'; 78 | mainContentsElem.appendChild(noControlsElem); 79 | return; 80 | } 81 | if (global.g_extensions.LISTEN) { 82 | // Label for listen button. 83 | let labelDivElem = document.createElement('div'); 84 | labelDivElem.className = 'listen-label'; 85 | labelDivElem.textContent = 'Listen for OSC: '; 86 | mainContentsElem.appendChild(labelDivElem); 87 | // Listen and ignore buttons. 88 | let listenSpanElem = document.createElement('span'); 89 | listenSpanElem.className = 'svg-listen'; 90 | listenSpanElem.style.display = 'none'; 91 | listenSpanElem.innerHTML = listenButtonSvg; 92 | let ignoreSpanElem = document.createElement('span'); 93 | ignoreSpanElem.className = 'svg-ignore'; 94 | ignoreSpanElem.style.display = 'inline-block'; 95 | ignoreSpanElem.innerHTML = ignoreButtonSvg; 96 | mainContentsElem.appendChild(listenSpanElem); 97 | mainContentsElem.appendChild(ignoreSpanElem); 98 | // Set listening state. 99 | setTimeout(settings.enableInitialListenState, 0); 100 | } 101 | { 102 | // Create style links, dark and light. 103 | let styleDarkElem = document.createElement('div'); 104 | styleDarkElem.id = 'choice-dark-mode'; 105 | styleDarkElem.innerHTML = 'dark light'; 106 | let styleLightElem = document.createElement('div'); 107 | styleLightElem.id = 'choice-light-mode'; 108 | styleLightElem.innerHTML = 'dark light'; 109 | styleLightElem.style.display = 'none'; 110 | mainContentsElem.appendChild(styleDarkElem); 111 | mainContentsElem.appendChild(styleLightElem); 112 | // Create css to bold the in-use style, underline the unused style. 113 | let styleElem = document.createElement('style'); 114 | styleElem.textContent = '.curr_mode { font-weight: bold; } #set_light {text-decoration: underline; cursor: pointer;} #set_dark {text-decoration: underline; cursor: pointer;}' 115 | mainContentsElem.appendChild(styleElem); 116 | let setLightLink = $('#set_light'); 117 | setLightLink.addEventListener('click', function() { 118 | settings.setStyleMode('light'); 119 | }, false); 120 | let setDarkLink = $('#set_dark'); 121 | setDarkLink.addEventListener('click', function() { 122 | settings.setStyleMode('dark'); 123 | }, false); 124 | // Set beginning style based upon the user's cookie. 125 | if (document.cookie.includes('style=light')) { 126 | settings.setStyleMode('light'); 127 | } 128 | } 129 | // Configuration for building. 130 | let cfg = {supportHtml5Color: g_supportHtml5Color}; 131 | // Root node. 132 | let rootElem = document.createElement('div'); 133 | rootElem.id = 'control_body_' + builder.generateId(); 134 | rootElem.setAttribute('data-dir-path', '/'); 135 | mainContentsElem.appendChild(rootElem); 136 | // Build contents for the main container. 137 | builder.buildContentsAddToContainer(contents, rootElem, cfg) 138 | } 139 | 140 | function extractControlPaths(obj) { 141 | var paths = []; 142 | if (obj.CONTENTS) { 143 | let dirNames = Object.keys(obj.CONTENTS); 144 | dirNames.sort(); 145 | for (let j = 0; j < dirNames.length; j++) { 146 | let name = dirNames[j]; 147 | let dirObj = obj.CONTENTS[dirNames[j]]; 148 | paths = paths.concat(extractControlPaths(dirObj)); 149 | } 150 | } 151 | if (obj.TYPE && obj.FULL_PATH) { 152 | paths.push(obj.FULL_PATH); 153 | } 154 | return paths; 155 | } 156 | 157 | function storeControlStructure(data) { 158 | g_allControlStruct = extractControlPaths(data); 159 | } 160 | 161 | function nullFunction() {} 162 | 163 | global.isOscReady = false; 164 | global.oscPort = null; 165 | 166 | function initWebSocket(url) { 167 | global.oscPort = new osc.WebSocketPort({ 168 | url: url, 169 | metadata: true 170 | }); 171 | global.oscPort.open(); 172 | oscPort.on('ready', function() { 173 | global.isOscReady = true; 174 | global.oscPort.socket.onmessage = function(e) { 175 | // Check if message was a JSON command. 176 | let msg = null; 177 | try { 178 | msg = JSON.parse(e.data); 179 | } catch (e) { 180 | // pass 181 | } 182 | if (msg) { 183 | processCommandMessage(msg); 184 | return; 185 | } 186 | // Non-JSON data, assume it's a binary OSC packet. 187 | let packet = osc.readPacket(new Uint8Array(e.data), {}); 188 | console.log('***** Got packet <' + JSON.stringify(packet) + '>'); 189 | let address = packet.address; 190 | // TODO: Validate address contains allowed characters. 191 | let query = '[data-full-path="' + address + '"]'; 192 | let detailsElem = document.querySelector(query); 193 | let groupElem = detailsElem.parentNode.parentNode; 194 | for (let i = 0; i < packet.args.length; i++) { 195 | let value = packet.args[i]; 196 | let controlElem = groupElem.children[i]; 197 | applyOSCMessageValue(controlElem, value); 198 | } 199 | } 200 | }); 201 | } 202 | 203 | // Apply OSC packet's single value by setting control state, update UI. 204 | function applyOSCMessageValue(controlElem, value) { 205 | // Get input or select tag, which needs to have value changed. 206 | let targetElem = controlElem.querySelector('input'); 207 | if (!targetElem) { 208 | targetElem = controlElem.querySelector('select'); 209 | } 210 | if (!targetElem) { 211 | return; 212 | } 213 | // Update position of slider polyfill. NOTE: Kind of a hack to 214 | // put this code here, it's a one-off. 215 | if (targetElem.attributes.type && 216 | targetElem.attributes.type.value == 'range') { 217 | if (global.g_numRangeMessagePending > 0) { 218 | global.g_numRangeMessagePending--; 219 | return; 220 | } 221 | targetElem.rangeSlider.update({value: value}, false); 222 | return; 223 | } 224 | let detailsElem = controlElem.querySelector('[class="details"]'); 225 | let setter = detailsElem.attributes['data-setter']; 226 | if (setter) { 227 | if (setter.value == 'color') { 228 | // If the html5 color control is being dragged around, 229 | // and LISTEN is enabled, the messages sent from this 230 | // control will be routed back to it, and subtly decrease 231 | // the lightness due to rounding errors. So, while the 232 | // control is being changed, wait a short amount of time 233 | // before accepting new updates. 234 | if (global.g_numColorMessagePending > 0) { 235 | global.g_numColorMessagePending--; 236 | return; 237 | } 238 | if (!global.g_supportHtml5Color) { 239 | // Polyfill control, update the color. 240 | value = builder.textToHexColor(value); 241 | let colorClass = '.color-control'; 242 | targetElem = controlElem.querySelector(colorClass); 243 | // Change the picker's color, but don't send events. 244 | let picker = targetElem.picker; 245 | let preserveHandler = picker.onChange; 246 | picker.onChange = nullFunction; 247 | picker.setColor(value); 248 | picker.onChange = preserveHandler; 249 | } 250 | } else if (setter.value == 'setCheckbox') { 251 | // If the control is a checkbox, there should only be 252 | // two possible values. Either check or uncheck the box, 253 | // but only if it matches one of the two known values. 254 | let first = targetElem.attributes['data-first'].value; 255 | let second = targetElem.attributes['data-second'].value; 256 | if (value == first) { 257 | targetElem.checked = false; 258 | } else if (value == second) { 259 | targetElem.checked = true; 260 | } 261 | return; 262 | } else if (setter.value == 'setToggle') { 263 | controls.runSetter(controlElem, setter.value, value); 264 | return; 265 | } else if (setter.value == 'button') { 266 | // do nothing 267 | return; 268 | } else { 269 | controls.runSetter(controlElem, setter.value, value); 270 | } 271 | } 272 | targetElem.value = value; 273 | } 274 | 275 | function processCommandMessage(msg) { 276 | if (msg.COMMAND == 'PATH_CHANGED') { 277 | if (global.g_extensions.PATH_CHANGED) { 278 | let refreshElem = document.getElementById('refresh-butter'); 279 | refreshElem.style.display = 'inline'; 280 | global.location.reload(true); 281 | } 282 | } else if (msg.COMMAND == 'PATH_ADDED') { 283 | if (global.g_extensions.PATH_ADDED) { 284 | let nodePath = msg.DATA; 285 | let pathParts = nodePath.split('/'); 286 | let numParts = pathParts.length - 1; 287 | let nodeName = pathParts[numParts]; 288 | let nodeUrl = global.g_serverUrl + nodePath; 289 | retrieve.retrieveJson(nodeUrl, (err, contents) => { 290 | // Get the directory container for where the newly created 291 | // node should go, creating new elements as needed. 292 | let targetElem = getOrMakeDirNode(pathParts.slice(0, numParts)); 293 | // Node container for the new element. 294 | let containerElem = document.createElement('div') 295 | containerElem.className = 'node'; 296 | let headerElem = document.createElement('header'); 297 | containerElem.appendChild(headerElem); 298 | targetElem.appendChild(containerElem); 299 | // Build the new node control, insert it into the container. 300 | let newElem = document.createElement('div'); 301 | newElem.id = 'control_body_' + builder.generateId(); 302 | newElem.setAttribute('data-dir-path', nodePath); 303 | containerElem.appendChild(newElem); 304 | builder.buildControlElements(newElem, nodeName, contents); 305 | // Add event listeners to new elements. 306 | addToggleEventHandlers(); 307 | maybeAddPolyfill(newElem); 308 | }); 309 | } 310 | } else if (msg.COMMAND == 'PATH_RENAMED') { 311 | if (global.g_extensions.PATH_RENAMED) { 312 | let oldPath = msg.DATA.OLD; 313 | let newPath = msg.DATA.NEW; 314 | let targetElem = document.querySelector( 315 | '[data-dir-path="' + oldPath + '"]'); 316 | if (!targetElem) { 317 | return; 318 | } 319 | targetElem.setAttribute('data-dir-path', newPath); 320 | let controlDetail = targetElem.querySelector('.control-name'); 321 | if (controlDetail) { 322 | let newParts = newPath.split('/'); 323 | let newName = newParts[newParts.length - 1]; 324 | controlDetail.textContent = builder.shortDisplay(newName); 325 | } 326 | let fullPathDetail = targetElem.querySelector('.full-path'); 327 | if (fullPathDetail) { 328 | fullPathDetail.textContent = builder.shortDisplay(newPath); 329 | } 330 | targetElem = document.querySelector( 331 | '[data-full-path="' + oldPath + '"]'); 332 | if (!targetElem) { 333 | return; 334 | } 335 | targetElem.setAttribute('data-full-path', newPath); 336 | } 337 | } else if (msg.COMMAND == 'PATH_REMOVED') { 338 | if (global.g_extensions.PATH_REMOVED) { 339 | let nodePath = msg.DATA; 340 | let targetElem = document.querySelector( 341 | '[data-dir-path="' + nodePath + '"]'); 342 | // Remove the parent, with either class "dir-container" or "node". 343 | let removeElem = targetElem.parentNode; 344 | if (removeElem) { 345 | removeElem.parentNode.removeChild(removeElem); 346 | } 347 | } 348 | } else { 349 | console.log('??????????'); 350 | console.log('Unknown message: ' + e.data); 351 | } 352 | } 353 | 354 | function getOrMakeDirNode(pathParts) { 355 | let result = document.querySelector('[data-dir-path="/"]'); 356 | for (let i = 1; i < pathParts.length; i++) { 357 | let path = pathParts.slice(0, i + 1).join('/'); 358 | let elem = result.querySelector( 359 | '[data-dir-path="' + path + '"]'); 360 | if (!elem) { 361 | let id = builder.generateId(); 362 | elem = document.createElement('div'); 363 | elem.id = 'control_body_' + id; 364 | elem.setAttribute('data-dir-path', path); 365 | // Directory container for the new element. 366 | let containerElem = document.createElement('div') 367 | containerElem.className = 'dir-container'; 368 | let headerElem = document.createElement('header'); 369 | headerElem.innerHTML = builder.createTogglerHtml(id, pathParts[i]); 370 | containerElem.appendChild(headerElem); 371 | // Add the container to the parent. 372 | containerElem.appendChild(elem); 373 | result.appendChild(containerElem); 374 | } 375 | result = elem; 376 | } 377 | return result; 378 | } 379 | 380 | function getDataEvent(element) { 381 | if (element.attributes['data-event']) { 382 | return element.attributes['data-event'].value; 383 | } 384 | return null; 385 | } 386 | 387 | function addInputEventHandlers() { 388 | let inputs = document.getElementsByTagName("input"); 389 | for (let i = 0; i < inputs.length; i++) { 390 | let input = inputs[i]; 391 | if (getDataEvent(input) == 'keypress') { 392 | input.addEventListener('keypress', 393 | userinput.charKeyPressEvent, false); 394 | } else if (input.type == "button" && input.attributes['data-toggle']) { 395 | input.addEventListener('click', userinput.toggleEvent, false); 396 | } else if (input.type == "button") { 397 | input.addEventListener('click', userinput.controlEvent, false); 398 | } else if (input.type == "range") { 399 | input.addEventListener('input', userinput.rangeModifyEvent, false); 400 | input.addEventListener('change', userinput.rangeModifyEvent, false); 401 | } else if (input.type == "color") { 402 | input.addEventListener('change', userinput.colorModifyEvent, false); 403 | } else { 404 | input.addEventListener('change', userinput.controlEvent, false); 405 | } 406 | } 407 | let selects = document.getElementsByTagName("select"); 408 | for (let i = 0; i < selects.length; i++) { 409 | let select = selects[i]; 410 | select.addEventListener('change', userinput.controlEvent, false); 411 | } 412 | let listenButtons = document.getElementsByClassName('svg-listen'); 413 | for (let i = 0; i < listenButtons.length; i++) { 414 | let listenBtn = listenButtons[i]; 415 | listenBtn.addEventListener('click', settings.listenClick, false); 416 | } 417 | let ignoreButtons = document.getElementsByClassName('svg-ignore'); 418 | for (let i = 0; i < ignoreButtons.length; i++) { 419 | let ignoreBtn = ignoreButtons[i]; 420 | ignoreBtn.addEventListener('click', settings.ignoreClick, false); 421 | } 422 | addToggleEventHandlers(); 423 | } 424 | 425 | function addToggleEventHandlers() { 426 | let toggleHideElems = document.getElementsByClassName('toggle-hide'); 427 | for (let i = 0; i < toggleHideElems.length; i++) { 428 | let elem = toggleHideElems[i]; 429 | if (!elem.hasListener) { 430 | elem.addEventListener('click', settings.toggleHide, false); 431 | elem.hasListener = true; 432 | } 433 | } 434 | let toggleShowElems = document.getElementsByClassName('toggle-show'); 435 | for (let i = 0; i < toggleShowElems.length; i++) { 436 | let elem = toggleShowElems[i]; 437 | if (!elem.hasListener) { 438 | elem.addEventListener('click', settings.toggleShow, false); 439 | elem.hasListener = true; 440 | } 441 | } 442 | } 443 | 444 | function maybeAddPolyfill(elem) { 445 | // Check if the added node needs a color polyfill. 446 | let colorElem = elem.querySelector('[class="color-control"]'); 447 | if (colorElem) { 448 | createRangeSliderPolyfill(colorElem); 449 | colorElem.addEventListener('change', userinput.colorModifyEvent, false); 450 | return; 451 | } 452 | // Check if the added node needs a range-slider polyfill. 453 | let rangeElem = elem.querySelector('input[type="range"]'); 454 | if (rangeElem) { 455 | createRangeSliderPolyfill(rangeElem); 456 | rangeElem.addEventListener('input', userinput.rangeModifyEvent, false); 457 | rangeElem.addEventListener('change', userinput.rangeModifyEvent, false); 458 | return; 459 | } 460 | } 461 | 462 | function addColorPickerPolyfills() { 463 | // If this browser does not support the built-in html5 color picker 464 | // element, create polyfill controls for each element. 465 | if (global.g_supportHtml5Color) { 466 | return; 467 | } 468 | let colorElems = document.getElementsByClassName('color-control'); 469 | for (let i = 0; i < colorElems.length; i++) { 470 | createColorPickerPolyfill(colorElems[i]); 471 | } 472 | } 473 | 474 | function createColorPickerPolyfill(colorElem) { 475 | let initValue = colorElem.attributes['data-value'].value; 476 | colorElem.picker = new vanillaColorPicker({ 477 | parent: colorElem, 478 | popup: false, 479 | alpha: false, 480 | onChange: function(color) { 481 | colorElem.value = color; 482 | userinput.controlEvent({target: colorElem}); 483 | }, 484 | }); 485 | colorElem.picker.setColor(initValue); 486 | } 487 | 488 | function addRangeSliderPolyfills() { 489 | let sliderList = document.querySelectorAll('input[type="range"]'); 490 | for (let i = 0; i < sliderList.length; i++) { 491 | createRangeSliderPolyfill(sliderList[i]); 492 | } 493 | } 494 | 495 | function createRangeSliderPolyfill(rangeElem) { 496 | let options = {polyfill: true}; 497 | if (rangeElem.attributes.min) { 498 | options.min = rangeElem.attributes.min; 499 | } 500 | if (rangeElem.attributes.max) { 501 | options.max = rangeElem.attributes.max; 502 | } 503 | if (rangeElem.attributes.step && rangeElem.attributes.step.value == 'any') { 504 | options.step = 0.001; 505 | } else if (rangeElem.attributes.step) { 506 | options.step = rangeElem.attributes.step; 507 | } 508 | rangeSlider.create(rangeElem, options); 509 | } 510 | 511 | function createApp(serverUrl) { 512 | global.g_serverUrl = serverUrl; 513 | initWebSocket(serverUrl.replace("http", "ws")); 514 | retrieve.retrieveHostInfo(serverUrl, (err, hostInfo) => { 515 | if (hostInfo) { 516 | storeHostInfo(hostInfo); 517 | } 518 | retrieve.retrieveJson(serverUrl, (err, result) => { 519 | if (err) { 520 | let mainContentsElem = $('#mainContents'); 521 | let errorElem = document.createElement('div'); 522 | errorElem.innerHTML = '' + err + ''; 524 | let firstElem = mainContentsElem.children[0]; 525 | mainContentsElem.insertBefore(errorElem, firstElem); 526 | return; 527 | } 528 | detectColorPicker(); 529 | buildFromQueryResult(result); 530 | storeControlStructure(result); 531 | addInputEventHandlers(); 532 | addColorPickerPolyfills(); 533 | addRangeSliderPolyfills(); 534 | }); 535 | }); 536 | } 537 | 538 | module.exports = { 539 | createApp: createApp, 540 | getDataEvent: getDataEvent, 541 | extractControlPaths: extractControlPaths, 542 | }; 543 | -------------------------------------------------------------------------------- /src/retrieve.js: -------------------------------------------------------------------------------- 1 | import 'whatwg-fetch'; 2 | 3 | function retrieveHostInfo(url, cb) { 4 | var hostInfoUrl = url + '/?HOST_INFO'; 5 | fetch(hostInfoUrl) 6 | .then(function(response) { 7 | return response.json(); 8 | }) 9 | .then(function(json) { 10 | cb(null, json); 11 | }).catch(function(err) { 12 | cb('No HOST_INFO "' + err + '"', null); 13 | }); 14 | } 15 | 16 | function retrieveJson(url, cb) { 17 | fetch(url) 18 | .then(function(response) { 19 | return response.json(); 20 | }) 21 | .then(function(json) { 22 | cb(null, json); 23 | }).catch(function(err) { 24 | cb('Failed to process JSON: ' + err, null); 25 | }); 26 | } 27 | 28 | export {retrieveJson, retrieveHostInfo}; 29 | -------------------------------------------------------------------------------- /src/settings.js: -------------------------------------------------------------------------------- 1 | // Configuration changes initiated by user: LISTEN, TOGGLE, and STYLE. 2 | 3 | // TODO: Common file. 4 | function $(selector) { 5 | return document.querySelector(selector); 6 | } 7 | 8 | // If OSC is ready (connected), start listening, otherwise wait 10 ms. 9 | function enableInitialListenState() { 10 | if (global.isOscReady) { 11 | listenIgnoreChange(true); 12 | } else { 13 | setTimeout(enableInitialListenState, 10); 14 | } 15 | } 16 | 17 | function listenClick(e) { 18 | listenIgnoreChange(true); 19 | } 20 | 21 | function ignoreClick(e) { 22 | listenIgnoreChange(false); 23 | } 24 | 25 | // Change the listen / ignore state, and send commands. 26 | function listenIgnoreChange(state) { 27 | global.g_isListenEnabled = state; 28 | let command = null; 29 | if (state) { 30 | $('.svg-listen').style.display = 'none'; 31 | $('.svg-ignore').style.display = 'inline-block'; 32 | command = 'LISTEN'; 33 | } else { 34 | $('.svg-listen').style.display = 'inline-block'; 35 | $('.svg-ignore').style.display = 'none'; 36 | command = 'IGNORE'; 37 | } 38 | if (global.isOscReady) { 39 | for (let i = 0; i < global.g_allControlStruct.length; i++) { 40 | var path = global.g_allControlStruct[i]; 41 | var msg = JSON.stringify( 42 | { 43 | 'COMMAND': command, 44 | 'DATA': path 45 | }); 46 | console.log('***** Sending WS: ' + msg); 47 | global.oscPort.socket.send(msg); 48 | } 49 | } 50 | } 51 | 52 | const TOGGLE_SHOW_DISPLAY = 'grid'; 53 | 54 | // Hide some directory. 55 | function toggleHide(e) { 56 | e.preventDefault(); 57 | let elem = e.target; 58 | for (let i = 0; i < 6; i++) { 59 | if (elem.tagName.toLowerCase() == 'div' && elem.id) { 60 | break; 61 | } 62 | elem = elem.parentNode; 63 | } 64 | let text = elem.id; 65 | let id = text.substr(12); 66 | $('#control_body_' + id).style.display = 'none'; 67 | $('#toggle_show_' + id).style.display = TOGGLE_SHOW_DISPLAY; 68 | $('#toggle_hide_' + id).style.display = 'none'; 69 | if (e.altKey) { 70 | let controlBody = $('#control_body_' + id); 71 | if (!controlBody) { 72 | return; 73 | } 74 | let dirContainerElems = controlBody.querySelectorAll('.dir-container'); 75 | for (let i = 0; i < dirContainerElems.length; i++) { 76 | let toggleElem = dirContainerElems[i].querySelector('.toggle-hide'); 77 | toggleHide({target: toggleElem, altKey: true}); 78 | } 79 | } 80 | } 81 | 82 | // Show some directory. 83 | function toggleShow(e) { 84 | e.preventDefault(); 85 | let elem = e.target; 86 | for (let i = 0; i < 6; i++) { 87 | if (elem.tagName.toLowerCase() == 'div' && elem.id) { 88 | break; 89 | } 90 | elem = elem.parentNode; 91 | } 92 | let text = elem.id; 93 | let id = text.substr(12); 94 | $('#control_body_' + id).style.display = TOGGLE_SHOW_DISPLAY; 95 | $('#toggle_show_' + id).style.display = 'none'; 96 | $('#toggle_hide_' + id).style.display = TOGGLE_SHOW_DISPLAY; 97 | if (e.altKey) { 98 | let controlBody = $('#control_body_' + id); 99 | if (!controlBody) { 100 | return; 101 | } 102 | let dirContainerElems = controlBody.querySelectorAll('.dir-container'); 103 | for (let i = 0; i < dirContainerElems.length; i++) { 104 | let toggleElem = dirContainerElems[i].querySelector('.toggle-show'); 105 | toggleShow({target: toggleElem, altKey: true}); 106 | } 107 | } 108 | } 109 | 110 | // Set the visual style to light or dark. 111 | function setStyleMode(mode) { 112 | if (mode == 'light') { 113 | $('#choice-dark-mode').style.display = 'none'; 114 | $('#choice-light-mode').style.display = 'block'; 115 | $('body').classList.add('light'); 116 | document.cookie = 'style=light'; 117 | } else if (mode == 'dark') { 118 | $('#choice-light-mode').style.display = 'none'; 119 | $('#choice-dark-mode').style.display = 'block'; 120 | $('body').classList.remove('light'); 121 | document.cookie = 'style=dark'; 122 | } 123 | } 124 | 125 | module.exports = { 126 | enableInitialListenState: enableInitialListenState, 127 | listenClick: listenClick, 128 | ignoreClick: ignoreClick, 129 | toggleHide: toggleHide, 130 | toggleShow: toggleShow, 131 | setStyleMode: setStyleMode, 132 | } 133 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | function extractControlKind(type, html) { 2 | if (type == 'c') { 3 | if (html.includes('')) { 12 | return 'dropdown'; 13 | } else if (html.includes('')) { 22 | return 'dropdown'; 23 | } else { 24 | return 'text'; 25 | } 26 | } else if (type == 'F' || type == 'T') { 27 | return 'toggle'; 28 | } else if (type == 'I' || type == 'N') { 29 | return 'button'; 30 | } else if (type == 'm' || type == 't') { 31 | return 'none'; 32 | } else { 33 | return 'unknown'; 34 | } 35 | } 36 | 37 | function typeToControlName(type) { 38 | if (type == 'c') { 39 | return 'char'; 40 | } else if (type == 'r') { 41 | return 'color'; 42 | } else if (type == 'd') { 43 | return 'double'; 44 | } else if (type == 'F') { 45 | return 'false'; 46 | } else if (type == 'f') { 47 | return 'float'; 48 | } else if (type == 'I') { 49 | return 'infinity'; 50 | } else if (type == 'i') { 51 | return 'integer'; 52 | } else if (type == 'h') { 53 | return 'longlong'; 54 | } else if (type == 'm') { 55 | return 'midi'; 56 | } else if (type == 'N') { 57 | return 'null'; 58 | } else if (type == 'a') { 59 | return 'string'; 60 | } else if (type == 'T') { 61 | return 'true'; 62 | } else if (type == 't') { 63 | return 'timetag'; 64 | } else { 65 | return 'unknown'; 66 | } 67 | } 68 | 69 | module.exports = { 70 | extractControlKind: extractControlKind, 71 | } 72 | -------------------------------------------------------------------------------- /src/userinput.js: -------------------------------------------------------------------------------- 1 | const controls = require('./controls.js'); 2 | 3 | function toggleEvent(e) { 4 | // Control that was modified. 5 | let controlElem = e.target.parentNode; 6 | let detailsElem = controlElem.querySelector('.details'); 7 | let setter = detailsElem.attributes['data-setter']; 8 | // Special hook for toggles because we need to modify the value before 9 | // calling `getControlArg` in the main `controlEvent` handler. 10 | if (setter) { 11 | controls.runSetter(controlElem, 'setToggleBeforeGetControlArg', 12 | e.target.value); 13 | } 14 | return controlEvent(e); 15 | } 16 | 17 | // Handle an event on a control, return whether an OSC message was sent. 18 | function controlEvent(e) { 19 | // Control that was modified. 20 | let controlElem = e.target.parentNode; 21 | let detailsElem = controlElem.querySelector('.details'); 22 | let fullPath = detailsElem.attributes['data-full-path'].value; 23 | let setter = detailsElem.attributes['data-setter']; 24 | // Group that contains this control (in case the node has multiple types). 25 | let groupElem = controlElem.parentNode; 26 | let args = []; 27 | for (let i = 0; i < groupElem.children.length; i++) { 28 | let c = groupElem.children[i]; 29 | if (c.tagName.toLowerCase() == 'div' && 30 | c.classList.contains('control')) { 31 | args.push(controls.getControlArg(c)); 32 | } 33 | } 34 | if (setter) { 35 | controls.runSetter(controlElem, setter.value, e.target.value); 36 | } 37 | var message = { 38 | address: fullPath, 39 | args: args, 40 | }; 41 | console.log('***** Sending value: ' + JSON.stringify(message)); 42 | if (window.isOscReady) { 43 | oscPort.send(message); 44 | return true; 45 | } 46 | return false; 47 | } 48 | 49 | function charKeyPressEvent(e) { 50 | e.target.value = String.fromCharCode(e.keyCode); 51 | return controlEvent(e); 52 | } 53 | 54 | global.g_numRangeMessagePending = 0; 55 | global.g_lastRangeMessageSent = null; 56 | 57 | function rangeModifyEvent(e) { 58 | let value = e.target.value; 59 | // Cache value so that it won't send twice in a row. 60 | if (e.target.cacheValue === value) { 61 | return; 62 | } 63 | e.target.cacheValue = value 64 | if (controlEvent(e)) { 65 | if (global.g_isListenEnabled) { 66 | global.g_numRangeMessagePending++; 67 | global.g_lastRangeMessageSent = new Date(); 68 | } 69 | } 70 | } 71 | 72 | global.g_numColorMessagePending = 0; 73 | global.g_lastColorMessageSent = null; 74 | 75 | function colorModifyEvent(e) { 76 | if (controlEvent(e)) { 77 | if (global.g_isListenEnabled) { 78 | global.g_numColorMessagePending++; 79 | global.g_lastColorMessageSent = new Date(); 80 | } 81 | } 82 | } 83 | 84 | setInterval(function() { 85 | let now = new Date(); 86 | if (global.g_lastRangeMessageSent) { 87 | if (now - global.g_lastRangeMessageSent > 1000) { 88 | global.g_numRangeMessagePending = 0; 89 | global.g_lastRangeMessageSent = null; 90 | } 91 | } 92 | if (global.g_lastColorMessageSent) { 93 | if (now - global.g_lastColorMessageSent > 1000) { 94 | global.g_numColorMessagePending = 0; 95 | global.g_lastColorMessageSent = null; 96 | } 97 | } 98 | }, 400); 99 | 100 | module.exports = { 101 | toggleEvent: toggleEvent, 102 | controlEvent: controlEvent, 103 | charKeyPressEvent: charKeyPressEvent, 104 | rangeModifyEvent: rangeModifyEvent, 105 | colorModifyEvent: colorModifyEvent, 106 | } 107 | -------------------------------------------------------------------------------- /test/builder.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const index = require('../src/index.js'); 3 | const builder = require('../src/builder.js'); 4 | 5 | describe('util', () => { 6 | describe('getDataEvent', () => { 7 | it('returns data-event value', () => { 8 | let obj = {'attributes': {'data-event': {'value': 'keypress'}}}; 9 | assert.equal(index.getDataEvent(obj), 'keypress'); 10 | obj = {'attributes': {'class': 'container'}} 11 | assert.equal(index.getDataEvent(obj), null); 12 | }); 13 | }); 14 | }); 15 | 16 | var g_supportHtml5Color; 17 | 18 | describe('buildSingleControl', () => { 19 | describe('i', () => { 20 | it('returns int control', () => { 21 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p'}; 22 | let html = builder.buildSingleControl(data, 'i', [0], 0); 23 | assert.equal(html, 24 | '' + 25 | '0' + 26 | '' + 27 | '' + 28 | ''); 31 | }); 32 | }); 33 | describe('i with min,max', () => { 34 | it('returns int control with min and max', () => { 35 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 36 | 'RANGE': [{'MIN': 0, 'MAX': 10}]}; 37 | let html = builder.buildSingleControl(data, 'i', [0], 0); 38 | assert.equal(html, 39 | '' + 40 | '0' + 41 | ' (0-10)' + 42 | '' + 43 | '' + 44 | ''); 47 | }); 48 | }); 49 | describe('i with vals', () => { 50 | it('returns int control with values in a dropdown', () => { 51 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 52 | 'RANGE': [{'VALS': [3,4,5,6]}]}; 53 | let html = builder.buildSingleControl(data, 'i', [0], 0); 54 | assert.equal(html, 55 | '' + 60 | ''); 62 | }); 63 | }); 64 | describe('i with min,max almost the same', () => { 65 | it('returns int control with checkbox', () => { 66 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 67 | 'RANGE': [{'MIN': 8, 'MAX': 9}]}; 68 | let html = builder.buildSingleControl(data, 'i', [0], 0); 69 | assert.equal(html, 70 | '' + 72 | ''); 75 | }); 76 | }); 77 | describe('i with only 2 vals', () => { 78 | it('returns int control with checkbox', () => { 79 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 80 | 'RANGE': [{'VALS': [5, 10]}]}; 81 | let html = builder.buildSingleControl(data, 'i', [0], 0); 82 | assert.equal(html, 83 | '' + 85 | ''); 88 | }); 89 | }); 90 | describe('i with min,max exactly the same', () => { 91 | it('returns int control with button', () => { 92 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 93 | 'RANGE': [{'MIN': 20, 'MAX': 20}]}; 94 | let html = builder.buildSingleControl(data, 'i', [0], 0); 95 | assert.equal(html, 96 | '' + 97 | ''); 100 | }); 101 | }); 102 | describe('i with only 1 val', () => { 103 | it('returns int control with button', () => { 104 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 105 | 'RANGE': [{'VALS': [32]}]}; 106 | let html = builder.buildSingleControl(data, 'i', [0], 0); 107 | assert.equal(html, 108 | '' + 109 | ''); 112 | }); 113 | }); 114 | describe('i with invalid size of RANGE', () => { 115 | it('returns a message about invalid node', () => { 116 | let data = {'TYPE': 'i', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 117 | 'RANGE': []}; 118 | let html = builder.buildSingleControl(data, 'i', [0], 0); 119 | assert.equal(html, 120 | 'Invalid node: RANGE needs ' + 121 | 'MIN,MAX or VALS'); 122 | }); 123 | }); 124 | describe('h', () => { 125 | it('returns longlong control', () => { 126 | let data = {'TYPE': 'h', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p'}; 127 | let html = builder.buildSingleControl(data, 'h', [0], 0); 128 | assert.equal(html, 129 | '' + 130 | '0' + 131 | '' + 132 | '' + 133 | ''); 136 | }); 137 | }); 138 | describe('T', () => { 139 | it('returns true control', () => { 140 | let data = {'TYPE': 'T', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p'}; 141 | let html = builder.buildSingleControl(data, 'T', [0], 0); 142 | assert.equal(html, 143 | '' + 145 | ''); 148 | }); 149 | }); 150 | describe('F', () => { 151 | it('returns false control', () => { 152 | let data = {'TYPE': 'F', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p'}; 153 | let html = builder.buildSingleControl(data, 'F', [0], 0); 154 | assert.equal(html, 155 | '' + 157 | ''); 160 | }); 161 | }); 162 | describe('r creates colorPicker', () => { 163 | it('returns an html5 color control', () => { 164 | let cfg = {supportHtml5Color: true}; 165 | let data = {'TYPE': 'r', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 166 | 'RANGE': []}; 167 | let html = builder.buildSingleControl(data, 'r', [0], 0, cfg); 168 | assert.equal(html, 169 | '' + 170 | ''); 173 | }); 174 | it('returns a third-party color control', () => { 175 | let data = {'TYPE': 'r', 'DESCRIPTION': 'desc', 'FULL_PATH': '/p', 176 | 'RANGE': []}; 177 | let html = builder.buildSingleControl(data, 'r', [0], 0); 178 | assert.equal(html, 179 | '
' + 180 | '
' + 181 | ''); 184 | }); 185 | }); 186 | }); 187 | 188 | -------------------------------------------------------------------------------- /test/extract.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const index = require('../src/index.js'); 3 | 4 | describe('extractControlPaths', () => { 5 | describe('two controls', () => { 6 | it('returns two paths', () => { 7 | let data = { 8 | 'FULL_PATH': '/', 9 | 'CONTENTS' : { 10 | 'dir' : { 11 | 'FULL_PATH': '/dir', 12 | 'CONTENTS' : { 13 | 'a_control' : { 14 | 'FULL_PATH': '/dir/a_control', 15 | 'TYPE' : 'c', 16 | }, 17 | 'b_control' : { 18 | 'FULL_PATH': '/dir/b_control', 19 | 'TYPE' : 'r', 20 | } 21 | } 22 | } 23 | } 24 | }; 25 | let paths = index.extractControlPaths(data); 26 | assert.deepEqual(paths, ['/dir/a_control', '/dir/b_control']); 27 | }); 28 | }); 29 | describe('has contents and type', () => { 30 | it('returns two paths', () => { 31 | let data = { 32 | 'FULL_PATH': '/', 33 | 'CONTENTS' : { 34 | 'dir' : { 35 | 'FULL_PATH': '/dir', 36 | 'CONTENTS' : { 37 | 'a_control' : { 38 | 'FULL_PATH': '/dir/a_control', 39 | 'TYPE' : 'c', 40 | }, 41 | }, 42 | 'TYPE': 'i' 43 | } 44 | } 45 | }; 46 | let paths = index.extractControlPaths(data); 47 | assert.deepEqual(paths, ['/dir/a_control', '/dir']); 48 | }); 49 | }); 50 | }); 51 | 52 | -------------------------------------------------------------------------------- /web/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | 4 | const PORT = 5050; 5 | 6 | app.set('view engine', 'ejs'); 7 | app.set('views', __dirname + '/views'); 8 | app.use(express.static('../assets/')); 9 | app.use(express.static('../output')); 10 | 11 | app.get('/', (req, res) => { 12 | const url = process.env['SERVER_URL']; 13 | if (!url) { 14 | throw 'SERVER_URL not set, cannot connect to OSC Server' 15 | } 16 | let context = {hostUrl: url}; 17 | res.render('pages/index', context); 18 | }); 19 | 20 | app.listen(PORT, () => { 21 | console.log(`Listening on port ${PORT}`); 22 | }); 23 | -------------------------------------------------------------------------------- /web/views/pages/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OSC Query Tool 5 | 6 | 7 | 8 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /web/views/pages/range-slider.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vidvox/oscqueryhtml/ca2504114c59569bee665fde473c22f6d98a273c/web/views/pages/range-slider.css -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: ['whatwg-fetch', './src/index.js'], 6 | devtool: 'source-map', 7 | output: { 8 | path: path.resolve(__dirname, 'output'), 9 | filename: 'bundle.js', 10 | library: 'oscQuery', 11 | libraryTarget: 'window', 12 | }, 13 | resolve: { 14 | alias: { 15 | 'osc': path.join(__dirname, 'node_modules/osc/src/osc.js'), 16 | 'osc-transports': path.join(__dirname, 'node_modules/osc/src/' + 17 | 'osc-transports.js'), 18 | 'osc-websocket-client': path.join(__dirname, 'node_modules/osc/src/' + 19 | 'platforms/osc-websocket-client.js'), 20 | 'vanilla-picker': path.join(__dirname, 'node_modules/vanilla-picker/' + 21 | 'dist/vanilla-picker.js'), 22 | } 23 | }, 24 | node: { 25 | fs: 'empty', 26 | tls: 'empty', 27 | }, 28 | externals: { 29 | ws: 'ws' 30 | }, 31 | optimization: { 32 | minimizer: [ 33 | new UglifyJsPlugin({ 34 | sourceMap: true, 35 | uglifyOptions:{safari10:true} 36 | }) 37 | ] 38 | } 39 | }; 40 | --------------------------------------------------------------------------------