diff --git a/.htmlhintrc b/.htmlhintrc new file mode 100644 index 0000000000000000000000000000000000000000..86dbac70f2f2827ba3bb98eae879dc285d00def6 --- /dev/null +++ b/.htmlhintrc @@ -0,0 +1,25 @@ +{ + "alt-require": false, + "attr-lowercase": true, + "attr-no-duplication": true, + "attr-unsafe-chars": true, + "attr-value-double-quotes": true, + "attr-value-not-empty": false, + "doctype-first": true, + "doctype-html5": true, + "head-script-disabled": false, + "href-abs-or-rel": false, + "id-class-ad-disabled": false, + "id-class-value": "dash", + "id-unique": true, + "inline-script-disabled": false, + "inline-style-disabled": true, + "space-tab-mixed-disabled": "space2", + "spec-char-escape": true, + "src-not-empty": true, + "style-disabled": false, + "tag-pair": true, + "tag-self-close": false, + "tagname-lowercase": true, + "title-require": true +} diff --git a/.travis.yml b/.travis.yml index 73a40d0761a7b442cd2435b7ac37a59f5c13a875..890fcfd3ac3ca6f4d4ac9507586110c817dc3edf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,6 @@ before_install: - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1 - export PATH="$HOME/.yarn/bin:$PATH" install: yarn -script: yarn lint +script: +- yarn lint +- ./scripts/check-yarn-lock.sh diff --git a/package.json b/package.json index 1b0e87daa003040077505b1403c365ecc97c5c36..d14d1c7a5166f8cf321335363872fa2da4806c6b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "start": "cross-env NODE_ENV=development webpack-dev-server", "build": "rimraf ./public && cross-env NODE_ENV=production webpack --mode=production", "prettier": "prettier --write '*.js' 'src/**/*.js'", - "lint": "eslint '*.js' 'src/**/*.js'" + "lint:js": "eslint '*.js' 'scripts/**/*.js' 'src/**/*.js'", + "lint:html": "node ./scripts/lint-html.js 'src/**/*.html'", + "lint": "yarn run lint:js && yarn run lint:html" }, "dependencies": { "@fortawesome/fontawesome": "^1.1.5", @@ -22,7 +24,7 @@ "aframe-extras": "^4.0.0", "aframe-input-mapping-component": "https://github.com/johnshaughnessy/aframe-input-mapping-component#feature/map-to-array", "aframe-physics-extras": "https://github.com/infinitelee/aframe-physics-extras#fix/physics-collider-crash", - "aframe-physics-system": "https://github.com/donmccurdy/aframe-physics-system", + "aframe-physics-system": "https://github.com/infinitelee/aframe-physics-system#feature/shape-component", "aframe-rounded": "^1.0.3", "aframe-slice9-component": "^1.0.0", "aframe-teleport-controls": "^0.3.1", @@ -33,10 +35,13 @@ "jsonschema": "^1.2.2", "minijanus": "^0.5.0", "mobile-detect": "^1.4.1", + "moment": "^2.22.0", + "moment-timezone": "^0.5.14", "moving-average": "^1.0.0", "naf-janus-adapter": "https://github.com/mozilla/naf-janus-adapter#feature/disconnect", "networked-aframe": "https://github.com/mozillareality/networked-aframe#mr-social-client/master", "nipplejs": "^0.6.7", + "phoenix": "^1.3.0", "query-string": "^5.0.1", "raven-js": "^3.20.1", "react": "^16.1.1", @@ -67,12 +72,14 @@ "file-loader": "^1.1.10", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.1.0", + "htmlhint": "^0.9.13", "lodash": "^4.17.5", "node-sass": "^4.7.2", "prettier": "^1.7.0", "rimraf": "^2.6.2", "sass-loader": "^6.0.7", "selfsigned": "^1.10.2", + "shelljs": "^0.8.1", "style-loader": "^0.20.2", "webpack": "^4.0.1", "webpack-cli": "^2.0.9", diff --git a/scripts/check-yarn-lock.sh b/scripts/check-yarn-lock.sh new file mode 100755 index 0000000000000000000000000000000000000000..e27b56ab47bc9746933266a02f0c30b7a7b9c6e1 --- /dev/null +++ b/scripts/check-yarn-lock.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +if [ `git diff yarn.lock | wc -l` -ne 0 ]; then + echo "" + tput setaf 1 + echo "!! UNCOMMITED YARN.LOCK CHANGES !!" + tput sgr0 + echo "" + exit 1 +fi diff --git a/scripts/default.env b/scripts/default.env index 0fd18aa5be8791b61c69d2587c4611ef7f1f6b70..a1186bbf1f20d84c6883a36bedea1e6ac88e4e7c 100644 --- a/scripts/default.env +++ b/scripts/default.env @@ -1,5 +1,5 @@ # This origin trial token is used to enable WebVR and Gamepad Extensions on Chrome 62+ # You can find more information about getting your own origin trial token here: https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md -ORIGIN_TRIAL_TOKEN="AvIMoF4hyRZQVfSfksoqP+7qzwa4FSBzHRHvUyzC8rMATJVRbcOiLewBxbXtJVyV3N62gsZv7PoSNtDqqtjzYAcAAABkeyJvcmlnaW4iOiJodHRwczovL3JldGljdWx1bS5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViVlIxLjFNNjIiLCJleHBpcnkiOjE1MTYxNDYyMDQsImlzU3ViZG9tYWluIjp0cnVlfQ==", -ORIGIN_TRIAL_EXPIRES="2018-05-15", +ORIGIN_TRIAL_TOKEN="ArEZ0vY0uMo3pj+oY8Up4u4Hy8QolJwKxG4/2WRhSPnTZRrviiGhzP6/y72nBdsIhdEyoundxqg//KLbs2vGnQoAAABkeyJvcmlnaW4iOiJodHRwczovL3JldGljdWx1bS5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViVlIxLjFNNjIiLCJleHBpcnkiOjE1MjYzNDg2MjEsImlzU3ViZG9tYWluIjp0cnVlfQ==" +ORIGIN_TRIAL_EXPIRES="2018-05-15" JANUS_SERVER="wss://prod-janus.reticulum.io" diff --git a/scripts/lint-html.js b/scripts/lint-html.js new file mode 100644 index 0000000000000000000000000000000000000000..d8891e836d3e9ed9cf0a8b7dea0aee107188a6fe --- /dev/null +++ b/scripts/lint-html.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +const { promisify } = require("util"); +const fs = require("fs"); +const mkdtemp = promisify(fs.mkdtemp); +const path = require("path"); +const os = require("os"); +const shell = require("shelljs"); + +(async function() { + function lintFile(tempDir, arg, file) { + const out = path.join(tempDir, file); + shell.mkdir("-p", path.dirname(out)); + shell.sed(/<%.+%>/, "", file).to(out); + const result = shell.exec(`node_modules/.bin/htmlhint ${arg} --config=.htmlhintrc ${out}`); + return result.code; + } + + let result = 0; + if (process.argv.length > 2) { + const tempDir = await mkdtemp(path.join(os.tmpdir(), "lint-html-")); + let files; + let arg = ""; + if (process.argv.length === 4) { + arg = process.argv[2]; + files = process.argv[3]; + } else { + files = process.argv[2]; + } + const results = shell.ls(files).map(lintFile.bind(null, tempDir, arg)); + result = results.reduce((a, r) => a + r, 0); + shell.rm("-r", tempDir); + } + + shell.exit(result); +})(); diff --git a/src/assets/images/dropdown_arrow.png b/src/assets/images/dropdown_arrow.png new file mode 100755 index 0000000000000000000000000000000000000000..caa42c1ffed82796540acdc192201cf20e822e0b Binary files /dev/null and b/src/assets/images/dropdown_arrow.png differ diff --git a/src/assets/images/dropdown_arrow@2x.png b/src/assets/images/dropdown_arrow@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..d4e74eb212652021837a17d860578c6f7114dcd5 Binary files /dev/null and b/src/assets/images/dropdown_arrow@2x.png differ diff --git a/src/assets/images/level_background.png b/src/assets/images/level_background.png new file mode 100755 index 0000000000000000000000000000000000000000..9d53b3c6dc75552b225d5717fa6fb8cd883b05cb Binary files /dev/null and b/src/assets/images/level_background.png differ diff --git a/src/assets/images/level_background@2x.png b/src/assets/images/level_background@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..4a9f08acc76396f4133673faed5b4ae38ca6cc87 Binary files /dev/null and b/src/assets/images/level_background@2x.png differ diff --git a/src/assets/images/level_fill.png b/src/assets/images/level_fill.png old mode 100644 new mode 100755 index 99f77b5655e6a50e0444364a3c2cfb4882b3b2d9..49a4f8a75064870db870bf994e8b25671205bfb0 Binary files a/src/assets/images/level_fill.png and b/src/assets/images/level_fill.png differ diff --git a/src/assets/images/level_fill@2x.png b/src/assets/images/level_fill@2x.png old mode 100644 new mode 100755 index 477d9801bb6d33737b571fce454ff265ff79e77c..28f313bc9d541fc92fd65c03945b35c1affdf9cb Binary files a/src/assets/images/level_fill@2x.png and b/src/assets/images/level_fill@2x.png differ diff --git a/src/assets/images/mic_level.png b/src/assets/images/mic_level.png old mode 100644 new mode 100755 index 5be15458d9ed41c46f861d8dd8435a11e452f80c..e4c1367ddf78efd48173a3d0a64c4c48c953a871 Binary files a/src/assets/images/mic_level.png and b/src/assets/images/mic_level.png differ diff --git a/src/assets/images/mic_level@2x.png b/src/assets/images/mic_level@2x.png old mode 100644 new mode 100755 index 94739aa1977cc5d5317eeb770905ed212ff248b4..621f944ed0b07b1a625a2627f5646406fcefbd98 Binary files a/src/assets/images/mic_level@2x.png and b/src/assets/images/mic_level@2x.png differ diff --git a/src/assets/images/speaker_level.png b/src/assets/images/speaker_level.png old mode 100644 new mode 100755 index 9ccedcc0350f90c95744d928128594829b5f5b90..f0557615258997bb7c54e7a6028e052c9c8a33f4 Binary files a/src/assets/images/speaker_level.png and b/src/assets/images/speaker_level.png differ diff --git a/src/assets/images/speaker_level@2x.png b/src/assets/images/speaker_level@2x.png old mode 100644 new mode 100755 index a807745cbcaaf823e6e8e99deda15459d1ed1d9a..3d60f4b8d287742ad3076ae7e63f988cca029f89 Binary files a/src/assets/images/speaker_level@2x.png and b/src/assets/images/speaker_level@2x.png differ diff --git a/src/assets/interactables/duck/DuckyMesh.glb b/src/assets/interactables/duck/DuckyMesh.glb index 5bb10e0cd79c623f72f87d438f6eddbe1d0dc250..f50dc37942d519c36e16a5e41bb8a44a18c4c7f1 100644 Binary files a/src/assets/interactables/duck/DuckyMesh.glb and b/src/assets/interactables/duck/DuckyMesh.glb differ diff --git a/src/assets/interactables/duck/gltf/DuckyMesh.fbm/Ducky.jpg b/src/assets/interactables/duck/gltf/DuckyMesh.fbm/Ducky.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c27845f40d1e4b91ddb5534c413f408580ed51e Binary files /dev/null and b/src/assets/interactables/duck/gltf/DuckyMesh.fbm/Ducky.jpg differ diff --git a/src/assets/interactables/duck/gltf/DuckyMesh.gltf b/src/assets/interactables/duck/gltf/DuckyMesh.gltf new file mode 100644 index 0000000000000000000000000000000000000000..83cc03646480c7cec3849fda0a8fd3580cec3abc --- /dev/null +++ b/src/assets/interactables/duck/gltf/DuckyMesh.gltf @@ -0,0 +1,248 @@ +{ + "asset": { + "generator": "FBX2glTF", + "version": "2.0" + }, + "scene": 0, + "buffers": [ + { + "byteLength": 18600, + "uri": "buffer.bin" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 3336, + "byteOffset": 0, + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 5724, + "byteOffset": 3336, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 5724, + "byteOffset": 9060, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 3816, + "byteOffset": 14784, + "target": 34962 + } + ], + "scenes": [ + { + "name": "Root Scene", + "nodes": [ + 0 + ], + "extras": { + "components": { + "shape": [ + { + "shape": "box", + "halfExtents": { + "x": 0.06, + "y": 0.04, + "z": 0.08 + }, + "offset": { + "x": 0, + "y": 0.052671334114334445, + "z": 0.01001389278835843 + }, + "orientation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "shape": "sphere", + "radius": 0.05, + "offset": { + "x": 0, + "y": 0.1287570519589527, + "z": 0.033095376412929145 + }, + "orientation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "shape": "cylinder", + "radiusTop": 0.02, + "radiusBottom": 0.02, + "height": 0.030000000000000013, + "numSegments": 8, + "offset": { + "x": 0, + "y": 0.12657048237702667, + "z": 0.09010837508332667 + }, + "orientation": { + "x": 0.7071067811865476, + "y": 0, + "z": 0, + "w": 0.7071067811865475 + } + } + ] + } + } + } + ], + "accessors": [ + { + "componentType": 5123, + "type": "SCALAR", + "count": 1668, + "bufferView": 0, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 477, + "bufferView": 1, + "byteOffset": 0, + "min": [ + -0.0587991699576378, + 0.0129461474716663, + -0.0740185976028442 + ], + "max": [ + 0.0618169121444225, + 0.173104390501976, + 0.104356855154037 + ] + }, + { + "componentType": 5126, + "type": "VEC3", + "count": 477, + "bufferView": 2, + "byteOffset": 0 + }, + { + "componentType": 5126, + "type": "VEC2", + "count": 477, + "bufferView": 3, + "byteOffset": 0 + } + ], + "images": [ + { + "name": "DuckyMesh.fbm/Ducky.jpg", + "uri": "DuckyMesh.fbm/Ducky.jpg" + } + ], + "samplers": [ + {} + ], + "textures": [ + { + "name": "file4", + "sampler": 0, + "source": 0 + } + ], + "materials": [ + { + "name": "lambert2", + "alphaMode": "OPAQUE", + "extras": { + "fromFBX": { + "shadingModel": "Lambert", + "isTruePBR": false + } + }, + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "texCoord": 0 + }, + "baseColorFactor": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor": 0.200000002980232, + "roughnessFactor": 0.800000011920929 + } + } + ], + "meshes": [ + { + "name": "Ducky", + "primitives": [ + { + "material": 0, + "mode": 4, + "attributes": { + "NORMAL": 2, + "POSITION": 1, + "TEXCOORD_0": 3 + }, + "indices": 0 + } + ] + } + ], + "nodes": [ + { + "name": "RootNode", + "translation": [ + 0, + 0, + 0 + ], + "rotation": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ], + "children": [ + 1 + ] + }, + { + "name": "Ducky", + "translation": [ + 0, + 0, + 0 + ], + "rotation": [ + 0, + 0, + 0, + 1 + ], + "scale": [ + 1, + 1, + 1 + ], + "mesh": 0 + } + ] +} \ No newline at end of file diff --git a/src/assets/interactables/duck/gltf/DuckyMesh.json b/src/assets/interactables/duck/gltf/DuckyMesh.json new file mode 100644 index 0000000000000000000000000000000000000000..97b41944a2ca7e12e5e12cf4892d2534ef73e5b4 --- /dev/null +++ b/src/assets/interactables/duck/gltf/DuckyMesh.json @@ -0,0 +1,60 @@ +{ + "scenes": { + "Root Scene": { + "shape": [ + { + "shape": "box", + "halfExtents": { + "x": 0.06, + "y": 0.04, + "z": 0.08 + }, + "offset": { + "x": 0, + "y": 0.052671334114334445, + "z": 0.01001389278835843 + }, + "orientation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "shape": "sphere", + "radius": 0.05, + "offset": { + "x": 0, + "y": 0.1287570519589527, + "z": 0.033095376412929145 + }, + "orientation": { + "x": 0, + "y": 0, + "z": 0, + "w": 1 + } + }, + { + "shape": "cylinder", + "radiusTop": 0.02, + "radiusBottom": 0.02, + "height": 0.030000000000000013, + "numSegments": 8, + "offset": { + "x": 0, + "y": 0.12657048237702667, + "z": 0.09010837508332667 + }, + "orientation": { + "x": 0.7071067811865476, + "y": 0, + "z": 0, + "w": 0.7071067811865475 + } + } + ] + } + } +} diff --git a/src/assets/interactables/duck/gltf/buffer.bin b/src/assets/interactables/duck/gltf/buffer.bin new file mode 100644 index 0000000000000000000000000000000000000000..d7ed323e11aef0c4b2045dd8f2ddd34154244b84 Binary files /dev/null and b/src/assets/interactables/duck/gltf/buffer.bin differ diff --git a/src/assets/stylesheets/2d-hud.css b/src/assets/stylesheets/2d-hud.css index a3509f308bd4638f231ef87b714b7bdb05b5dfcf..a50436114181d18997248dce77a3cc1d9500363f 100644 --- a/src/assets/stylesheets/2d-hud.css +++ b/src/assets/stylesheets/2d-hud.css @@ -38,6 +38,7 @@ display: flex; align-items: center; justify-content: center; + z-index: 10; } :local(.panel) { diff --git a/src/assets/stylesheets/audio.scss b/src/assets/stylesheets/audio.scss index 67aac69962d861e993fb0e58e5dec9441536d2a9..d73a85eddc926c4b9a2f58c21fd4a97521822fd8 100644 --- a/src/assets/stylesheets/audio.scss +++ b/src/assets/stylesheets/audio.scss @@ -28,17 +28,29 @@ @extend %rounded-border; @extend %default-font; + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; background-color: black; padding: 6px; + padding-right: 30px; color: white; font-size: 1.1em; width: 90%; } &__mic-icon { + pointer-events: none; position: absolute; - left: 7.5%; - top: 10px; + left: 8%; + top: 9px; + } + + &__dropdown-arrow { + pointer-events: none; + position: absolute; + right: 7.5%; + top: 16px; } } @@ -50,42 +62,16 @@ align-items: center; width: 100%; - &__mic { - position:relative; - width: 111px; - height: 111px; - } - - &__mic_icon { - position: absolute; - top: 0; - left: 0; - z-index: 2; - min-width: 111px; - min-height: 111px; - } - - &__speaker { + &__icon { position:relative; width: 111px; height: 111px; } - &__speaker_icon { - position: absolute; + &__icon-part { + position:absolute; top: 0; left: 0; - z-index: 2; - min-width: 111px; - min-height: 111px; - } - - &__level { - position: absolute; - top: 0; - left: 0; - opacity: 1.0; - z-index: 1; } } @@ -118,17 +104,26 @@ @extend %top-subtitle; } - &__icon { + &__button-container { flex: 10; display: flex; justify-content: center; align-items: center; cursor: pointer; + width: 111px; + height: 111px; + } + + &__button { + background: none; + border: none; + cursor: pointer; } &__next { @extend %bottom-button; - margin: auto; - flex: 1 1 20px; + padding-top: 0; + padding-bottom: 0; + flex: 1 1; } } diff --git a/src/assets/stylesheets/entry.scss b/src/assets/stylesheets/entry.scss index bdd20d1ee7af717ac0b2808b696c85fe49c8d775..abed31db312891d5d0d4c425b42310851df21553 100644 --- a/src/assets/stylesheets/entry.scss +++ b/src/assets/stylesheets/entry.scss @@ -20,28 +20,18 @@ justify-content: center; &__screen-sharing { - font-size: 1.4em; - margin-left: 2.95em; - margin-top: 0.6em; - } + font-size: 1.4em; + margin-left: 2.95em; + margin-top: 0.6em; - &__screen-sharing-checkbox { - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; - width: 2em; - height: 2em; - border: 3px solid white; - border-radius: 9px; - vertical-align: sub; - margin: 0 0.6em + &__checkbox { + @extend %checkbox; + } + &__checkbox:checked { + @extend %checkbox-checked; + } } - &__screen-sharing-checkbox:checked { - border: 9px double white; - outline: 9px solid white; - outline-offset: -18px; - } &__secondary { width: 100%; @@ -58,6 +48,10 @@ margin-top: 10px; margin-bottom: 10px; cursor: pointer; + background: none; + color: white; + border: none; + @extend %default-font; &__icon { flex: 1 1 90px; diff --git a/src/assets/stylesheets/exited.scss b/src/assets/stylesheets/exited.scss index 72959090e6cf5ed18d29c2750beb7bbb4280fcae..693d6d38798705979478930f0b175ea57aa6e183 100644 --- a/src/assets/stylesheets/exited.scss +++ b/src/assets/stylesheets/exited.scss @@ -1,4 +1,6 @@ .exited-panel { + position: absolute; + color: white; background-color: black; width: 100%; height: 100%; diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss index 000e974bbad3aec3760eb51e9863c78755a590fc..95f2caa2629d34e3f99fbab242f5cbf28b498038 100644 --- a/src/assets/stylesheets/profile.scss +++ b/src/assets/stylesheets/profile.scss @@ -41,6 +41,10 @@ color: $grey-text; } + &__display-name-label { + font-size: 1.2em; + margin-right: 0.5em; + } &__form-field-text { @extend %rounded-border; @extend %default-font; @@ -54,19 +58,34 @@ margin: 0.5em 0; } - &__form-submit { - @extend %default-font; - border: none; + &__terms { + margin-bottom: 16px; - margin: 8px; - width: 100px; - line-height: 1.5em; - font-size: 1.0em; + &__checkbox { + @extend %checkbox; + vertical-align: unset; + } + &__checkbox:checked { + @extend %checkbox-checked; + } - background-color: transparent; - font-weight: bold; - color: white; - cursor: pointer; + &__text { + display: inline-block; + max-width: 20em; + } + + &__link { + color: white; + } + + &__link:visited { + color: grey; + } + } + + &__form-submit { + @extend %bottom-button; + margin: 0; } } @@ -87,12 +106,16 @@ flex: 6 1 auto; font-size: 1.2em; line-height: 50px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } &__app_name { font-size: 1.8em; padding-right: 18px; line-height: 50px; + white-space: nowrap; } } diff --git a/src/assets/stylesheets/shared.scss b/src/assets/stylesheets/shared.scss index c2d4f013de5b853664b751400969602b356a53f9..f959943585bbfacf37791586f5485add3e524cd1 100644 --- a/src/assets/stylesheets/shared.scss +++ b/src/assets/stylesheets/shared.scss @@ -17,11 +17,17 @@ $darker-grey: rgba(64, 64, 64, 1.0); } %bottom-button { + @extend %default-font; font-size: 1em; font-weight: bold; margin-top: auto; margin-bottom: 30px; cursor: pointer; + border: 3px solid white; + border-radius: 14px; + padding: 12px; + background: none; + color: white; } %top-title { @@ -42,3 +48,21 @@ $darker-grey: rgba(64, 64, 64, 1.0); border: none; font-size: 64pt; } + +%checkbox { + appearance: none; + -moz-appearance: none; + -webkit-appearance: none; + width: 2em; + height: 2em; + border: 3px solid white; + border-radius: 9px; + vertical-align: sub; + margin: 0 0.6em +} + +%checkbox-checked { + border: 9px double white; + outline: 9px solid white; + outline-offset: -18px; +} diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 1757ec61cce5b97855750537a7812efe5349a57e..1b9844c8abf2dc7d9ed7c089f84923faa601fd96 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -14,8 +14,14 @@ "entry.daydream-via-chrome": "Using Google Chrome", "entry.enable-screen-sharing": "Share my desktop", "profile.save": "SAVE", + "profile.display_name.label": "Display name:", "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32", "profile.header": "Your identity", + "profile.terms.prefix": "I confirm that I am over the age of 13 and agree to the", + "profile.terms.privacy": "privacy policy", + "profile.terms.conjunction": "and", + "profile.terms.tou": "terms of use", + "profile.terms.suffix": ".", "profile.avatar-selector.loading": "Loading Avatars...", "audio.title": "Test your audio", "audio.subtitle-desktop": "Confirm HMD speaker output", @@ -26,7 +32,6 @@ "audio.grant-subtitle": "Mic access needed to be heard by others", "audio.granted-title": "Mic permissions granted", "audio.granted-subtitle": "You can still mute yourself in-game", - "audio.grant-next": " ", "audio.granted-next": "NEXT", "exit.subtitle": "Your session has ended.", "autoexit.title": "Auto-ending session in ", diff --git a/src/avatar-selector.html b/src/avatar-selector.html index 8496ef94d7f95ec90d29ae5c7e3c030c15659238..531a7e15f8ba2bbd479273d0af8332f3b3c836e1 100644 --- a/src/avatar-selector.html +++ b/src/avatar-selector.html @@ -3,6 +3,7 @@ <head> <meta charset="utf-8"> + <title>avatar selector</title> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <% if(NODE_ENV === "production") { %> <script src="https://cdn.rawgit.com/brianpeiris/aframe/845825ae694449524c185c44a314d361eead4680/dist/aframe-master.min.js"></script> diff --git a/src/behaviours/oculus-touch-joystick-dpad4.js b/src/behaviours/joystick-dpad4.js similarity index 86% rename from src/behaviours/oculus-touch-joystick-dpad4.js rename to src/behaviours/joystick-dpad4.js index bf397ba2fbda2357c7fdf1cfc0310c8aa1ba548c..d0cc1e2a87d1882346deb724d5ba14570d46b02c 100644 --- a/src/behaviours/oculus-touch-joystick-dpad4.js +++ b/src/behaviours/joystick-dpad4.js @@ -1,7 +1,7 @@ import { angleTo4Direction } from "../utils/dpad"; // @TODO specify 4 or 8 direction -function oculus_touch_joystick_dpad4(el, outputPrefix) { +function joystick_dpad4(el, outputPrefix) { this.angleToDirection = angleTo4Direction; this.outputPrefix = outputPrefix; this.centerRadius = 0.6; @@ -11,7 +11,7 @@ function oculus_touch_joystick_dpad4(el, outputPrefix) { el.addEventListener("axismove", this.emitDPad4); } -oculus_touch_joystick_dpad4.prototype = { +joystick_dpad4.prototype = { emitDPad4: function(event) { const x = event.detail.axis[0]; const y = event.detail.axis[1]; @@ -25,4 +25,4 @@ oculus_touch_joystick_dpad4.prototype = { } }; -export { oculus_touch_joystick_dpad4 }; +export default joystick_dpad4; diff --git a/src/behaviours/vive-trackpad-dpad4.js b/src/behaviours/trackpad-dpad4.js similarity index 93% rename from src/behaviours/vive-trackpad-dpad4.js rename to src/behaviours/trackpad-dpad4.js index 99bdf8873d4cbcce3541691e6073af71c153db2b..b23ca1dbdb1f61ec6d99e9d2fab17ab4c96b0248 100644 --- a/src/behaviours/vive-trackpad-dpad4.js +++ b/src/behaviours/trackpad-dpad4.js @@ -1,6 +1,6 @@ import { angleTo4Direction } from "../utils/dpad"; -function vive_trackpad_dpad4(el, outputPrefix) { +function trackpad_dpad4(el, outputPrefix) { this.outputPrefix = outputPrefix; this.lastDirection = ""; this.previous = ""; @@ -15,7 +15,7 @@ function vive_trackpad_dpad4(el, outputPrefix) { el.addEventListener("trackpadup", this.unpress); } -vive_trackpad_dpad4.prototype = { +trackpad_dpad4.prototype = { press: function() { this.pressed = true; }, @@ -51,4 +51,4 @@ vive_trackpad_dpad4.prototype = { } }; -export { vive_trackpad_dpad4 }; +export default trackpad_dpad4; diff --git a/src/components/animated-robot-hands.js b/src/components/animated-robot-hands.js deleted file mode 100644 index 1b26402848a8cb641560d76307708c4d77d53352..0000000000000000000000000000000000000000 --- a/src/components/animated-robot-hands.js +++ /dev/null @@ -1,94 +0,0 @@ -// Global THREE, AFRAME -const POSES = { - open: "allOpen", - thumbDown: "thumbDown", - indexDown: "indexDown", - mrpDown: "mrpDown", - thumbUp: "thumbsUp", - point: "point", - fist: "allGrip", - pinch: "pinch" -}; - -// TODO: When we have analog values of index-finger triggers or middle-finger grips, -// it would be nice to animate the hands proportionally to those analog values. -AFRAME.registerComponent("animated-robot-hands", { - dependencies: ["animation-mixer"], - schema: { - leftHand: { type: "selector", default: "#player-left-controller" }, - rightHand: { type: "selector", default: "#player-right-controller" } - }, - - init: function() { - this.playAnimation = this.playAnimation.bind(this); - - this.mixer = this.el.components["animation-mixer"].mixer; - - const object3DMap = this.el.object3DMap; - const rootObj = object3DMap.mesh || object3DMap.scene; - this.clipActionObject = rootObj.parent; - - // Set hands to open pose because the bind pose is funky dues - // to the workaround for FBX2glTF animations. - this.openL = this.mixer.clipAction(POSES.open + "_L", this.clipActionObject); - this.openR = this.mixer.clipAction(POSES.open + "_R", this.clipActionObject); - this.openL.play(); - this.openR.play(); - }, - - play: function() { - this.data.leftHand.addEventListener("hand-pose", this.playAnimation); - this.data.rightHand.addEventListener("hand-pose", this.playAnimation); - }, - - pause: function() { - this.data.leftHand.removeEventListener("hand-pose", this.playAnimation); - this.data.rightHand.removeEventListener("hand-pose", this.playAnimation); - }, - - // Animate from pose to pose. - // TODO: Transition from current pose (which may be BETWEEN two other poses) - // to the target pose, rather than stopping previous actions altogether. - playAnimation: function(evt) { - const isLeft = evt.target === this.data.leftHand; - // Stop the initial animations we started when the model loaded. - if (!this.openLStopped && isLeft) { - this.openL.stop(); - this.openLStopped = true; - } else if (!this.openRStopped && !isLeft) { - this.openR.stop(); - this.openRStopped = true; - } - - const { current, previous } = evt.detail; - const mixer = this.mixer; - const suffix = isLeft ? "_L" : "_R"; - const prevPose = POSES[previous] + suffix; - const currPose = POSES[current] + suffix; - - // STOP previous actions playing for this hand. - if (this["pose" + suffix + "_to"] !== undefined) { - this["pose" + suffix + "_to"].stop(); - } - if (this["pose" + suffix + "_from"] !== undefined) { - this["pose" + suffix + "_from"].stop(); - } - - const duration = 0.065; - // console.log( - // `Animating ${isLeft ? "left" : "right"} hand from ${prevPose} to ${currPose} over ${duration} seconds.` - // ); - const from = mixer.clipAction(prevPose, this.clipActionObject); - const to = mixer.clipAction(currPose, this.clipActionObject); - from.fadeOut(duration); - to.fadeIn(duration); - to.play(); - from.play(); - // Update the mixer slightly to prevent one frame of the default pose - // from appearing. TODO: Find out why that happens - this.mixer.update(0.001); - - this["pose" + suffix + "_to"] = to; - this["pose" + suffix + "_from"] = from; - } -}); diff --git a/src/components/audio-feedback.js b/src/components/audio-feedback.js index d4bdf5792e79cc1eb3191a0d042f59e89308956d..a72ec196ece7d4f66f5cc0e85b7f9a1edaeff916 100644 --- a/src/components/audio-feedback.js +++ b/src/components/audio-feedback.js @@ -1,20 +1,13 @@ AFRAME.registerComponent("networked-audio-analyser", { schema: {}, async init() { - const networkedEl = await NAF.utils.getNetworkedEntity(this.el); - const ownerId = networkedEl.components.networked.data.owner; - - const stream = await NAF.connection.adapter.getMediaStream(ownerId); - - if (!stream) { - return; - } - - const ctx = THREE.AudioContext.getContext(); - const source = ctx.createMediaStreamSource(stream); - this.analyser = ctx.createAnalyser(); - this.levels = new Uint8Array(this.analyser.frequencyBinCount); - source.connect(this.analyser); + this.el.addEventListener("sound-source-set", event => { + const ctx = THREE.AudioContext.getContext(); + this.analyser = ctx.createAnalyser(); + this.analyser.fftSize = 32; + this.levels = new Uint8Array(this.analyser.frequencyBinCount); + event.detail.soundSource.connect(this.analyser); + }); }, tick: function() { diff --git a/src/components/hand-poses.js b/src/components/hand-poses.js new file mode 100644 index 0000000000000000000000000000000000000000..16d1f1479af6f4f4e17963084e6e135ecb82b942 --- /dev/null +++ b/src/components/hand-poses.js @@ -0,0 +1,75 @@ +const POSES = { + open: "allOpen", + thumbDown: "thumbDown", + indexDown: "indexDown", + mrpDown: "mrpDown", + thumbUp: "thumbsUp", + point: "point", + fist: "allGrip", + pinch: "pinch" +}; + +const NETWORK_POSES = ["allOpen", "thumbDown", "indexDown", "mrpDown", "thumbsUp", "point", "allGrip", "pinch"]; + +AFRAME.registerComponent("hand-pose", { + multiple: true, + schema: { + pose: { default: 0 } + }, + + init() { + this.animatePose = this.animatePose.bind(this); + this.mixer = this.el.components["animation-mixer"]; + const object3DMap = this.mixer.el.object3DMap; + const rootObj = object3DMap.mesh || object3DMap.scene; + this.clipActionObject = rootObj.parent; + const suffix = this.id == "left" ? "_L" : "_R"; + this.from = this.to = this.mixer.mixer.clipAction(POSES.open + suffix, this.clipActionObject); + this.from.play(); + }, + + update(oldData) { + if (oldData.pose != this.data.pose) { + this.animatePose(NETWORK_POSES[oldData.pose || 0], NETWORK_POSES[this.data.pose]); + } + }, + + animatePose(prev, curr) { + this.from.stop(); + this.to.stop(); + + const duration = 0.065; + const suffix = this.id == "left" ? "_L" : "_R"; + this.from = this.mixer.mixer.clipAction(prev + suffix, this.clipActionObject); + this.to = this.mixer.mixer.clipAction(curr + suffix, this.clipActionObject); + + this.from.fadeOut(duration); + this.to.fadeIn(duration); + this.to.play(); + this.from.play(); + + this.mixer.mixer.update(0.001); + } +}); + +AFRAME.registerComponent("hand-pose-controller", { + multiple: true, + schema: { + eventSrc: { type: "selector" } + }, + init: function() { + this.setHandPose = this.setHandPose.bind(this); + }, + + play: function() { + this.data.eventSrc.addEventListener("hand-pose", this.setHandPose); + }, + + pause: function() { + this.data.eventSrc.removeEventListener("hand-pose", this.setHandPose); + }, + + setHandPose: function(evt) { + this.el.setAttribute(`hand-pose__${this.id}`, "pose", NETWORK_POSES.indexOf(POSES[evt.detail.current])); + } +}); diff --git a/src/components/hud-controller.js b/src/components/hud-controller.js new file mode 100644 index 0000000000000000000000000000000000000000..4ee274f34ae9daa6f24c8e414eee824b845c49db --- /dev/null +++ b/src/components/hud-controller.js @@ -0,0 +1,77 @@ +import { AppModes } from "../systems/app-mode.js"; + +const TWOPI = Math.PI * 2; +function deltaAngle(a, b) { + const p = Math.abs(b - a) % TWOPI; + return p > Math.PI ? TWOPI - p : p; +} + +/** + * Positions the HUD and toggles app mode based on where the user is looking + */ +AFRAME.registerComponent("hud-controller", { + schema: { + head: { type: "selector" }, + offset: { default: 0.7 }, // distance from hud above head, + lookCutoff: { default: 20 }, // angle at which the hud should be "on", + animRange: { default: 30 }, // degrees over which to animate the hud into view + yawCutoff: { default: 50 } // yaw degrees at wich the hud should reoirent even if the user is looking up + }, + init() { + this.isYLocked = false; + this.lockedHeadPositionY = 0; + }, + + pause() { + // TODO: this assumes full control over current app mode reguardless of what else might be manipulating it, this is obviously wrong + const AppModeSystem = this.el.sceneEl.systems["app-mode"]; + AppModeSystem.setMode(AppModes.DEFAULT); + }, + + tick() { + const hud = this.el.object3D; + const head = this.data.head.object3D; + const sceneEl = this.el.sceneEl; + + const { offset, lookCutoff, animRange, yawCutoff } = this.data; + + const pitch = head.rotation.x * THREE.Math.RAD2DEG; + const yawDif = deltaAngle(head.rotation.y, hud.rotation.y) * THREE.Math.RAD2DEG; + + // Reorient the hud only if the user is looking away from the hud, for right now this arbitrarily means the hud is 1/3 way animated away + // TODO: come up with better huristics for this that maybe account for the user turning away from the hud "too far", also animate the position so that it doesnt just snap. + if (yawDif >= yawCutoff || pitch < lookCutoff - animRange / 3) { + const lookDir = new THREE.Vector3(0, 0, -1); + lookDir.applyQuaternion(head.quaternion); + lookDir.add(head.position); + hud.position.x = lookDir.x; + hud.position.z = lookDir.z; + hud.setRotationFromEuler(new THREE.Euler(0, head.rotation.y, 0)); + } + + // animate the hud into place over animRange degrees as the user aproaches the lookCutoff angle + const t = 1 - THREE.Math.clamp(lookCutoff - pitch, 0, animRange) / animRange; + + // Lock the hud in place relative to a known head position so it doesn't bob up and down + // with the user's head + if (!this.isYLocked && t === 1) { + this.lockedHeadPositionY = head.position.y; + } + const EPSILON = 0.001; + this.isYLocked = t > 1 - EPSILON; + + hud.position.y = (this.isYLocked ? this.lockedHeadPositionY : head.position.y) + offset + (1 - t) * offset; + hud.rotation.x = (1 - t) * THREE.Math.DEG2RAD * 90; + + // update the app mode when the HUD locks on or off + // TODO: this assumes full control over current app mode reguardless of what else might be manipulating it, this is obviously wrong + const AppModeSystem = sceneEl.systems["app-mode"]; + if (pitch > lookCutoff && AppModeSystem.mode !== AppModes.HUD) { + AppModeSystem.setMode(AppModes.HUD); + sceneEl.renderer.sortObjects = true; + } else if (pitch < lookCutoff && AppModeSystem.mode === AppModes.HUD) { + AppModeSystem.setMode(AppModes.DEFAULT); + sceneEl.renderer.sortObjects = false; + } + } +}); diff --git a/src/components/nav-mesh-helper.js b/src/components/nav-mesh-helper.js new file mode 100644 index 0000000000000000000000000000000000000000..5c1be4ce96946dddb6ba98590dc7da27d403ab10 --- /dev/null +++ b/src/components/nav-mesh-helper.js @@ -0,0 +1,16 @@ +AFRAME.registerComponent("nav-mesh-helper", { + schema: { + teleportControls: { type: "selectorAll", default: "[teleport-controls]" } + }, + + init: function() { + const teleportControls = this.data.teleportControls; + this.el.addEventListener("bundleloaded", () => { + if (!teleportControls) return; + + for (let i = 0; i < teleportControls.length; i++) { + teleportControls[i].components["teleport-controls"].queryCollisionEntities(); + } + }); + } +}); diff --git a/src/components/super-cursor.js b/src/components/super-cursor.js index b8daee493abcbfb543db553538c5b968748da381..ee70e960663144709f17108d70a2378a6bc9f935 100644 --- a/src/components/super-cursor.js +++ b/src/components/super-cursor.js @@ -92,7 +92,7 @@ AFRAME.registerComponent("super-cursor", { this.data.cursor.object3D.position.copy(this.point); } - this.isInteractable = intersection && intersection.object.el.className === "interactable"; + this.isInteractable = intersection && this._isInteractable(intersection.object.el); if ((this.isGrabbing || this.isInteractable) && !this.wasIntersecting) { this.wasIntersecting = true; @@ -103,6 +103,13 @@ AFRAME.registerComponent("super-cursor", { } }, + _isInteractable: function(el) { + return ( + el.className === "interactable" || + (el.parentNode && el.parentNode != el.sceneEl && this._isInteractable(el.parentNode)) + ); + }, + _handleMouseDown: function() { if (this.isInteractable) { const lookControls = this.data.camera.components["look-controls"]; @@ -122,7 +129,19 @@ AFRAME.registerComponent("super-cursor", { }, _handleWheel: function(e) { - if (this.isGrabbing) this.currentDistanceMod += e.deltaY / 10; + if (this.isGrabbing) { + switch (e.deltaMode) { + case e.DOM_DELTA_PIXEL: + this.currentDistanceMod += e.deltaY / 500; + break; + case e.DOM_DELTA_LINE: + this.currentDistanceMod += e.deltaY / 10; + break; + case e.DOM_DELTA_PAGE: + this.currentDistanceMod += e.deltaY / 2; + break; + } + } }, _handleEnterVR: function() { diff --git a/src/components/virtual-gamepad-controls.css b/src/components/virtual-gamepad-controls.css index d3e36e2fa243e0d9e35e693224cbd235420a0702..572e6169f6a29c911d0fa89e37382b10838da3c0 100644 --- a/src/components/virtual-gamepad-controls.css +++ b/src/components/virtual-gamepad-controls.css @@ -1,6 +1,6 @@ :local(.touchZone) { position: absolute; - top: 0; + height: 20vh; bottom: 0; } @@ -13,7 +13,3 @@ left: 50%; right: 0; } - -:local(.touchZone) .nipple { - margin: 5vh 5vw; -} diff --git a/src/components/virtual-gamepad-controls.js b/src/components/virtual-gamepad-controls.js index d70219bf1e6374fefc6d6daaeb9e8e10bfc27fb8..f92b7d4534f8e45e499edf6bcf1345f9e0f33374 100644 --- a/src/components/virtual-gamepad-controls.js +++ b/src/components/virtual-gamepad-controls.js @@ -16,29 +16,42 @@ AFRAME.registerComponent("virtual-gamepad-controls", { const leftStick = nipplejs.create({ zone: leftTouchZone, - mode: "static", color: "white", - position: { left: "50px", bottom: "50px" } + fadeTime: 0 }); const rightStick = nipplejs.create({ zone: rightTouchZone, - mode: "static", color: "white", - position: { right: "50px", bottom: "50px" } + fadeTime: 0 }); - this.onJoystickChanged = this.onJoystickChanged.bind(this); + this.onMoveJoystickChanged = this.onMoveJoystickChanged.bind(this); + this.onMoveJoystickEnd = this.onMoveJoystickEnd.bind(this); + this.onLookJoystickChanged = this.onLookJoystickChanged.bind(this); + this.onLookJoystickEnd = this.onLookJoystickEnd.bind(this); - rightStick.on("move end", this.onJoystickChanged); - leftStick.on("move end", this.onJoystickChanged); + leftStick.on("move", this.onMoveJoystickChanged); + leftStick.on("end", this.onMoveJoystickEnd); + + rightStick.on("move", this.onLookJoystickChanged); + rightStick.on("end", this.onLookJoystickEnd); this.leftTouchZone = leftTouchZone; this.rightTouchZone = rightTouchZone; this.leftStick = leftStick; this.rightStick = rightStick; - this.yaw = 0; + this.inVr = false; + this.moving = false; + this.rotating = false; + + this.moveEvent = { + axis: [0, 0] + }; + this.rotateYEvent = { + value: 0 + }; this.onEnterVr = this.onEnterVr.bind(this); this.onExitVr = this.onExitVr.bind(this); @@ -46,39 +59,59 @@ AFRAME.registerComponent("virtual-gamepad-controls", { this.el.sceneEl.addEventListener("exit-vr", this.onExitVr); }, - onJoystickChanged(event, joystick) { - if (event.target.id === this.leftStick.id) { - if (event.type === "move") { - const angle = joystick.angle.radian; - const force = joystick.force < 1 ? joystick.force : 1; - const x = Math.cos(angle) * force; - const z = Math.sin(angle) * force; - this.el.sceneEl.emit("move", { axis: [x, z] }); - } else { - this.el.sceneEl.emit("move", { axis: [0, 0] }); + onMoveJoystickChanged(event, joystick) { + const angle = joystick.angle.radian; + const force = joystick.force < 1 ? joystick.force : 1; + const x = Math.cos(angle) * force; + const z = Math.sin(angle) * force; + this.moving = true; + this.moveEvent.axis[0] = x; + this.moveEvent.axis[1] = z; + }, + + onMoveJoystickEnd() { + this.moving = false; + this.moveEvent.axis[0] = 0; + this.moveEvent.axis[1] = 0; + this.el.sceneEl.emit("move", this.moveEvent); + }, + + onLookJoystickChanged(event, joystick) { + // Set pitch and yaw angles on right stick move + const angle = joystick.angle.radian; + const force = joystick.force < 1 ? joystick.force : 1; + this.rotating = true; + this.rotateYEvent.value = Math.cos(angle) * force; + }, + + onLookJoystickEnd() { + this.rotating = false; + this.rotateYEvent.value = 0; + this.el.sceneEl.emit("rotateY", this.rotateYEvent); + }, + + tick() { + if (!this.inVr) { + if (this.moving) { + this.el.sceneEl.emit("move", this.moveEvent); } - } else { - if (event.type === "move") { - // Set pitch and yaw angles on right stick move - const angle = joystick.angle.radian; - const force = joystick.force < 1 ? joystick.force : 1; - this.yaw = Math.cos(angle) * force; - this.el.sceneEl.emit("rotateY", { value: this.yaw }); - } else { - this.yaw = 0; - this.el.sceneEl.emit("rotateY", { value: this.yaw }); + + if (this.rotating) { + this.el.sceneEl.emit("rotateY", this.rotateYEvent); } } }, onEnterVr() { // Hide the joystick controls + this.inVr = true; this.leftTouchZone.style.display = "none"; this.rightTouchZone.style.display = "none"; }, onExitVr() { // Show the joystick controls + this.inVr = false; this.leftTouchZone.style.display = "block"; this.rightTouchZone.style.display = "block"; }, diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js index 06e81969a0f72d0d3a90ae7b247f28bf68fa2aaa..2166e7a62a186c9caa1ce3da8f39b5cafa4fff31 100644 --- a/src/gltf-component-mappings.js +++ b/src/gltf-component-mappings.js @@ -2,5 +2,6 @@ import "./components/gltf-model-plus"; AFRAME.GLTFModelPlus.registerComponent("scale-audio-feedback", "scale-audio-feedback"); AFRAME.GLTFModelPlus.registerComponent("loop-animation", "loop-animation"); +AFRAME.GLTFModelPlus.registerComponent("shape", "shape"); AFRAME.GLTFModelPlus.registerComponent("visible", "visible"); AFRAME.GLTFModelPlus.registerComponent("nav-mesh", "nav-mesh"); diff --git a/src/hub.html b/src/hub.html index 293fa12ea82ba46d485e3f0c9b4a38ff8856c754..e7ca7655ff31d81da8529db96cfecdf85f5c66a1 100644 --- a/src/hub.html +++ b/src/hub.html @@ -3,10 +3,11 @@ <head> <meta charset="utf-8"> - <title>moz://a duck</title> - <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M62+)" data-expires="<%= ORIGIN_TRIAL_EXPIRES %>" content="<%= ORIGIN_TRIAL_TOKEN %>"> + <title>moz://a duck</title> + <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> + <% if(NODE_ENV === "production") { %> <script src="https://cdn.rawgit.com/brianpeiris/aframe/845825ae694449524c185c44a314d361eead4680/dist/aframe-master.min.js"></script> <% } else { %> @@ -46,7 +47,7 @@ <a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item> <a-asset-item id="interactable-duck" response-type="arraybuffer" src="./assets/interactables/duck/DuckyMesh.glb"></a-asset-item> - <img id="water-normal-map" src="./assets/waternormals.jpg"></a-asset-item> + <img id="water-normal-map" src="./assets/waternormals.jpg"> <!-- Templates --> @@ -64,19 +65,19 @@ <a-entity class="model" gltf-model-plus="inflate: true"> <template data-selector=".RootScene"> - <a-entity ik-controller animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity> + <a-entity ik-controller hand-pose__left hand-pose__right animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity> </template> <template data-selector=".Neck"> - <a-entity> - <a-entity - class="nametag" - billboard - text="side: double; align: center; color: #ddd" - position="0 1 0" - scale="6 6 6" - ></a-entity> - </a-entity> + <a-entity> + <a-entity + class="nametag" + billboard + text="side: double; align: center; color: #ddd" + position="0 1 0" + scale="6 6 6" + ></a-entity> + </a-entity> </template> <template data-selector=".Chest"> @@ -113,11 +114,11 @@ <template id="interactable-template"> <a-entity - gltf-model-plus="src: #interactable-duck" + gltf-model-plus="src: #interactable-duck; inflate: true;" scale="2 2 2" class="interactable" super-networked-interactable="counter: #counter; mass: 5;" - body="type: dynamic; mass: 5; shape: box;" + body="type: dynamic; shape: none; mass: 5;" grabbable stretchable="useWorldPosition: true;" ></a-entity> @@ -154,7 +155,7 @@ > <a-sphere id="3d-cursor" - radius=0.02 + radius="0.02" static-body="shape: sphere;" mixin="super-hands" segments-height="9" @@ -165,7 +166,7 @@ <!-- Player Rig --> <a-entity id="player-rig" - networked="template: #remote-avatar-template; attachLocalTemplate: false;" + networked="template: #remote-avatar-template; attachTemplateToLocal: false;" spawn-controller="radius: 4;" wasd-to-analog2d character-controller="pivot: #player-camera" @@ -211,8 +212,6 @@ haptic-feedback ></a-entity> - - <a-entity id="player-right-controller" class="right-controller" @@ -237,8 +236,11 @@ <template data-selector=".RootScene"> <a-entity ik-controller - animated-robot-hands animation-mixer + hand-pose__left + hand-pose__right + hand-pose-controller__left="eventSrc:#player-left-controller" + hand-pose-controller__right="eventSrc:#player-right-controller" ></a-entity> </template> @@ -285,9 +287,14 @@ ></a-entity> <!-- Environment --> - <a-entity id="environment-root" position="0 0 0"></a-entity> + <a-entity + id="environment-root" + nav-mesh-helper + static-body="shape: none;" + class="collidable" + ></a-entity> - <a-entity id="skybox" + <a-entity id="skybox" scale="8000 8000 8000" skybox="azimuth:0.280; inclination:0.440" @@ -304,23 +311,6 @@ xr="ar: false" ></a-entity> - <a-cylinder - position="0 0.45 0" - material="visible: false" - height="1" radius="3.1" - segments-radial="12" - static-body - class="collidable" - ></a-cylinder> - - <a-plane - material="visible: false" - rotation="-90 0 0" - height="35" - width="35" - static-body - class="collidable" - ></a-plane> </a-scene> <div id="ui-root"></div> diff --git a/src/hub.js b/src/hub.js index 1db136698165309ced35534b3b735f96e61169e0..0489cb38e9afe45a50547030d2eba8eeacb1856e 100644 --- a/src/hub.js +++ b/src/hub.js @@ -1,5 +1,8 @@ import "./assets/stylesheets/hub.scss"; +import moment from "moment-timezone"; +import uuid from "uuid/v4"; import queryString from "query-string"; +import { Socket } from "phoenix"; import { patchWebGLRenderingContext } from "./utils/webgl"; patchWebGLRenderingContext(); @@ -15,8 +18,8 @@ import "aframe-rounded"; import "webrtc-adapter"; import "aframe-slice9-component"; -import { vive_trackpad_dpad4 } from "./behaviours/vive-trackpad-dpad4"; -import { oculus_touch_joystick_dpad4 } from "./behaviours/oculus-touch-joystick-dpad4"; +import trackpad_dpad4 from "./behaviours/trackpad-dpad4"; +import joystick_dpad4 from "./behaviours/joystick-dpad4"; import { PressedMove } from "./activators/pressedmove"; import { ReverseY } from "./activators/reversey"; import "./activators/shortpress"; @@ -38,18 +41,20 @@ import "./components/water"; import "./components/skybox"; import "./components/layers"; import "./components/spawn-controller"; -import "./components/animated-robot-hands"; import "./components/hide-when-quality"; import "./components/player-info"; import "./components/debug"; import "./components/animation-mixer"; import "./components/loop-animation"; +import "./components/hand-poses"; import "./components/gltf-model-plus"; import "./components/gltf-bundle"; +import "./components/hud-controller"; import ReactDOM from "react-dom"; import React from "react"; import UIRoot from "./react-components/ui-root"; +import HubChannel from "./utils/hub-channel"; import "./systems/personal-space-bubble"; import "./systems/app-mode"; @@ -79,12 +84,14 @@ import "./components/super-spawner"; import "./components/super-cursor"; import "./components/event-repeater"; +import "./components/nav-mesh-helper"; + import registerNetworkSchemas from "./network-schemas"; import { inGameActions, config as inputConfig } from "./input-mappings"; import registerTelemetry from "./telemetry"; import Store from "./storage/store"; -import { generateDefaultProfile } from "./utils/identity.js"; +import { generateDefaultProfile, generateRandomName } from "./utils/identity.js"; import { getAvailableVREntryTypes } from "./utils/vr-caps-detect.js"; import ConcurrentLoadDetector from "./utils/concurrent-load-detector.js"; @@ -96,21 +103,28 @@ function qsTruthy(param) { registerTelemetry(); -AFRAME.registerInputBehaviour("vive_trackpad_dpad4", vive_trackpad_dpad4); -AFRAME.registerInputBehaviour("oculus_touch_joystick_dpad4", oculus_touch_joystick_dpad4); +AFRAME.registerInputBehaviour("trackpad_dpad4", trackpad_dpad4); +AFRAME.registerInputBehaviour("joystick_dpad4", joystick_dpad4); AFRAME.registerInputActivator("pressedmove", PressedMove); AFRAME.registerInputActivator("reverseY", ReverseY); AFRAME.registerInputMappings(inputConfig, true); const store = new Store(); const concurrentLoadDetector = new ConcurrentLoadDetector(); +const hubChannel = new HubChannel(store); concurrentLoadDetector.start(); // Always layer in any new default profile bits store.update({ profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); +// Regenerate name to encourage users to change it. +if (!store.state.profile.has_changed_name) { + store.update({ profile: { display_name: generateRandomName() } }); +} + async function exitScene() { + hubChannel.disconnect(); const scene = document.querySelector("a-scene"); scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this document.body.removeChild(scene); @@ -148,7 +162,7 @@ async function enterScene(mediaStream, enterInVR, janusRoomId) { scene.setAttribute("stats", true); } - if (isMobile || qsTruthy(qs.mobile)) { + if (isMobile || qsTruthy("mobile")) { playerRig.setAttribute("virtual-gamepad-controls", {}); } @@ -184,6 +198,12 @@ async function enterScene(mediaStream, enterInVR, janusRoomId) { }); if (!qsTruthy("offline")) { + document.body.addEventListener("connected", () => { + hubChannel.sendEntryEvent().then(() => { + store.update({ lastEnteredAt: moment().toJSON() }); + }); + }); + scene.components["networked-scene"].connect(); if (mediaStream) { @@ -207,15 +227,14 @@ async function enterScene(mediaStream, enterInVR, janusRoomId) { } } -function mountUI(scene) { +function mountUI(scene, props = {}) { const disableAutoExitOnConcurrentLoad = qsTruthy("allow_multi"); const forcedVREntryType = qs.vr_entry_type || null; const enableScreenSharing = qsTruthy("enable_screen_sharing"); const htmlPrefix = document.body.dataset.htmlPrefix || ""; + const showProfileEntry = !store.state.profile.has_changed_name; - // TODO: Refactor to avoid using return value - /* eslint-disable react/no-render-return-value */ - const uiRoot = ReactDOM.render( + ReactDOM.render( <UIRoot {...{ scene, @@ -226,14 +245,13 @@ function mountUI(scene) { forcedVREntryType, enableScreenSharing, store, - htmlPrefix + htmlPrefix, + showProfileEntry, + ...props }} />, document.getElementById("ui-root") ); - /* eslint-enable react/no-render-return-value */ - - return uiRoot; } const onReady = async () => { @@ -243,26 +261,31 @@ const onReady = async () => { registerNetworkSchemas(); - const uiRoot = mountUI(scene); + mountUI(scene); + + let modifiedProps = {}; + const remountUI = props => { + modifiedProps = { ...modifiedProps, ...props }; + mountUI(scene, modifiedProps); + }; getAvailableVREntryTypes().then(availableVREntryTypes => { - uiRoot.setState({ availableVREntryTypes }); - uiRoot.handleForcedVREntryType(); + remountUI({ availableVREntryTypes }); }); const environmentRoot = document.querySelector("#environment-root"); const initialEnvironmentEl = document.createElement("a-entity"); initialEnvironmentEl.addEventListener("bundleloaded", () => { - uiRoot.setState({ initialEnvironmentLoaded: true }); - // Wait a tick so that the environments actually render. - setTimeout(() => scene.renderer.animate(null)); + remountUI({ initialEnvironmentLoaded: true }); + // Wait a tick plus some margin so that the environments actually render. + setTimeout(() => scene.renderer.animate(null), 100); }); environmentRoot.appendChild(initialEnvironmentEl); if (qs.room) { // If ?room is set, this is `yarn start`, so just use a default environment and query string room. - uiRoot.setState({ janusRoomId: qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1 }); + remountUI({ janusRoomId: qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1 }); initialEnvironmentEl.setAttribute("gltf-bundle", { src: "https://asset-bundles-prod.reticulum.io/rooms/meetingroom/MeetingRoom.bundle.json" // src: "https://asset-bundles-prod.reticulum.io/rooms/theater/TheaterMeshes.bundle.json" @@ -272,15 +295,32 @@ const onReady = async () => { return; } - const hubId = document.location.pathname.substring(1).split("/")[0]; + // Connect to reticulum over phoenix channels to get hub info. + const hubId = qs.hub_id || document.location.pathname.substring(1).split("/")[0]; console.log(`Hub ID: ${hubId}`); - const res = await fetch(`/api/v1/hubs/${hubId}`); - const data = await res.json(); - const hub = data.hubs[0]; - const defaultSpaceTopic = hub.topics[0]; - const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; - uiRoot.setState({ janusRoomId: defaultSpaceTopic.janus_room_id }); - initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); + + const socketProtocol = document.location.protocol === "https:" ? "wss:" : "ws:"; + const socketPort = qs.phx_port || document.location.port; + const socketHost = qs.phx_host || document.location.hostname; + const socketUrl = `${socketProtocol}//${socketHost}${socketPort ? `:${socketPort}` : ""}/socket`; + console.log(`Phoenix Channel URL: ${socketUrl}`); + + const socket = new Socket(socketUrl, { params: { session_id: uuid() } }); + socket.connect(); + + const channel = socket.channel(`hub:${hubId}`, {}); + + channel + .join() + .receive("ok", data => { + const hub = data.hubs[0]; + const defaultSpaceTopic = hub.topics[0]; + const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; + remountUI({ janusRoomId: defaultSpaceTopic.janus_room_id }); + initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); + hubChannel.setPhoenixChannel(channel); + }) + .receive("error", res => console.error(res)); }; document.addEventListener("DOMContentLoaded", onReady); diff --git a/src/input-mappings.js b/src/input-mappings.js index 035110dd677efa748c280ae0d6b8f14fde1ae6f4..9296fd71ffa9f31c32308cff00cd7d4bd3096dcf 100644 --- a/src/input-mappings.js +++ b/src/input-mappings.js @@ -21,29 +21,37 @@ const config = { behaviours: { default: { "oculus-touch-controls": { - joystick: "oculus_touch_joystick_dpad4" + joystick: "joystick_dpad4" }, "vive-controls": { - trackpad: "vive_trackpad_dpad4" + trackpad: "trackpad_dpad4" + }, + "daydream-controls": { + trackpad: "trackpad_dpad4" + }, + "gearvr-controls": { + trackpad: "trackpad_dpad4" } } }, mappings: { default: { "vive-controls": { - menudown: ["action_mute", "thumb_down"], - menuup: "thumb_up", "trackpad.pressedmove": { left: "move" }, trackpad_dpad4_pressed_west_down: { right: "snap_rotate_left" }, trackpad_dpad4_pressed_east_down: { right: "snap_rotate_right" }, trackpad_dpad4_pressed_center_down: { right: "action_teleport_down" }, + trackpad_dpad4_pressed_north_down: { right: "action_teleport_down" }, + trackpad_dpad4_pressed_south_down: { right: "action_teleport_down" }, trackpadup: { right: "action_teleport_up" }, + menudown: "thumb_down", + menuup: "thumb_up", gripdown: ["action_grab", "middle_ring_pinky_down", "index_down"], gripup: ["action_release", "middle_ring_pinky_up", "index_up"], trackpadtouchstart: "thumb_down", trackpadtouchend: "thumb_up", - triggerdown: "index_down", - triggerup: "index_up" + triggerdown: ["action_grab", "index_down"], + triggerup: ["action_release", "index_up"] }, "oculus-touch-controls": { joystick_dpad4_west: { @@ -52,7 +60,6 @@ const config = { joystick_dpad4_east: { right: "snap_rotate_right" }, - xbuttondown: "action_mute", gripdown: ["action_grab", "middle_ring_pinky_down"], gripup: ["action_release", "middle_ring_pinky_up"], abuttontouchstart: "thumb_down", @@ -67,20 +74,26 @@ const config = { surfacetouchend: "thumb_up", thumbsticktouchstart: "thumb_down", thumbsticktouchend: "thumb_up", - triggerdown: ["action_teleport_down", "index_down"], - triggerup: ["action_teleport_up", "index_up"], + triggerdown: "index_down", + triggerup: "index_up", "axismove.reverseY": { left: "move" }, - right_dpad_east: "snap_rotate_right", - right_dpad_west: "snap_rotate_left", abuttondown: "action_teleport_down", abuttonup: "action_teleport_up" }, "daydream-controls": { - trackpaddown: "action_teleport_down", + trackpad_dpad4_pressed_west_down: "snap_rotate_left", + trackpad_dpad4_pressed_east_down: "snap_rotate_right", + trackpad_dpad4_pressed_center_down: "action_teleport_down", + trackpad_dpad4_pressed_north_down: "action_teleport_down", + trackpad_dpad4_pressed_south_down: "action_teleport_down", trackpadup: "action_teleport_up" }, "gearvr-controls": { - trackpaddown: "action_teleport_down", + trackpad_dpad4_pressed_west_down: "snap_rotate_left", + trackpad_dpad4_pressed_east_down: "snap_rotate_right", + trackpad_dpad4_pressed_center_down: "action_teleport_down", + trackpad_dpad4_pressed_north_down: "action_teleport_down", + trackpad_dpad4_pressed_south_down: "action_teleport_down", trackpadup: "action_teleport_up" }, keyboard: { @@ -100,14 +113,14 @@ const config = { s_up: "s_up", d_down: "d_down", d_up: "d_up", - W_down: "w_down", - W_up: "w_up", - A_down: "a_down", - A_up: "a_up", - S_down: "s_down", - S_up: "s_up", - D_down: "d_down", - D_up: "d_up" + arrowup_down: "w_down", + arrowup_up: "w_up", + arrowleft_down: "a_down", + arrowleft_up: "a_up", + arrowdown_down: "s_down", + arrowdown_up: "s_up", + arrowright_down: "d_down", + arrowright_up: "d_up" } }, hud: { @@ -116,8 +129,8 @@ const config = { triggerup: { right: "action_ui_select_up" } }, "oculus-touch-controls": { - triggerdown: { right: "action_ui_select_down" }, - triggerup: { right: "action_ui_select_up" }, + abuttondown: "action_ui_select_down", + abuttonup: "action_ui_select_up", gripdown: "middle_ring_pinky_down", gripup: "middle_ring_pinky_up", abuttontouchstart: "thumb_down", diff --git a/src/network-schemas.js b/src/network-schemas.js index 54cc0b63ba40efed17baf43c0d3554b1ea2fa4bc..822951bedb782de1ba401ae20c00232f47b61412 100644 --- a/src/network-schemas.js +++ b/src/network-schemas.js @@ -3,9 +3,22 @@ function registerNetworkSchemas() { template: "#remote-avatar-template", components: [ "position", - "rotation", + { + component: "rotation", + lerp: false + }, "scale", "player-info", + { + selector: ".RootScene", + component: "hand-pose__left", + property: "pose" + }, + { + selector: ".RootScene", + component: "hand-pose__right", + property: "pose" + }, { selector: ".camera", component: "position" diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index d88340f04532b89d07512d38bb3f292c78e4e844..528d5b81558e37f12aea6a0182c4cc08d8971782 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -20,8 +20,8 @@ class AvatarSelector extends Component { const numAvatars = this.props.avatars.length; return ((currAvatarIndex + direction) % numAvatars + numAvatars) % numAvatars; }; - nextAvatarIndex = () => this.getAvatarIndex(1); - previousAvatarIndex = () => this.getAvatarIndex(-1); + nextAvatarIndex = () => this.getAvatarIndex(-1); + previousAvatarIndex = () => this.getAvatarIndex(1); emitChangeToNext = () => { const nextAvatarId = this.props.avatars[this.nextAvatarIndex()].id; @@ -38,7 +38,17 @@ class AvatarSelector extends Component { // HACK - a-animation ought to restart the animation when the `to` attribute changes, but it doesn't // so we need to force it here. const currRot = this.animation.parentNode.getAttribute("rotation"); - this.animation.setAttribute("from", `${currRot.x} ${currRot.y} ${currRot.z}`); + const currY = currRot.y; + const toRot = String.split(this.animation.attributes.to.value, " "); + const toY = toRot[1]; + const step = 360.0 / this.props.avatars.length; + const brokenlyBigRotation = Math.abs(toY - currY) > 3 * step; + let fromY = currY; + if (brokenlyBigRotation) { + // Rotation in Y wrapped around 360. Adjust the "from" to prevent a dramatic rotation + fromY = currY < toY ? currY + 360 : currY - 360; + } + this.animation.setAttribute("from", `${currRot.x} ${fromY} ${currRot.z}`); this.animation.stop(); this.animation.handleMixinUpdate(); this.animation.start(); @@ -83,7 +93,7 @@ class AvatarSelector extends Component { attribute="rotation" dur="1000" easing="ease-out" - to={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length + 180} 0`} + to={`0 ${(360 * this.getAvatarIndex() / this.props.avatars.length + 180) % 360} 0`} /> {avatarEntities} </a-entity> diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js index 8ed8b171d6e7ec08d82d59af2c5314d9353a7910..92d0ef5ef8ccd4992d9913bfb30144fff018c0b9 100644 --- a/src/react-components/entry-buttons.js +++ b/src/react-components/entry-buttons.js @@ -12,7 +12,7 @@ import DaydreamEntyImg from "../assets/images/daydream_entry.svg"; const mobiledetect = new MobileDetect(navigator.userAgent); const EntryButton = props => ( - <div className="entry-button" onClick={props.onClick}> + <button className="entry-button" onClick={props.onClick}> <img src={props.iconSrc} className="entry-button__icon" /> <div className="entry-button__label"> <div className="entry-button__label__contents"> @@ -25,7 +25,7 @@ const EntryButton = props => ( {props.subtitle && <div className="entry-button__subtitle">{props.subtitle}</div>} </div> </div> - </div> + </button> ); EntryButton.propTypes = { diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index 523016e25bdf1e0f127bf5a889165b1ede806322..2732f3ca9bd173cc3390bd1af05316b8f6614747 100644 --- a/src/react-components/profile-entry-panel.js +++ b/src/react-components/profile-entry-panel.js @@ -14,10 +14,8 @@ class ProfileEntryPanel extends Component { constructor(props) { super(props); - this.state = { - display_name: this.props.store.state.profile.display_name, - avatar_id: this.props.store.state.profile.avatar_id - }; + const { display_name, avatar_id } = this.props.store.state.profile; + this.state = { display_name, avatar_id }; this.props.store.addEventListener("statechanged", this.storeUpdated); } @@ -28,10 +26,15 @@ class ProfileEntryPanel extends Component { saveStateAndFinish = e => { e.preventDefault(); + const has_agreed_to_terms = this.props.store.state.profile.has_agreed_to_terms || this.state.has_agreed_to_terms; + if (!has_agreed_to_terms) return; + const { has_changed_name, display_name } = this.props.store.state.profile; + const hasChangedName = has_changed_name || this.state.display_name !== display_name; this.props.store.update({ profile: { - display_name: this.state.display_name, - avatar_id: this.state.avatar_id + has_agreed_to_terms: true, + has_changed_name: hasChangedName, + ...this.state } }); this.props.finished(); @@ -74,20 +77,47 @@ class ProfileEntryPanel extends Component { <div className="profile-entry__subtitle"> <FormattedMessage id="profile.header" /> </div> - <input - className="profile-entry__form-field-text" - value={this.state.display_name} - onChange={e => this.setState({ display_name: e.target.value })} - required - pattern={SCHEMA.definitions.profile.properties.display_name.pattern} - title={formatMessage({ id: "profile.display_name.validation_warning" })} - ref={inp => (this.nameInput = inp)} - /> + <label> + <span className="profile-entry__display-name-label"> + <FormattedMessage id="profile.display_name.label" /> + </span> + <input + className="profile-entry__form-field-text" + value={this.state.display_name} + onChange={e => this.setState({ display_name: e.target.value })} + required + pattern={SCHEMA.definitions.profile.properties.display_name.pattern} + title={formatMessage({ id: "profile.display_name.validation_warning" })} + ref={inp => (this.nameInput = inp)} + /> + </label> <iframe className="profile-entry__avatar-selector" src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatar_id}`} ref={ifr => (this.avatarSelector = ifr)} /> + {!this.props.store.state.profile.has_agreed_to_terms && ( + <label className="profile-entry__terms"> + <input + className="profile-entry__terms__checkbox" + type="checkbox" + required + value={this.state.has_agreed_to_terms} + onChange={e => this.setState({ has_agreed_to_terms: e.target.checked })} + /> + <span className="profile-entry__terms__text"> + <FormattedMessage id="profile.terms.prefix" />{" "} + <a className="profile-entry__terms__link" target="_blank" href="/privacy"> + <FormattedMessage id="profile.terms.privacy" /> + </a>{" "} + <FormattedMessage id="profile.terms.conjunction" />{" "} + <a className="profile-entry__terms__link" target="_blank" href="/terms"> + <FormattedMessage id="profile.terms.tou" /> + </a> + <FormattedMessage id="profile.terms.suffix" /> + </span> + </label> + )} <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" })} /> </div> </form> diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js index 43ec49291007089d54e1d1a52c7a586e766e5fb4..ca7a3b891c3c99cc3aca753244ba4289abe525b4 100644 --- a/src/react-components/profile-info-header.js +++ b/src/react-components/profile-info-header.js @@ -4,7 +4,7 @@ import PropTypes from "prop-types"; export const ProfileInfoHeader = props => ( <div className="profile-info-header"> <img src="../assets/images/account.svg" onClick={props.onClick} className="profile-info-header__icon" /> - <div className="profile-info-header__profile_display_name" onClick={props.onClick}> + <div className="profile-info-header__profile_display_name" onClick={props.onClick} title={props.name}> {props.name} </div> <div className="profile-info-header__app_name"> diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 282f4b4572156fb4e757616a1e8c02e19df34727..3e22208dacc6219b59cc9276562c71d43f129f03 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -60,11 +60,14 @@ class UIRoot extends Component { enableScreenSharing: PropTypes.bool, store: PropTypes.object, scene: PropTypes.object, - htmlPrefix: PropTypes.string + htmlPrefix: PropTypes.string, + showProfileEntry: PropTypes.bool, + availableVREntryTypes: PropTypes.object, + initialEnvironmentLoaded: PropTypes.bool, + janusRoomId: PropTypes.number }; state = { - availableVREntryTypes: null, entryStep: ENTRY_STEPS.start, enterInVR: false, @@ -87,14 +90,16 @@ class UIRoot extends Component { autoExitTimerInterval: null, secondsRemainingBeforeAutoExit: Infinity, - initialEnvironmentLoaded: false, exited: false, - showProfileEntry: false, - - janusRoomId: null + showProfileEntry: false }; + constructor(props) { + super(props); + this.state.showProfileEntry = this.props.showProfileEntry; + } + componentDidMount() { this.setupTestTone(); this.props.concurrentLoadDetector.addEventListener("concurrentload", this.onConcurrentLoad); @@ -104,8 +109,10 @@ class UIRoot extends Component { this.props.scene.addEventListener("stateremoved", this.onAframeStateChanged); } - componentWillUnmount() { - this.props.scene.removeEventListener("loaded", this.onSceneLoaded); + componentDidUpdate(prevProps) { + if (this.props.availableVREntryTypes && prevProps.availableVREntryTypes !== this.props.availableVREntryTypes) { + this.handleForcedVREntryType(); + } } onSceneLoaded = () => { @@ -250,7 +257,7 @@ class UIRoot extends Component { }; enterGearVR = async () => { - if (this.state.availableVREntryTypes.gearvr === VR_DEVICE_AVAILABILITY.yes) { + if (this.props.availableVREntryTypes.gearvr === VR_DEVICE_AVAILABILITY.yes) { await this.performDirectEntryFlow(true); } else { this.exit(); @@ -269,7 +276,7 @@ class UIRoot extends Component { }; enterDaydream = async () => { - if (this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe) { + if (this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe) { this.exit(); // We are not in mobile chrome, so launch into chrome via an Intent URL @@ -366,23 +373,26 @@ class UIRoot extends Component { const AudioContext = window.AudioContext || window.webkitAudioContext; const micLevelAudioContext = new AudioContext(); const micSource = micLevelAudioContext.createMediaStreamSource(mediaStream); - const analyzer = micLevelAudioContext.createAnalyser(); - const levels = new Uint8Array(analyzer.fftSize); + const analyser = micLevelAudioContext.createAnalyser(); + analyser.fftSize = 32; + const levels = new Uint8Array(analyser.frequencyBinCount); - micSource.connect(analyzer); + micSource.connect(analyser); const micUpdateInterval = setInterval(() => { - analyzer.getByteTimeDomainData(levels); - + analyser.getByteTimeDomainData(levels); let v = 0; - for (let x = 0; x < levels.length; x++) { - v = Math.max(levels[x] - 127, v); + v = Math.max(levels[x] - 128, v); } - const level = v / 128.0; - this.micLevelMovingAverage.push(Date.now(), level); - this.setState({ micLevel: this.micLevelMovingAverage.movingAverage() }); + // Multiplier to increase visual indicator. + const multiplier = 6; + // We use a moving average to smooth out the visual animation or else it would twitch too fast for + // the css renderer to keep up. + this.micLevelMovingAverage.push(Date.now(), level * multiplier); + const average = this.micLevelMovingAverage.movingAverage(); + this.setState({ micLevel: average }); }, 50); const micDeviceId = this.micDeviceIdForMicLabel(this.micLabelForMediaStream(mediaStream)); @@ -464,7 +474,7 @@ class UIRoot extends Component { }; onAudioReadyButton = () => { - this.props.enterScene(this.state.mediaStream, this.state.enterInVR, this.state.janusRoomId); + this.props.enterScene(this.state.mediaStream, this.state.enterInVR, this.props.janusRoomId); const mediaStream = this.state.mediaStream; @@ -489,7 +499,7 @@ class UIRoot extends Component { }; render() { - if (!this.state.initialEnvironmentLoaded || !this.state.availableVREntryTypes || !this.state.janusRoomId) { + if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) { return ( <IntlProvider locale={lang} messages={messages}> <div className="loading-panel"> @@ -530,7 +540,7 @@ class UIRoot extends Component { /firefox/i.test(navigator.userAgent) && ( <label className="entry-panel__screen-sharing"> <input - className="entry-panel__screen-sharing-checkbox" + className="entry-panel__screen-sharing__checkbox" type="checkbox" value={this.state.shareScreen} onChange={this.setStateAndRequestScreen} @@ -543,21 +553,21 @@ class UIRoot extends Component { this.state.entryStep === ENTRY_STEPS.start ? ( <div className="entry-panel"> <TwoDEntryButton onClick={this.enter2D} /> - {this.state.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( + {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( <GenericEntryButton onClick={this.enterVR} /> )} - {this.state.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && ( + {this.props.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && ( <GearVREntryButton onClick={this.enterGearVR} /> )} - {this.state.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && ( + {this.props.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && ( <DaydreamEntryButton onClick={this.enterDaydream} subtitle={ - this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : "" + this.props.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : "" } /> )} - {this.state.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( + {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( <div className="entry-panel__secondary" onClick={this.enterVR}> <FormattedMessage id="entry.cardboard" /> </div> @@ -579,28 +589,22 @@ class UIRoot extends Component { id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"} /> </div> - <div className="mic-grant-panel__icon"> + <div className="mic-grant-panel__button-container"> {this.state.entryStep == ENTRY_STEPS.mic_grant ? ( - <img - onClick={this.onMicGrantButton} - src="../assets/images/mic_denied.png" - srcSet="../assets/images/mic_denied@2x.png 2x" - className="mic-grant-panel__icon" - /> + <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> + <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" /> + </button> ) : ( - <img - onClick={this.onMicGrantButton} - src="../assets/images/mic_granted.png" - srcSet="../assets/images/mic_granted@2x.png 2x" - className="mic-grant-panel__icon" - /> + <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> + <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" /> + </button> )} </div> - <div className="mic-grant-panel__next" onClick={this.onMicGrantButton}> - <FormattedMessage - id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-next" : "audio.granted-next"} - /> - </div> + {this.state.entryStep == ENTRY_STEPS.mic_granted && ( + <button className="mic-grant-panel__next" onClick={this.onMicGrantButton}> + <FormattedMessage id="audio.granted-next" /> + </button> + )} </div> ) : null; @@ -622,39 +626,49 @@ class UIRoot extends Component { )} </div> <div className="audio-setup-panel__levels"> - <div className="audio-setup-panel__levels__mic"> + <div className="audio-setup-panel__levels__icon"> + <img + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + <img + src="../assets/images/level_fill.png" + srcSet="../assets/images/level_fill@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + style={micClip} + /> {this.state.audioTrack ? ( <img src="../assets/images/mic_level.png" srcSet="../assets/images/mic_level@2x.png 2x" - className="audio-setup-panel__levels__mic_icon" + className="audio-setup-panel__levels__icon-part" /> ) : ( <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" - className="audio-setup-panel__levels__mic_icon" + className="audio-setup-panel__levels__icon-part" /> )} - <img - src="../assets/images/level_fill.png" - srcSet="../assets/images/level_fill@2x.png 2x" - className="audio-setup-panel__levels__level" - style={micClip} - /> </div> - <div className="audio-setup-panel__levels__speaker"> + <div className="audio-setup-panel__levels__icon"> <img - src="../assets/images/speaker_level.png" - srcSet="../assets/images/speaker_level@2x.png 2x" - className="audio-setup-panel__levels__speaker_icon" + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" /> <img src="../assets/images/level_fill.png" srcSet="../assets/images/level_fill@2x.png 2x" - className="audio-setup-panel__levels__level" + className="audio-setup-panel__levels__icon-part" style={speakerClip} /> + <img + src="../assets/images/speaker_level.png" + srcSet="../assets/images/speaker_level@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> </div> </div> {this.state.audioTrack && ( @@ -670,9 +684,16 @@ class UIRoot extends Component { </option> ))} </select> - <div className="audio-setup-panel__device-chooser__mic-icon"> - <img src="../assets/images/mic_small.png" srcSet="../assets/images/mic_small@2x.png 2x" /> - </div> + <img + className="audio-setup-panel__device-chooser__mic-icon" + src="../assets/images/mic_small.png" + srcSet="../assets/images/mic_small@2x.png 2x" + /> + <img + className="audio-setup-panel__device-chooser__dropdown-arrow" + src="../assets/images/dropdown_arrow.png" + srcSet="../assets/images/dropdown_arrow@2x.png 2x" + /> </div> )} {this.shouldShowHmdMicWarning() && ( @@ -687,9 +708,9 @@ class UIRoot extends Component { </span> </div> )} - <div className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> + <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> <FormattedMessage id="audio.enter-now" /> - </div> + </button> </div> ) : null; diff --git a/src/storage/store.js b/src/storage/store.js index 6f480aa76db6f14904b611cecf5b0b6b84b1f41f..4351ebeda99e9f9665fcbbf395858296cd2e56a4 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -1,5 +1,6 @@ import uuid from "uuid/v4"; import { Validator } from "jsonschema"; +import { merge } from "lodash"; const LOCAL_STORE_KEY = "___mozilla_duck"; const STORE_STATE_CACHE_KEY = Symbol(); @@ -16,6 +17,8 @@ export const SCHEMA = { type: "object", additionalProperties: false, properties: { + has_agreed_to_terms: { type: "boolean" }, + has_changed_name: { type: "boolean" }, display_name: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" }, avatar_id: { type: "string" } } @@ -27,7 +30,8 @@ export const SCHEMA = { properties: { id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, profile: { $ref: "#/definitions/profile" }, - lastUsedMicDeviceId: { type: "string" } + lastUsedMicDeviceId: { type: "string" }, + lastEnteredAt: { type: "string" } }, additionalProperties: false @@ -55,7 +59,7 @@ export default class Store extends EventTarget { throw new Error("Store id is immutable."); } - const finalState = { ...this.state, ...newState }; + const finalState = merge(this.state, newState); const isValid = validator.validate(finalState, SCHEMA).valid; if (!isValid) { diff --git a/src/systems/app-mode.js b/src/systems/app-mode.js index da5a4239298512ee51c2267b34882fdc32af0118..49e8d361bd90de4612fe4548fc0ecb4cc027d53d 100644 --- a/src/systems/app-mode.js +++ b/src/systems/app-mode.js @@ -1,6 +1,6 @@ /* global AFRAME, console, setTimeout, clearTimeout */ -const AppModes = Object.freeze({ DEFAULT: "default", HUD: "hud" }); +export const AppModes = Object.freeze({ DEFAULT: "default", HUD: "hud" }); /** * Simple system for keeping track of a modal app state @@ -89,82 +89,6 @@ AFRAME.registerComponent("app-mode-input-mappings", { } }); -const TWOPI = Math.PI * 2; -function deltaAngle(a, b) { - const p = Math.abs(b - a) % TWOPI; - return p > Math.PI ? TWOPI - p : p; -} - -/** - * Positions the HUD and toggles app mode based on where the user is looking - */ -AFRAME.registerComponent("hud-controller", { - schema: { - head: { type: "selector" }, - offset: { default: 0.7 }, // distance from hud above head, - lookCutoff: { default: 20 }, // angle at which the hud should be "on", - animRange: { default: 30 }, // degrees over which to animate the hud into view - yawCutoff: { default: 50 } // yaw degrees at wich the hud should reoirent even if the user is looking up - }, - init() { - this.isYLocked = false; - this.lockedHeadPositionY = 0; - }, - - pause() { - // TODO: this assumes full control over current app mode reguardless of what else might be manipulating it, this is obviously wrong - const AppModeSystem = this.el.sceneEl.systems["app-mode"]; - AppModeSystem.setMode(AppModes.DEFAULT); - }, - - tick() { - const hud = this.el.object3D; - const head = this.data.head.object3D; - const sceneEl = this.el.sceneEl; - - const { offset, lookCutoff, animRange, yawCutoff } = this.data; - - const pitch = head.rotation.x * THREE.Math.RAD2DEG; - const yawDif = deltaAngle(head.rotation.y, hud.rotation.y) * THREE.Math.RAD2DEG; - - // Reorient the hud only if the user is looking away from the hud, for right now this arbitrarily means the hud is 1/3 way animated away - // TODO: come up with better huristics for this that maybe account for the user turning away from the hud "too far", also animate the position so that it doesnt just snap. - if (yawDif >= yawCutoff || pitch < lookCutoff - animRange / 3) { - const lookDir = new THREE.Vector3(0, 0, -1); - lookDir.applyQuaternion(head.quaternion); - lookDir.add(head.position); - hud.position.x = lookDir.x; - hud.position.z = lookDir.z; - hud.setRotationFromEuler(new THREE.Euler(0, head.rotation.y, 0)); - } - - // animate the hud into place over animRange degrees as the user aproaches the lookCutoff angle - const t = 1 - THREE.Math.clamp(lookCutoff - pitch, 0, animRange) / animRange; - - // Lock the hud in place relative to a known head position so it doesn't bob up and down - // with the user's head - if (!this.isYLocked && t === 1) { - this.lockedHeadPositionY = head.position.y; - } - const EPSILON = 0.001; - this.isYLocked = t > 1 - EPSILON; - - hud.position.y = (this.isYLocked ? this.lockedHeadPositionY : head.position.y) + offset + (1 - t) * offset; - hud.rotation.x = (1 - t) * THREE.Math.DEG2RAD * 90; - - // update the app mode when the HUD locks on or off - // TODO: this assumes full control over current app mode reguardless of what else might be manipulating it, this is obviously wrong - const AppModeSystem = sceneEl.systems["app-mode"]; - if (pitch > lookCutoff && AppModeSystem.mode !== AppModes.HUD) { - AppModeSystem.setMode(AppModes.HUD); - sceneEl.renderer.sortObjects = true; - } else if (pitch < lookCutoff && AppModeSystem.mode === AppModes.HUD) { - AppModeSystem.setMode(AppModes.DEFAULT); - sceneEl.renderer.sortObjects = false; - } - } -}); - /** * Toggle visibility of an entity based on if the user is in vr mode or not */ diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js new file mode 100644 index 0000000000000000000000000000000000000000..13e51b21b1e1f3702432a7a04c6804949c332b88 --- /dev/null +++ b/src/utils/hub-channel.js @@ -0,0 +1,75 @@ +import moment from "moment-timezone"; + +export default class HubChannel { + constructor(store) { + this.store = store; + } + + setPhoenixChannel = channel => { + this.channel = channel; + }; + + sendEntryEvent = async () => { + if (!this.channel) { + console.warn("No phoenix channel initialized before room entry."); + return; + } + + let entryDisplayType = "Screen"; + + if (navigator.getVRDisplays) { + const vrDisplay = (await navigator.getVRDisplays()).find(d => d.isPresenting); + + if (vrDisplay) { + entryDisplayType = vrDisplay.displayName; + } + } + + // This is fairly hacky, but gets the # of initial occupants + let initialOccupantCount = 0; + + if (NAF.connection.adapter && NAF.connection.adapter.publisher) { + initialOccupantCount = NAF.connection.adapter.publisher.initialOccupants.length; + } + + const entryTimingFlags = this.getEntryTimingFlags(); + + const entryEvent = { + ...entryTimingFlags, + initialOccupantCount, + entryDisplayType, + userAgent: navigator.userAgent + }; + + this.channel.push("events:entered", entryEvent); + }; + + getEntryTimingFlags = () => { + const entryTimingFlags = { isNewDaily: true, isNewMonthly: true, isNewDayWindow: true, isNewMonthWindow: true }; + + if (!this.store.state.lastEnteredAt) { + return entryTimingFlags; + } + + const lastEntered = moment(this.store.state.lastEnteredAt); + const lastEnteredPst = moment(lastEntered).tz("America/Los_Angeles"); + const nowPst = moment().tz("America/Los_Angeles"); + const dayWindowAgo = moment().subtract(1, "day"); + const monthWindowAgo = moment().subtract(1, "month"); + + entryTimingFlags.isNewDaily = + lastEnteredPst.dayOfYear() !== nowPst.dayOfYear() || lastEnteredPst.year() !== nowPst.year(); + entryTimingFlags.isNewMonthly = + lastEnteredPst.month() !== nowPst.month() || lastEnteredPst.year() !== nowPst.year(); + entryTimingFlags.isNewDayWindow = lastEntered.isBefore(dayWindowAgo); + entryTimingFlags.isNewMonthWindow = lastEntered.isBefore(monthWindowAgo); + + return entryTimingFlags; + }; + + disconnect = () => { + if (this.channel) { + this.channel.socket.disconnect(); + } + }; +} diff --git a/src/utils/identity.js b/src/utils/identity.js index def830cbc4df4d9d1a71b0c10d7b54c5a5610b4a..db78b027e3e851aa254532f264438e362062576c 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -1,178 +1,108 @@ import { avatars } from "../assets/avatars/avatars.js"; const names = [ - "albattani", - "allen", - "almeida", - "agnesi", - "archimedes", - "ardinghelli", - "aryabhata", - "austin", - "babbage", - "banach", - "bardeen", - "bartik", - "bassi", - "beaver", - "bell", - "benz", - "bhabha", - "bhaskara", - "blackwell", - "bohr", - "booth", - "borg", - "bose", - "boyd", - "brahmagupta", - "brattain", - "brown", - "carson", - "chandrasekhar", - "shannon", - "clarke", - "colden", - "cori", - "cray", - "curran", - "curie", - "darwin", - "davinci", - "dijkstra", - "dubinsky", - "easley", - "edison", - "einstein", - "elion", - "engelbart", - "euclid", - "euler", - "fermat", - "fermi", - "feynman", - "franklin", - "galileo", - "gates", - "goldberg", - "goldstine", - "goldwasser", - "golick", - "goodall", - "haibt", - "hamilton", - "hawking", - "heisenberg", - "hermann", - "heyrovsky", - "hodgkin", - "hoover", - "hopper", - "hugle", - "hypatia", - "jackson", - "jang", - "jennings", - "jepsen", - "johnson", - "joliot", - "jones", - "kalam", - "kare", - "keller", - "kepler", - "khorana", - "kilby", - "kirch", - "knuth", - "kowalevski", - "lalande", - "lamarr", - "lamport", - "leakey", - "leavitt", - "lewin", - "lichterman", - "liskov", - "lovelace", - "lumiere", - "mahavira", - "mayer", - "mccarthy", - "mcclintock", - "mclean", - "mcnulty", - "meitner", - "meninsky", - "mestorf", - "minsky", - "mirzakhani", - "morse", - "murdock", - "neumann", - "newton", - "nightingale", - "nobel", - "noether", - "northcutt", - "noyce", - "panini", - "pare", - "pasteur", - "payne", - "perlman", - "pike", - "poincare", - "poitras", - "ptolemy", - "raman", - "ramanujan", - "ride", - "montalcini", - "ritchie", - "roentgen", - "rosalind", - "saha", - "sammet", - "shaw", - "shirley", - "shockley", - "sinoussi", - "snyder", - "spence", - "stallman", - "stonebraker", - "swanson", - "swartz", - "swirles", - "tesla", - "thompson", - "torvalds", - "turing", - "varahamihira", - "visvesvaraya", - "volhard", - "wescoff", - "wiles", - "williams", - "wilson", - "wing", - "wozniak", - "wright", - "yalow", - "yonath" + "Baers-Pochard", + "Baikal-Teal", + "Barrows-Goldeneye", + "Blue-Billed", + "Blue-Duck", + "Blue-Winged", + "Brown-Teal", + "Bufflehead", + "Canvasback", + "Cape-Shoveler", + "Chestnut-Teal", + "Chiloe-Wigeon", + "Cinnamon-Teal", + "Comb-Duck", + "Common-Eider", + "Common-Goldeneye", + "Common-Merganser", + "Common-Pochard", + "Common-Scoter", + "Common-Shelduck", + "Cotton-Pygmy", + "Crested-Duck", + "Crested-Shelduck", + "Eatons-Pintail", + "Falcated", + "Ferruginous", + "Freckled-Duck", + "Gadwall", + "Garganey", + "Greater-Scaup", + "Green-Pygmy", + "Grey-Teal", + "Hardhead", + "Harlequin", + "Hartlaubs", + "Hooded-Merganser", + "Hottentot-Teal", + "Kelp-Goose", + "King-Eider", + "Lake-Duck", + "Laysan-Duck", + "Lesser-Scaup", + "Long-Tailed", + "Maccoa-Duck", + "Mallard", + "Mandarin", + "Marbled-Teal", + "Mellers-Duck", + "Merganser", + "Northern-Pintail", + "Orinoco-Goose", + "Paradise-Shelduck", + "Plumed-Whistler", + "Puna-Teal", + "Pygmy-Goose", + "Radjah-Shelduck", + "Red-Billed", + "Red-Crested", + "Red-Shoveler", + "Ring-Necked", + "Ringed-Teal", + "Rosy-Billed", + "Ruddy-Shelduck", + "Salvadoris-Teal", + "Scaly-Sided", + "Shelduck", + "Shoveler", + "Silver-Teal", + "Smew", + "Spectacled-Eider", + "Spot-Billed", + "Spotted-Whistler", + "Steamerduck", + "Stellers-Eider", + "Sunda-Teal", + "Surf-Scoter", + "Tufted-Duck", + "Velvet-Scoter", + "Wandering-Whistler", + "Whistling-duck", + "White-Backed", + "White-Cheeked", + "White-Winged", + "Wigeon", + "Wood-Duck", + "Yellow-Billed" ]; function selectRandom(arr) { return arr[Math.floor(Math.random() * arr.length)]; } +export function generateRandomName() { + return `${selectRandom(names)}-${Math.floor(10000 + Math.random() * 10000)}`; +} + export const avatarIds = avatars.map(av => av.id); export function generateDefaultProfile() { - const name = selectRandom(names); return { - display_name: name.replace(/^./, name[0].toUpperCase()), + has_agreed_to_terms: false, + has_changed_name: false, avatar_id: selectRandom(avatarIds) }; } diff --git a/yarn.lock b/yarn.lock index 97096cfc4ee0f9850dfba4d5e359d96c115c78d3..4391edbc296f19f7af084c959e516b67caed2123 100644 --- a/yarn.lock +++ b/yarn.lock @@ -118,7 +118,7 @@ accepts@1.3.3: mime-types "~2.1.11" negotiator "0.6.1" -accepts@~1.3.4: +accepts@~1.3.4, accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: @@ -170,19 +170,13 @@ aframe-extras@^4.0.0: version "0.1.2" resolved "https://github.com/johnshaughnessy/aframe-input-mapping-component#4c7e493ad6c4a25eef27d32551c94d8b78541191" -aframe-lerp-component@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/aframe-lerp-component/-/aframe-lerp-component-1.1.0.tgz#dc8c12c53698770c30f25eef8873e34a4e79c765" - dependencies: - almost-equal "^1.1.0" - "aframe-physics-extras@https://github.com/infinitelee/aframe-physics-extras#fix/physics-collider-crash": version "0.1.2" resolved "https://github.com/infinitelee/aframe-physics-extras#49b2d5d3c0caac905783aee51d9e89dbdf7199b8" -"aframe-physics-system@https://github.com/donmccurdy/aframe-physics-system": - version "3.0.1" - resolved "https://github.com/donmccurdy/aframe-physics-system#08a98a4c3d77c4c38a1fa27067aa0d894447902e" +"aframe-physics-system@https://github.com/infinitelee/aframe-physics-system#feature/shape-component": + version "3.0.2" + resolved "https://github.com/infinitelee/aframe-physics-system#be3e43f1af5b995952e1b3d27dce216ab2e79f05" dependencies: browserify "^14.3.0" budo "^10.0.3" @@ -238,10 +232,6 @@ ajv@^6.0.1, ajv@^6.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -almost-equal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/almost-equal/-/almost-equal-1.1.0.tgz#f851c631138757994276aa2efbe8dfa3066cccdd" - alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -474,6 +464,10 @@ async@0.2.x: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" +async@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.4.2.tgz#6c9edcb11ced4f0dd2f2d40db0d49a109c088aab" + async@^1.5.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1351,7 +1345,7 @@ boom@2.x.x: dependencies: hoek "2.x.x" -brace-expansion@^1.1.7: +brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: @@ -1904,6 +1898,13 @@ cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" +cli@0.6.x: + version "0.6.6" + resolved "https://registry.yarnpkg.com/cli/-/cli-0.6.6.tgz#02ad44a380abf27adac5e6f0cdd7b043d74c53e3" + dependencies: + exit "0.1.2" + glob "~ 3.2.1" + cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -2010,14 +2011,18 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@*, colors@^1.1.2, colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colors@*: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@^1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + combine-source-map@~0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" @@ -2046,6 +2051,10 @@ commander@2.14.x, commander@^2.9.0, commander@~2.14.1: version "2.14.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" +commander@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -2112,7 +2121,7 @@ connect-pushstate@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/connect-pushstate/-/connect-pushstate-1.1.0.tgz#bcab224271c439604a0fb0f614c0a5f563e88e24" -console-browserify@^1.1.0: +console-browserify@1.1.x, console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" dependencies: @@ -2300,6 +2309,12 @@ cssesc@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" +csslint@0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/csslint/-/csslint-0.10.0.tgz#3a6a04e7565c8e9d19beb49767c7ec96e8365805" + dependencies: + parserlib "~0.2.2" + cssnano@^3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" @@ -2634,13 +2649,19 @@ domhandler@2.1: dependencies: domelementtype "1" +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + dependencies: + domelementtype "1" + domutils@1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" dependencies: domelementtype "1" -domutils@1.5.1: +domutils@1.5, domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" dependencies: @@ -2792,6 +2813,10 @@ enhanced-resolve@^4.0.0: memory-fs "^0.4.0" tapable "^1.0.0" +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" @@ -3021,6 +3046,10 @@ exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" +exit@0.1.2, exit@0.1.x: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -3051,7 +3080,42 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express@^4.10.7, express@^4.16.2: +express@^4.10.7: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -3257,6 +3321,18 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -3532,6 +3608,16 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob@5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^6.0.1, glob@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -3553,6 +3639,13 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.2, glob@~7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +"glob@~ 3.2.1": + version "3.2.11" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" + dependencies: + inherits "2" + minimatch "0.3" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -3896,6 +3989,30 @@ htmlescape@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" +htmlhint@^0.9.13: + version "0.9.13" + resolved "https://registry.yarnpkg.com/htmlhint/-/htmlhint-0.9.13.tgz#08163cb1e6aa505048ebb0b41063a7ca07dc6c88" + dependencies: + async "1.4.2" + colors "1.0.3" + commander "2.6.0" + csslint "0.10.0" + glob "5.0.15" + jshint "2.8.0" + parse-glob "3.0.4" + strip-json-comments "1.0.4" + xml "1.0.0" + +htmlparser2@3.8.x: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + htmlparser2@~3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" @@ -3909,7 +4026,7 @@ http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@1.6.2, http-errors@~1.6.2: +http-errors@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: @@ -3918,6 +4035,15 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.4.0: version "0.4.10" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" @@ -4523,6 +4649,19 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" +jshint@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.8.0.tgz#1d09a3bd913c4cadfa81bf18d582bd85bffe0d44" + dependencies: + cli "0.6.x" + console-browserify "1.1.x" + exit "0.1.x" + htmlparser2 "3.8.x" + lodash "3.7.x" + minimatch "2.0.x" + shelljs "0.3.x" + strip-json-comments "1.0.x" + json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" @@ -4789,6 +4928,10 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" +lodash@3.7.x: + version "3.7.0" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45" + lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" @@ -4841,6 +4984,10 @@ lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" @@ -5051,12 +5198,25 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" +minimatch@0.3: + version "0.3.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" + dependencies: + lru-cache "2" + sigmund "~1.0.0" + "minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: brace-expansion "^1.1.7" +minimatch@2.0.x: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + dependencies: + brace-expansion "^1.0.0" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -5148,6 +5308,16 @@ module-deps@^6.0.0: through2 "^2.0.0" xtend "^4.0.0" +moment-timezone@^0.5.14: + version "0.5.14" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -5250,10 +5420,9 @@ neo-async@^2.5.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" "networked-aframe@https://github.com/mozillareality/networked-aframe#mr-social-client/master": - version "0.5.1" - resolved "https://github.com/mozillareality/networked-aframe#5d2f50ddf65140f0ae671b2b53c1c667de18dca5" + version "0.6.1" + resolved "https://github.com/mozillareality/networked-aframe#69be0e7e5f66070526c8240cb795b9e88da971a9" dependencies: - aframe-lerp-component "^1.1.0" easyrtc "1.1.0" express "^4.10.7" serve-static "^1.8.0" @@ -5717,7 +5886,7 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" -parse-glob@^3.0.4: +parse-glob@3.0.4, parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" dependencies: @@ -5752,6 +5921,10 @@ parseqs@0.0.5: dependencies: better-assert "~1.0.0" +parserlib@~0.2.2: + version "0.2.5" + resolved "https://registry.yarnpkg.com/parserlib/-/parserlib-0.2.5.tgz#85907dd8605aa06abb3dd295d50bb2b8fa4dd117" + parseuri@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" @@ -5851,6 +6024,10 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" +phoenix@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.3.0.tgz#1df2c27f986ee295e37c9983ec28ebac1d7f4a3e" + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -6222,7 +6399,7 @@ prop-types@^15.5.4, prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -proxy-addr@~2.0.2: +proxy-addr@~2.0.2, proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" dependencies: @@ -6477,6 +6654,15 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -6981,7 +7167,7 @@ serve-static@1.13.1: parseurl "~1.3.2" send "0.16.1" -serve-static@^1.10.0, serve-static@^1.8.0: +serve-static@1.13.2, serve-static@^1.10.0, serve-static@^1.8.0: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" dependencies: @@ -7075,6 +7261,10 @@ shell-quote@^1.4.2, shell-quote@^1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" +shelljs@0.3.x: + version "0.3.0" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1" + shelljs@^0.7.0: version "0.7.8" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" @@ -7083,6 +7273,18 @@ shelljs@^0.7.0: interpret "^1.0.0" rechoir "^0.6.2" +shelljs@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.1.tgz#729e038c413a2254c4078b95ed46e0397154a9f1" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -7341,7 +7543,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", statuses@~1.3.1: +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -7487,6 +7693,10 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" +strip-json-comments@1.0.4, strip-json-comments@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -7769,7 +7979,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.15: +type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: @@ -8353,6 +8563,10 @@ xml-char-classes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d" +xml@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.0.tgz#de3ee912477be2f250b60f612f34a8c4da616efe" + xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"