Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
@ -1,5 +1,11 @@
|
|||||||
## Version develop
|
## Version develop
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- New scripting API features :
|
||||||
|
- Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu` to add a custom menu or an iframe to the menu.
|
||||||
|
|
||||||
|
## Version 1.4.14
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
- New scripting API features :
|
- New scripting API features :
|
||||||
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
||||||
|
@ -35,6 +35,9 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
|
|||||||
127.0.0.1 workadventure.localhost
|
127.0.0.1 workadventure.localhost
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: If on the first run you get a page with "network error". Try to ``docker-compose stop`` , then ``docker-compose start``.
|
||||||
|
Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.testing and accepting them.
|
||||||
|
|
||||||
### MacOS developers, your environment with Vagrant
|
### MacOS developers, your environment with Vagrant
|
||||||
|
|
||||||
If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c).
|
If you are using MacOS, you can increase Docker performance using Vagrant. If you want more explanations, you can read [this medium article](https://medium.com/better-programming/vagrant-to-increase-docker-performance-with-macos-25b354b0c65c).
|
||||||
|
@ -54,7 +54,6 @@
|
|||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"query-string": "^6.13.3",
|
"query-string": "^6.13.3",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"systeminformation": "^4.31.1",
|
|
||||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||||
"uuidv4": "^6.0.7"
|
"uuidv4": "^6.0.7"
|
||||||
},
|
},
|
||||||
|
@ -554,7 +554,7 @@ chokidar@^3.4.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.1.2"
|
fsevents "~2.1.2"
|
||||||
|
|
||||||
chownr@^1.1.1:
|
chownr@^1.1.4:
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||||
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||||
@ -1159,7 +1159,7 @@ fragment-cache@^0.2.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
map-cache "^0.2.2"
|
map-cache "^0.2.2"
|
||||||
|
|
||||||
fs-minipass@^1.2.5:
|
fs-minipass@^1.2.7:
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
|
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
|
||||||
integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
|
integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
|
||||||
@ -1969,7 +1969,7 @@ minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||||
|
|
||||||
minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
|
minipass@^2.6.0, minipass@^2.9.0:
|
||||||
version "2.9.0"
|
version "2.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
|
||||||
integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
|
integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
|
||||||
@ -1977,7 +1977,7 @@ minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
|
|||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
yallist "^3.0.0"
|
yallist "^3.0.0"
|
||||||
|
|
||||||
minizlib@^1.2.1:
|
minizlib@^1.3.3:
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
|
resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
|
||||||
integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
|
integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
|
||||||
@ -1992,7 +1992,7 @@ mixin-deep@^1.2.0:
|
|||||||
for-in "^1.0.2"
|
for-in "^1.0.2"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3:
|
mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5:
|
||||||
version "0.5.5"
|
version "0.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||||
@ -2290,9 +2290,9 @@ path-key@^3.0.0, path-key@^3.1.0:
|
|||||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||||
|
|
||||||
path-parse@^1.0.6:
|
path-parse@^1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
path-type@^1.0.0:
|
path-type@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
@ -2578,7 +2578,7 @@ rxjs@^6.6.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
@ -2962,11 +2962,6 @@ supports-color@^7.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
systeminformation@^4.31.1:
|
|
||||||
version "4.31.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.31.1.tgz#2e02c26987494d4b6a4d2d83138724593bc98d50"
|
|
||||||
integrity sha512-dVCDWNMN8ncMZo5vbMCA5dpAdMgzafK2ucuJy5LFmGtp1cG6farnPg8QNvoOSky9SkFoEX1Aw0XhcOFV6TnLYA==
|
|
||||||
|
|
||||||
table@^5.2.3:
|
table@^5.2.3:
|
||||||
version "5.4.6"
|
version "5.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||||
@ -2978,17 +2973,17 @@ table@^5.2.3:
|
|||||||
string-width "^3.0.0"
|
string-width "^3.0.0"
|
||||||
|
|
||||||
tar@^4.4.2:
|
tar@^4.4.2:
|
||||||
version "4.4.13"
|
version "4.4.19"
|
||||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
|
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3"
|
||||||
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
|
integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr "^1.1.1"
|
chownr "^1.1.4"
|
||||||
fs-minipass "^1.2.5"
|
fs-minipass "^1.2.7"
|
||||||
minipass "^2.8.6"
|
minipass "^2.9.0"
|
||||||
minizlib "^1.2.1"
|
minizlib "^1.3.3"
|
||||||
mkdirp "^0.5.0"
|
mkdirp "^0.5.5"
|
||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.2.1"
|
||||||
yallist "^3.0.3"
|
yallist "^3.1.1"
|
||||||
|
|
||||||
tdigest@^0.1.1:
|
tdigest@^0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
@ -3282,7 +3277,7 @@ y18n@^3.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
|
||||||
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
|
||||||
|
|
||||||
yallist@^3.0.0, yallist@^3.0.3:
|
yallist@^3.0.0, yallist@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||||
|
6
benchmark/package-lock.json
generated
@ -429,9 +429,9 @@
|
|||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||||
},
|
},
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
},
|
},
|
||||||
"path-type": {
|
"path-type": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
@ -315,8 +315,8 @@ path-is-absolute@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
|
|
||||||
path-parse@^1.0.6:
|
path-parse@^1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
|
|
||||||
path-type@^1.0.0:
|
path-type@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
|
@ -101,6 +101,7 @@
|
|||||||
},
|
},
|
||||||
"redis": {
|
"redis": {
|
||||||
"image": "redis:6",
|
"image": "redis:6",
|
||||||
|
"ports": [6379]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -7,14 +7,14 @@ not overwrite existing ones) and click on the animation editor:
|
|||||||
|
|
||||||
|
|
||||||
<div class="px-5 card rounded d-inline-block">
|
<div class="px-5 card rounded d-inline-block">
|
||||||
<img class="document-img" src="https://workadventu.re/img/docs/anims/camera.png" alt="" />
|
<img class="document-img" src="images/anims/camera.png" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
You can now add all tiles that should be part of the animation via drag and drop to the "playlist" and adjust the frame duration:
|
You can now add all tiles that should be part of the animation via drag and drop to the "playlist" and adjust the frame duration:
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img class="figure-img img-fluid rounded" src="https://workadventu.re/img/docs/anims/animation_editor.png" alt="" />
|
<img class="figure-img img-fluid rounded" src="images/anims/animation_editor.png" alt="" />
|
||||||
<figcaption class="figure-caption">The tile animation editor</figcaption>
|
<figcaption class="figure-caption">The tile animation editor</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
@ -24,7 +24,7 @@ You can preview animations directly in Tiled, using the "Show tile animations" o
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img class="figure-img img-fluid rounded" src="https://workadventu.re/img/docs/anims/settings_show_animations.png" alt="" />
|
<img class="figure-img img-fluid rounded" src="images/anims/settings_show_animations.png" alt="" />
|
||||||
<figcaption class="figure-caption">The Show Tile Animations option</figcaption>
|
<figcaption class="figure-caption">The Show Tile Animations option</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,3 +18,4 @@ The list of functions below is **deprecated**. You should not use those but. use
|
|||||||
- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
|
- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
|
||||||
- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
|
- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
|
||||||
- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
|
- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
|
||||||
|
- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`.
|
@ -7,7 +7,7 @@ If you use group layers in your map, to reference a layer in a group you will ne
|
|||||||
Example :
|
Example :
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ Listens to the position of the current user. The event is triggered when the use
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
@ -140,7 +140,7 @@ Replace the tile at the `x` and `y` coordinates in the layer named `layer` by th
|
|||||||
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ Moreover, `WA.state` functions can be used to persist this state across reloads.
|
|||||||
```
|
```
|
||||||
WA.state.saveVariable(key : string, data : unknown): void
|
WA.state.saveVariable(key : string, data : unknown): void
|
||||||
WA.state.loadVariable(key : string) : unknown
|
WA.state.loadVariable(key : string) : unknown
|
||||||
|
WA.state.hasVariable(key : string) : boolean
|
||||||
WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription
|
WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription
|
||||||
WA.state.[any property]: unknown
|
WA.state.[any property]: unknown
|
||||||
```
|
```
|
||||||
@ -70,7 +71,7 @@ Each object will represent a variable.
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/object_variable.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/object_variable.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ You can position this popup by using a "rectangle" object in Tiled that you will
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -68,25 +68,53 @@ WA.room.onLeaveZone('myZone', () => {
|
|||||||
|
|
||||||
### Add custom menu
|
### Add custom menu
|
||||||
|
|
||||||
```typescript
|
|
||||||
WA.ui.registerMenuCommand(menuCommand: string, callback: (menuCommand: string) => void): void
|
|
||||||
```
|
```
|
||||||
Add a custom menu item containing the text `commandDescriptor` in the main menu. A click on the menu will trigger the `callback`.
|
WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu
|
||||||
|
```
|
||||||
|
Add a custom menu item containing the text `commandDescriptor` in the navbar of the menu.
|
||||||
|
`options` attribute accepts an object with three properties :
|
||||||
|
- `callback : (commandDescriptor: string) => void` : A click on the custom menu will trigger the `callback`.
|
||||||
|
- `iframe: string` : A click on the custom menu will open the `iframe` inside the menu.
|
||||||
|
- `allowApi?: boolean` : Allow the iframe of the custom menu to use the Scripting API.
|
||||||
|
|
||||||
|
Important : `options` accepts only `callback` or `iframe` not both.
|
||||||
|
|
||||||
Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script.
|
Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script.
|
||||||
|
|
||||||
Example:
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<img src="images/custom-menu-navbar.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<img src="images/custom-menu-iframe.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Example:
|
||||||
```javascript
|
```javascript
|
||||||
|
const menu = WA.ui.registerMenuCommand('menu test',
|
||||||
|
{
|
||||||
|
callback: () => {
|
||||||
|
WA.chat.sendChatMessage('test');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
WA.ui.registerMenuCommand("test", () => {
|
// Some time later, if you want to remove the menu:
|
||||||
WA.chat.sendChatMessage("test clicked", "menu cmd")
|
menu.remove();
|
||||||
})
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="col">
|
Please note that `registerMenuCommand` returns an object of the `Menu` class.
|
||||||
<img src="https://workadventu.re/img/docs/menu-command.png" class="figure-img img-fluid rounded" alt="" />
|
|
||||||
</div>
|
The `Menu` class contains a single method: `remove(): void`. This will obviously remove the menu when called.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Menu {
|
||||||
|
/**
|
||||||
|
* Remove the menu
|
||||||
|
*/
|
||||||
|
remove() {};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +131,7 @@ WA.ui.displayActionMessage({
|
|||||||
Displays a message at the bottom of the screen (that will disappear when space bar is pressed).
|
Displays a message at the bottom of the screen (that will disappear when space bar is pressed).
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="https://workadventu.re/img/docs/trigger_message.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/trigger_message.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
BIN
docs/maps/images/anims/animation_editor.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
docs/maps/images/anims/camera.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/maps/images/anims/settings_show_animations.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/maps/images/collides-1.png
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
docs/maps/images/collides-2.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
docs/maps/images/collides-3.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
docs/maps/images/collides-4.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/maps/images/custom-menu-iframe.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
docs/maps/images/custom-menu-navbar.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
docs/maps/images/groupLayer.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
docs/maps/images/menu-command.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
docs/maps/images/nameIndexProperty.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/maps/images/object_variable.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/maps/images/open_website_allow_api.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/maps/images/screen_popup_in_game.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
docs/maps/images/screen_popup_tiled.png
Normal file
After Width: | Height: | Size: 162 KiB |
BIN
docs/maps/images/script_property.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
docs/maps/images/text-object.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
docs/maps/images/tiled_screenshot_1.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/maps/images/trigger_event.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/maps/images/trigger_message.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/maps/images/website_allowapi_property.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/maps/images/website_url_property.png
Normal file
After Width: | Height: | Size: 59 KiB |
@ -46,7 +46,7 @@ You can put relative URLs. If your script file is next to your map, you can simp
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/script_property.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/script_property.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
<figcaption class="figure-caption">The script property</figcaption>
|
<figcaption class="figure-caption">The script property</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +72,7 @@ In order to allow communication with WorkAdventure, you need to add an additiona
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/open_website_allow_api.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/open_website_allow_api.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
<figcaption class="figure-caption">The `openWebsiteAllowApi` property</figcaption>
|
<figcaption class="figure-caption">The `openWebsiteAllowApi` property</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@ font that has support for a variety of accents. It renders great when used at *8
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/text-object.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
<img src="images/text-object.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
<figcaption class="figure-caption">The "font-family" property</figcaption>
|
<figcaption class="figure-caption">The "font-family" property</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@ A few things to notice:
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/tiled_screenshot_1.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
<img src="images/tiled_screenshot_1.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
<figcaption class="figure-caption">"floorLayer" is compulsory</figcaption>
|
<figcaption class="figure-caption">"floorLayer" is compulsory</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
@ -62,21 +62,21 @@ To make a tile "collidable", you should:
|
|||||||
|
|
||||||
1. select the relevant tileset and switch to "edit" mode:
|
1. select the relevant tileset and switch to "edit" mode:
|
||||||
|
|
||||||
![](https://workadventu.re/img/docs/collides-1.png){.document-img}
|
![](images/collides-1.png){.document-img}
|
||||||
|
|
||||||
2. right click on a tile of the tileset to select it:
|
2. right click on a tile of the tileset to select it:
|
||||||
|
|
||||||
![](https://workadventu.re/img/docs/collides-2.png){.document-img}
|
![](images/collides-2.png){.document-img}
|
||||||
|
|
||||||
3. on the left pane in the custom properties section, right click and select "Add properties":
|
3. on the left pane in the custom properties section, right click and select "Add properties":
|
||||||
|
|
||||||
![](https://workadventu.re/img/docs/collides-3.png){.document-img}
|
![](images/collides-3.png){.document-img}
|
||||||
|
|
||||||
Please add a `collides` property. The type of the property must be **bool**.
|
Please add a `collides` property. The type of the property must be **bool**.
|
||||||
|
|
||||||
4. finally, check the checkbox for the `collides` property:
|
4. finally, check the checkbox for the `collides` property:
|
||||||
|
|
||||||
![](https://workadventu.re/img/docs/collides-4.png){.document-img}
|
![](images/collides-4.png){.document-img}
|
||||||
|
|
||||||
Repeat for every tile that should be "collidable".
|
Repeat for every tile that should be "collidable".
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ To do this in Tiled:
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/website_url_property.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
<img src="images/website_url_property.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
<figcaption class="figure-caption">A "website" object</figcaption>
|
<figcaption class="figure-caption">A "website" object</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +34,7 @@ to explicitly allow it, by setting an additional `allowApi` property to `true`.
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<figure class="figure">
|
<figure class="figure">
|
||||||
<img src="https://workadventu.re/img/docs/website_allowapi_property.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
<img src="images/website_allowapi_property.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||||
<figcaption class="figure-caption">A "website" object that can communicate using the Iframe API</figcaption>
|
<figcaption class="figure-caption">A "website" object that can communicate using the Iframe API</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
26
front/dist/index.tmpl.html
vendored
@ -34,7 +34,6 @@
|
|||||||
<title>WorkAdventure</title>
|
<title>WorkAdventure</title>
|
||||||
</head>
|
</head>
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
<body id="body" style="margin: 0; background-color: #000">
|
||||||
|
|
||||||
<div class="main-container" id="main-container">
|
<div class="main-container" id="main-container">
|
||||||
<!-- Create the editor container -->
|
<!-- Create the editor container -->
|
||||||
<div id="game" class="game">
|
<div id="game" class="game">
|
||||||
@ -62,31 +61,6 @@
|
|||||||
<img src="resources/logos/close.svg"/>
|
<img src="resources/logos/close.svg"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="audioplayerctrl" class="hidden">
|
|
||||||
<div class="audioplayer">
|
|
||||||
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
|
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z" />
|
|
||||||
<g id="audioplayer_volume_icon_playing">
|
|
||||||
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" />
|
|
||||||
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" />
|
|
||||||
<path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div class="audioplayer">
|
|
||||||
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.025" value="1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="audioplayer">
|
|
||||||
<label id="label-audioplayer_decrease_while_talking" for="audioplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
|
||||||
reduce in conversations
|
|
||||||
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
|
||||||
</label>
|
|
||||||
<div id="audioplayer" style="visibility: hidden"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||||
|
78
front/dist/resources/html/gameMenu.html
vendored
@ -1,78 +0,0 @@
|
|||||||
<style>
|
|
||||||
#gameMenu main{
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
#gameMenu button {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
|
||||||
#gameMenu section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
section#socialLinks{
|
|
||||||
position: absolute;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
section#socialLinks img{
|
|
||||||
width: 32px;
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
}
|
|
||||||
@media only screen and (max-height: 700px) {
|
|
||||||
#gameMenu main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-end;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
#gameMenu section{
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
section#socialLinks{
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div id="gameMenu" hidden>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section>
|
|
||||||
<button id="shareButton">Share url</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="changeNameButton">Edit name</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="changeSkinButton">Edit skin</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="changeCompanionButton">Edit companion</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="editGameSettingsButton">Settings</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="toggleFullscreen">Toggle fullscreen</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="enableNotification">Enable notifications</button>
|
|
||||||
</section>
|
|
||||||
<!-- TODO activate authentication -->
|
|
||||||
<section hidden>
|
|
||||||
<button id="oidcLogin">Oauth Login</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button id="sparkButton">Create map</button>
|
|
||||||
</section>
|
|
||||||
<section id="adminConsoleSection" hidden>
|
|
||||||
<button id="adminConsoleButton">Admin console</button>
|
|
||||||
</section>
|
|
||||||
<section id="socialLinks" hidden>
|
|
||||||
<a class="not-button" href="https://www.facebook.com/workadventure.WA" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
|
||||||
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
28
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,28 +0,0 @@
|
|||||||
<style>
|
|
||||||
#menuIcon button {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
}
|
|
||||||
#menuIcon button img{
|
|
||||||
width: 14px;
|
|
||||||
padding-top: 0;
|
|
||||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
|
||||||
}
|
|
||||||
#menuIcon section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
@media only screen and (max-height: 700px) {
|
|
||||||
#menuIcon section {
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<main id="menuIcon" hidden>
|
|
||||||
<section>
|
|
||||||
<button id="openMenuButton">
|
|
||||||
<img src="/static/images/menu.svg">
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
81
front/dist/resources/html/gameQualityMenu.html
vendored
@ -1,81 +0,0 @@
|
|||||||
<style>
|
|
||||||
#gameQuality {
|
|
||||||
background: #eceeee;
|
|
||||||
border: 1px solid #42464b;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 20px auto 0;
|
|
||||||
width: 50vw;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
#gameQuality .cautiousText {
|
|
||||||
font-size: 50%;
|
|
||||||
}
|
|
||||||
#gameQuality h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#gameQuality select {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#gameQuality section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#gameQuality section.action{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#gameQuality button {
|
|
||||||
margin: 10px;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
}
|
|
||||||
#gameQuality button#gameQualityFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="gameQuality" hidden>
|
|
||||||
<section>
|
|
||||||
<h5>Game quality</h3>
|
|
||||||
<p class="cautiousText">(Editing these settings will restart the game)</p>
|
|
||||||
<select id="select-game-quality">
|
|
||||||
<option value="120">High video quality (120 fps)</option>
|
|
||||||
<option value="60">Medium video quality (60 fps, recommended)</option>
|
|
||||||
<option value="40">Minimum video quality (40 fps)</option>
|
|
||||||
<option value="20">Small video quality (20 fps)</option>
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h5>Video quality</h3>
|
|
||||||
<select id="select-video-quality">
|
|
||||||
<option value="30">High video quality (30 fps)</option>
|
|
||||||
<option value="20">Medium video quality (20 fps, recommended)</option>
|
|
||||||
<option value="10">Minimum video quality (10 fps)</option>
|
|
||||||
<option value="5">Small video quality (5 fps)</option>
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="gameQualityFormSubmit">Save</button>
|
|
||||||
<button type="reset" class="close" id="gameQualityFormCancel">Cancel</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
115
front/dist/resources/html/gameReport.html
vendored
@ -1,115 +0,0 @@
|
|||||||
<style>
|
|
||||||
#gameReport {
|
|
||||||
background: #eceeee;
|
|
||||||
border: 1px solid #42464b;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 2px auto 0;
|
|
||||||
width: 298px;
|
|
||||||
}
|
|
||||||
#gameReport h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#gameReport h3 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#gameReport textarea {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 100px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#gameReport section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#gameReport section.action{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#gameReport button {
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 60%;
|
|
||||||
background-color: #dc3545;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding: 3px 10px 3px 10px;
|
|
||||||
}
|
|
||||||
#gameReport button#gameReportFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
display: block;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
#gameReport section a{
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 0 6px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
#gameReport section h6,
|
|
||||||
#gameReport section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#gameReport section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#gameReport p{
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 3px 0 0 0;
|
|
||||||
}
|
|
||||||
#gameReport form p{
|
|
||||||
margin: 0px 70px;
|
|
||||||
}
|
|
||||||
#gameReport section p.err{
|
|
||||||
color: red;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#gameReport section p.info{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<main id="gameReport" hidden>
|
|
||||||
<section>
|
|
||||||
<button id="gameReportFormCancel">X</button>
|
|
||||||
<h1>Moderate <span id="nameReported"></span></h1>
|
|
||||||
<p id="askActionP">What action do you want to take?</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h3>Block: </h3>
|
|
||||||
<p>Block any communication from and to this user. This can be reverted.</p>
|
|
||||||
<section class="action">
|
|
||||||
<button id="toggleBlockButton">Block this user</button>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="reportSection">
|
|
||||||
<h3>Report: </h3>
|
|
||||||
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
|
|
||||||
<form>
|
|
||||||
<section>
|
|
||||||
<h6>Your message: </h6>
|
|
||||||
<textarea type="text" name="report" id="gameReportInput"></textarea>
|
|
||||||
<p class="err" id="gameReportErr"></p>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="gameReportFormSubmit">Report this user</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
96
front/dist/resources/html/gameShare.html
vendored
@ -1,96 +0,0 @@
|
|||||||
<style>
|
|
||||||
#gameShare {
|
|
||||||
background: #eceeee;
|
|
||||||
border: 1px solid #42464b;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 20px auto 0;
|
|
||||||
width: 50vw;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
#gameShare h1 {
|
|
||||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
|
||||||
border-bottom: 1px solid #a6abaf;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #727678;
|
|
||||||
display: block;
|
|
||||||
height: 43px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
|
||||||
}
|
|
||||||
#gameShare input {
|
|
||||||
font-size: 70%;
|
|
||||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
|
||||||
border: 1px solid #a1a3a3;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #696969;
|
|
||||||
height: 30px;
|
|
||||||
transition: box-shadow 0.3s;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
#gameShare section {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
#gameShare section.action{
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#gameShare button {
|
|
||||||
margin: 10px;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
border-radius: 7px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
#gameShare button#gameShareFormCancel {
|
|
||||||
background-color: #c7c7c700;
|
|
||||||
color: #292929;
|
|
||||||
}
|
|
||||||
#gameShare section a{
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
margin: 0 6px;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
#gameShare section h6,
|
|
||||||
#gameShare section h5{
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
#gameShare section.text-center{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
#gameShare section p{
|
|
||||||
font-size: 8px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
#gameShare section p.err{
|
|
||||||
color: red;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#gameShare section p.info{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#gameShare section input#gameShareLink{
|
|
||||||
background-color: #a1a3a3;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form id="gameShare" hidden>
|
|
||||||
<section class="text-center">
|
|
||||||
<h5>Share this link !</h5>
|
|
||||||
<p class="info" id="gameShareInfo"></p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h6>Link</h6>
|
|
||||||
<input type="text" name="email" id="gameShareLink" readonly>
|
|
||||||
</section>
|
|
||||||
<section class="action">
|
|
||||||
<button type="submit" id="gameShareFormSubmit">Copy</button>
|
|
||||||
<button type="submit" id="gameShareFormCancel">Close</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
BIN
front/dist/resources/logos/tcm_full.png
vendored
Normal file
After Width: | Height: | Size: 300 B |
BIN
front/dist/resources/logos/tcm_short.png
vendored
Normal file
After Width: | Height: | Size: 216 B |
7
front/dist/service-worker-prod.js
vendored
@ -1,4 +1,4 @@
|
|||||||
let CACHE_NAME = 'workavdenture-cache-v1.4.14';
|
let CACHE_NAME = 'workavdenture-cache';
|
||||||
let urlsToCache = [
|
let urlsToCache = [
|
||||||
'/'
|
'/'
|
||||||
];
|
];
|
||||||
@ -14,7 +14,8 @@ self.addEventListener('install', function(event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', function(event) {
|
self.addEventListener('fetch', function(event) {
|
||||||
event.respondWith(
|
//TODO mamnage fetch data and cache management
|
||||||
|
/*event.respondWith(
|
||||||
caches.match(event.request)
|
caches.match(event.request)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
// Cache hit - return response
|
// Cache hit - return response
|
||||||
@ -44,7 +45,7 @@ self.addEventListener('fetch', function(event) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);*/
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('wait', function(event) {
|
self.addEventListener('wait', function(event) {
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@ -15,7 +15,6 @@ import type { SetPropertyEvent } from "./setPropertyEvent";
|
|||||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||||
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
|
||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
import type { SetTilesEvent } from "./SetTilesEvent";
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./SetVariableEvent";
|
import type { SetVariableEvent } from "./SetVariableEvent";
|
||||||
@ -33,6 +32,7 @@ import type {
|
|||||||
TriggerActionMessageEvent,
|
TriggerActionMessageEvent,
|
||||||
} from "./ui/TriggerActionMessageEvent";
|
} from "./ui/TriggerActionMessageEvent";
|
||||||
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
||||||
|
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T;
|
data: T;
|
||||||
@ -63,7 +63,8 @@ export type IframeEventMap = {
|
|||||||
stopSound: null;
|
stopSound: null;
|
||||||
getState: undefined;
|
getState: undefined;
|
||||||
loadTileset: LoadTilesetEvent;
|
loadTileset: LoadTilesetEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent;
|
registerMenu: MenuRegisterEvent;
|
||||||
|
unregisterMenu: UnregisterMenuEvent;
|
||||||
setTiles: SetTilesEvent;
|
setTiles: SetTilesEvent;
|
||||||
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
|
||||||
|
|
||||||
export const isSetVariableEvent = new tg.IsInterface()
|
export const isSetVariableEvent = new tg.IsInterface()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import * as tg from "generic-type-guard";
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
|
|
||||||
export const isMenuItemRegisterEvent = new tg.IsInterface()
|
|
||||||
.withProperties({
|
|
||||||
menutItem: tg.isString,
|
|
||||||
})
|
|
||||||
.get();
|
|
||||||
/**
|
|
||||||
* A message sent from the iFrame to the game to add a new menu item.
|
|
||||||
*/
|
|
||||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
|
||||||
|
|
||||||
export const isMenuItemRegisterIframeEvent = new tg.IsInterface()
|
|
||||||
.withProperties({
|
|
||||||
type: tg.isSingletonString("registerMenuCommand"),
|
|
||||||
data: isMenuItemRegisterEvent,
|
|
||||||
})
|
|
||||||
.get();
|
|
||||||
|
|
||||||
const _registerMenuCommandStream: Subject<string> = new Subject();
|
|
||||||
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
|
||||||
|
|
||||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
|
||||||
_registerMenuCommandStream.next(event.menutItem);
|
|
||||||
}
|
|
31
front/src/Api/Events/ui/MenuRegisterEvent.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from a script to the game to remove a custom menu from the menu
|
||||||
|
*/
|
||||||
|
export const isUnregisterMenuEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type UnregisterMenuEvent = tg.GuardedType<typeof isUnregisterMenuEvent>;
|
||||||
|
|
||||||
|
export const isMenuRegisterOptions = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
allowApi: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from a script to the game to add a custom menu from the menu
|
||||||
|
*/
|
||||||
|
export const isMenuRegisterEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
iframe: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
|
options: isMenuRegisterOptions,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type MenuRegisterEvent = tg.GuardedType<typeof isMenuRegisterEvent>;
|
@ -29,11 +29,12 @@ import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"
|
|||||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent";
|
||||||
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
||||||
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
|
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
|
||||||
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
|
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
|
||||||
|
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
query: IframeQueryMap[T]["query"],
|
query: IframeQueryMap[T]["query"],
|
||||||
@ -93,12 +94,6 @@ class IframeListener {
|
|||||||
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
|
||||||
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
public readonly setPropertyStream = this._setPropertyStream.asObservable();
|
||||||
|
|
||||||
private readonly _registerMenuCommandStream: Subject<string> = new Subject();
|
|
||||||
public readonly registerMenuCommandStream = this._registerMenuCommandStream.asObservable();
|
|
||||||
|
|
||||||
private readonly _unregisterMenuCommandStream: Subject<string> = new Subject();
|
|
||||||
public readonly unregisterMenuCommandStream = this._unregisterMenuCommandStream.asObservable();
|
|
||||||
|
|
||||||
private readonly _playSoundStream: Subject<PlaySoundEvent> = new Subject();
|
private readonly _playSoundStream: Subject<PlaySoundEvent> = new Subject();
|
||||||
public readonly playSoundStream = this._playSoundStream.asObservable();
|
public readonly playSoundStream = this._playSoundStream.asObservable();
|
||||||
|
|
||||||
@ -260,17 +255,23 @@ class IframeListener {
|
|||||||
this._removeBubbleStream.next();
|
this._removeBubbleStream.next();
|
||||||
} else if (payload.type == "onPlayerMove") {
|
} else if (payload.type == "onPlayerMove") {
|
||||||
this.sendPlayerMove = true;
|
this.sendPlayerMove = true;
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
|
||||||
const data = payload.data.menutItem;
|
|
||||||
// @ts-ignore
|
|
||||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
|
||||||
this._unregisterMenuCommandStream.next(data);
|
|
||||||
});
|
|
||||||
handleMenuItemRegistrationEvent(payload.data);
|
|
||||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||||
this._setTilesStream.next(payload.data);
|
this._setTilesStream.next(payload.data);
|
||||||
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
||||||
this._modifyEmbeddedWebsiteStream.next(payload.data);
|
this._modifyEmbeddedWebsiteStream.next(payload.data);
|
||||||
|
} else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) {
|
||||||
|
const dataName = payload.data.name;
|
||||||
|
this.iframeCloseCallbacks.get(iframe)?.push(() => {
|
||||||
|
handleMenuUnregisterEvent(dataName);
|
||||||
|
});
|
||||||
|
handleMenuRegistrationEvent(
|
||||||
|
payload.data.name,
|
||||||
|
payload.data.iframe,
|
||||||
|
foundSrc,
|
||||||
|
payload.data.options
|
||||||
|
);
|
||||||
|
} else if (payload.type == "unregisterMenu" && isUnregisterMenuEvent(payload.data)) {
|
||||||
|
handleMenuUnregisterEvent(payload.data.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -293,57 +294,67 @@ class IframeListener {
|
|||||||
this.iframes.delete(iframe);
|
this.iframes.delete(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerScript(scriptUrl: string): void {
|
registerScript(scriptUrl: string): Promise<void> {
|
||||||
console.log("Loading map related script at ", scriptUrl);
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
console.log("Loading map related script at ", scriptUrl);
|
||||||
|
|
||||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||||
// Using external iframe mode (
|
// Using external iframe mode (
|
||||||
const iframe = document.createElement("iframe");
|
const iframe = document.createElement("iframe");
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
iframe.style.display = "none";
|
iframe.style.display = "none";
|
||||||
iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl);
|
iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl);
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.sandbox.add("allow-scripts");
|
iframe.sandbox.add("allow-scripts");
|
||||||
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
iframe.addEventListener("load", () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
this.scripts.set(scriptUrl, iframe);
|
document.body.prepend(iframe);
|
||||||
this.registerIframe(iframe);
|
|
||||||
} else {
|
|
||||||
// production code
|
|
||||||
const iframe = document.createElement("iframe");
|
|
||||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
|
||||||
iframe.style.display = "none";
|
|
||||||
|
|
||||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
this.scripts.set(scriptUrl, iframe);
|
||||||
iframe.sandbox.add("allow-scripts");
|
this.registerIframe(iframe);
|
||||||
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
} else {
|
||||||
|
// production code
|
||||||
|
const iframe = document.createElement("iframe");
|
||||||
|
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||||
|
iframe.style.display = "none";
|
||||||
|
|
||||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||||
iframe.srcdoc =
|
iframe.sandbox.add("allow-scripts");
|
||||||
"<!doctype html>\n" +
|
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||||
"\n" +
|
|
||||||
'<html lang="en">\n' +
|
|
||||||
"<head>\n" +
|
|
||||||
'<script src="' +
|
|
||||||
window.location.protocol +
|
|
||||||
"//" +
|
|
||||||
window.location.host +
|
|
||||||
'/iframe_api.js" ></script>\n' +
|
|
||||||
'<script src="' +
|
|
||||||
scriptUrl +
|
|
||||||
'" ></script>\n' +
|
|
||||||
"<title></title>\n" +
|
|
||||||
"</head>\n" +
|
|
||||||
"</html>\n";
|
|
||||||
|
|
||||||
document.body.prepend(iframe);
|
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||||
|
iframe.srcdoc =
|
||||||
|
"<!doctype html>\n" +
|
||||||
|
"\n" +
|
||||||
|
'<html lang="en">\n' +
|
||||||
|
"<head>\n" +
|
||||||
|
'<script src="' +
|
||||||
|
window.location.protocol +
|
||||||
|
"//" +
|
||||||
|
window.location.host +
|
||||||
|
'/iframe_api.js" ></script>\n' +
|
||||||
|
'<script src="' +
|
||||||
|
scriptUrl +
|
||||||
|
'" ></script>\n' +
|
||||||
|
"<title></title>\n" +
|
||||||
|
"</head>\n" +
|
||||||
|
"</html>\n";
|
||||||
|
|
||||||
this.scripts.set(scriptUrl, iframe);
|
iframe.addEventListener("load", () => {
|
||||||
this.registerIframe(iframe);
|
resolve();
|
||||||
}
|
});
|
||||||
|
|
||||||
|
document.body.prepend(iframe);
|
||||||
|
|
||||||
|
this.scripts.set(scriptUrl, iframe);
|
||||||
|
this.registerIframe(iframe);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBaseUrl(src: string, source: MessageEventSource | null): string {
|
private getBaseUrl(src: string, source: MessageEventSource | null): string {
|
||||||
|
@ -1,38 +1,35 @@
|
|||||||
import {sendToWorkadventure} from "../IframeApiContribution";
|
import { sendToWorkadventure } from "../IframeApiContribution";
|
||||||
import type {LoadSoundEvent} from "../../Events/LoadSoundEvent";
|
import type { LoadSoundEvent } from "../../Events/LoadSoundEvent";
|
||||||
import type {PlaySoundEvent} from "../../Events/PlaySoundEvent";
|
import type { PlaySoundEvent } from "../../Events/PlaySoundEvent";
|
||||||
import type {StopSoundEvent} from "../../Events/StopSoundEvent";
|
import type { StopSoundEvent } from "../../Events/StopSoundEvent";
|
||||||
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
||||||
|
|
||||||
export class Sound {
|
export class Sound {
|
||||||
constructor(private url: string) {
|
constructor(private url: string) {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'loadSound',
|
type: "loadSound",
|
||||||
"data": {
|
data: {
|
||||||
url: this.url,
|
url: this.url,
|
||||||
} as LoadSoundEvent
|
} as LoadSoundEvent,
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public play(config: SoundConfig) {
|
public play(config: SoundConfig | undefined) {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'playSound',
|
type: "playSound",
|
||||||
"data": {
|
data: {
|
||||||
url: this.url,
|
url: this.url,
|
||||||
config
|
config,
|
||||||
} as PlaySoundEvent
|
} as PlaySoundEvent,
|
||||||
|
|
||||||
});
|
});
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
public stop() {
|
public stop() {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
"type": 'stopSound',
|
type: "stopSound",
|
||||||
"data": {
|
data: {
|
||||||
url: this.url,
|
url: this.url,
|
||||||
} as StopSoundEvent
|
} as StopSoundEvent,
|
||||||
|
|
||||||
});
|
});
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
17
front/src/Api/iframe/Ui/Menu.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { sendToWorkadventure } from "../IframeApiContribution";
|
||||||
|
|
||||||
|
export class Menu {
|
||||||
|
constructor(private menuName: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove the menu
|
||||||
|
*/
|
||||||
|
public remove() {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "unregisterMenu",
|
||||||
|
data: {
|
||||||
|
name: this.menuName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,10 @@ export class WorkadventureStateCommands extends IframeApiContribution<Workadvent
|
|||||||
return variables.get(key);
|
return variables.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasVariable(key: string): boolean {
|
||||||
|
return variables.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
onVariableChange(key: string): Observable<unknown> {
|
onVariableChange(key: string): Observable<unknown> {
|
||||||
let subject = variableSubscribers.get(key);
|
let subject = variableSubscribers.get(key);
|
||||||
if (subject === undefined) {
|
if (subject === undefined) {
|
||||||
@ -85,6 +89,12 @@ const proxyCommand = new Proxy(new WorkadventureStateCommands(), {
|
|||||||
target.saveVariable(p.toString(), value);
|
target.saveVariable(p.toString(), value);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
||||||
|
if (p in target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return target.hasVariable(p.toString());
|
||||||
|
},
|
||||||
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
||||||
|
|
||||||
export default proxyCommand;
|
export default proxyCommand;
|
||||||
|
@ -6,6 +6,8 @@ import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescrip
|
|||||||
import { Popup } from "./Ui/Popup";
|
import { Popup } from "./Ui/Popup";
|
||||||
import { ActionMessage } from "./Ui/ActionMessage";
|
import { ActionMessage } from "./Ui/ActionMessage";
|
||||||
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
|
||||||
|
import { Menu } from "./Ui/Menu";
|
||||||
|
import type { RequireOnlyOne } from "../types";
|
||||||
|
|
||||||
let popupId = 0;
|
let popupId = 0;
|
||||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||||
@ -14,9 +16,18 @@ const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
|
|||||||
Map<number, ButtonClickedCallback>
|
Map<number, ButtonClickedCallback>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const menus: Map<string, Menu> = new Map<string, Menu>();
|
||||||
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
||||||
const actionMessages = new Map<string, ActionMessage>();
|
const actionMessages = new Map<string, ActionMessage>();
|
||||||
|
|
||||||
|
interface MenuDescriptor {
|
||||||
|
callback?: (commandDescriptor: string) => void;
|
||||||
|
iframe?: string;
|
||||||
|
allowApi?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuOptions = RequireOnlyOne<MenuDescriptor, "callback" | "iframe">;
|
||||||
|
|
||||||
interface ZonedPopupOptions {
|
interface ZonedPopupOptions {
|
||||||
zone: string;
|
zone: string;
|
||||||
objectLayerName?: string;
|
objectLayerName?: string;
|
||||||
@ -52,6 +63,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||||||
typeChecker: isMenuItemClickedEvent,
|
typeChecker: isMenuItemClickedEvent,
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
const callback = menuCallbacks.get(event.menuItem);
|
const callback = menuCallbacks.get(event.menuItem);
|
||||||
|
const menu = menus.get(event.menuItem);
|
||||||
|
if (menu === undefined) {
|
||||||
|
throw new Error('Could not find menu named "' + event.menuItem + '"');
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(event.menuItem);
|
callback(event.menuItem);
|
||||||
}
|
}
|
||||||
@ -104,14 +119,53 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu {
|
||||||
menuCallbacks.set(commandDescriptor, callback);
|
const menu = new Menu(commandDescriptor);
|
||||||
sendToWorkadventure({
|
|
||||||
type: "registerMenuCommand",
|
if (typeof options === "function") {
|
||||||
data: {
|
menuCallbacks.set(commandDescriptor, options);
|
||||||
menutItem: commandDescriptor,
|
sendToWorkadventure({
|
||||||
},
|
type: "registerMenu",
|
||||||
});
|
data: {
|
||||||
|
name: commandDescriptor,
|
||||||
|
options: {
|
||||||
|
allowApi: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.allowApi = options.allowApi === undefined ? options.iframe !== undefined : options.allowApi;
|
||||||
|
|
||||||
|
if (options.iframe !== undefined) {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "registerMenu",
|
||||||
|
data: {
|
||||||
|
name: commandDescriptor,
|
||||||
|
iframe: options.iframe,
|
||||||
|
options: {
|
||||||
|
allowApi: options.allowApi,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (options.callback !== undefined) {
|
||||||
|
menuCallbacks.set(commandDescriptor, options.callback);
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "registerMenu",
|
||||||
|
data: {
|
||||||
|
name: commandDescriptor,
|
||||||
|
options: {
|
||||||
|
allowApi: options.allowApi,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"When adding a menu with WA.ui.registerMenuCommand, you must pass either an iframe or a callback"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menus.set(commandDescriptor, menu);
|
||||||
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBubble(): void {
|
displayBubble(): void {
|
||||||
|
4
front/src/Api/types.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type RequireOnlyOne<T, keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, keys>> &
|
||||||
|
{
|
||||||
|
[K in keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<keys, K>, undefined>>;
|
||||||
|
}[keys];
|
@ -1,4 +1,6 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||||
|
import {menuIconVisiblilityStore, menuVisiblilityStore} from "../Stores/MenuStore";
|
||||||
import {enableCameraSceneVisibilityStore} from "../Stores/MediaStore";
|
import {enableCameraSceneVisibilityStore} from "../Stores/MediaStore";
|
||||||
import CameraControls from "./CameraControls.svelte";
|
import CameraControls from "./CameraControls.svelte";
|
||||||
import MyCamera from "./MyCamera.svelte";
|
import MyCamera from "./MyCamera.svelte";
|
||||||
@ -23,10 +25,9 @@
|
|||||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
|
import Menu from "./Menu/Menu.svelte";
|
||||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
||||||
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
|
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
|
||||||
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
|
||||||
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
|
||||||
import AdminMessage from "./TypeMessage/BanMessage.svelte";
|
import AdminMessage from "./TypeMessage/BanMessage.svelte";
|
||||||
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
import TextMessage from "./TypeMessage/TextMessage.svelte";
|
||||||
import {banMessageVisibleStore} from "../Stores/TypeMessageStore/BanMessageStore";
|
import {banMessageVisibleStore} from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
@ -37,6 +38,8 @@
|
|||||||
import LayoutManager from "./LayoutManager/LayoutManager.svelte";
|
import LayoutManager from "./LayoutManager/LayoutManager.svelte";
|
||||||
import {audioManagerVisibilityStore} from "../Stores/AudioManagerStore";
|
import {audioManagerVisibilityStore} from "../Stores/AudioManagerStore";
|
||||||
import AudioManager from "./AudioManager/AudioManager.svelte"
|
import AudioManager from "./AudioManager/AudioManager.svelte"
|
||||||
|
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||||
|
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -93,6 +96,21 @@
|
|||||||
<LayoutManager></LayoutManager>
|
<LayoutManager></LayoutManager>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $showReportScreenStore !== userReportEmpty}
|
||||||
|
<div>
|
||||||
|
<ReportMenu></ReportMenu>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $menuIconVisiblilityStore}
|
||||||
|
<div>
|
||||||
|
<MenuIcon></MenuIcon>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if $menuVisiblilityStore}
|
||||||
|
<div>
|
||||||
|
<Menu></Menu>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if $gameOverlayVisibilityStore}
|
{#if $gameOverlayVisibilityStore}
|
||||||
<div>
|
<div>
|
||||||
<VideoOverlay></VideoOverlay>
|
<VideoOverlay></VideoOverlay>
|
||||||
@ -100,11 +118,6 @@
|
|||||||
<CameraControls></CameraControls>
|
<CameraControls></CameraControls>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $consoleGlobalMessageManagerVisibleStore}
|
|
||||||
<div>
|
|
||||||
<ConsoleGlobalMessageManager></ConsoleGlobalMessageManager>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $helpCameraSettingsVisibleStore}
|
{#if $helpCameraSettingsVisibleStore}
|
||||||
<div>
|
<div>
|
||||||
<HelpCameraSettingsPopup></HelpCameraSettingsPopup>
|
<HelpCameraSettingsPopup></HelpCameraSettingsPopup>
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
<script lang="typescript">
|
|
||||||
import { fly } from 'svelte/transition';
|
|
||||||
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
|
|
||||||
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
|
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
|
||||||
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
|
||||||
|
|
||||||
let inputSendTextActive = true;
|
|
||||||
let uploadMusicActive = false;
|
|
||||||
let handleSendText: { sendTextMessage(broadcast: boolean): void };
|
|
||||||
let handleSendAudio: { sendAudioMessage(broadcast: boolean): Promise<void> };
|
|
||||||
let broadcastToWorld = false;
|
|
||||||
|
|
||||||
function closeConsoleGlobalMessage() {
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDown(e:KeyboardEvent) {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
closeConsoleGlobalMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputSendTextActivate() {
|
|
||||||
inputSendTextActive = true;
|
|
||||||
uploadMusicActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputUploadMusicActivate() {
|
|
||||||
uploadMusicActive = true;
|
|
||||||
inputSendTextActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function send() {
|
|
||||||
if (inputSendTextActive) {
|
|
||||||
handleSendText.sendTextMessage(broadcastToWorld);
|
|
||||||
}
|
|
||||||
if (uploadMusicActive) {
|
|
||||||
handleSendAudio.sendAudioMessage(broadcastToWorld);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown}/>
|
|
||||||
|
|
||||||
<div class="console-global-message">
|
|
||||||
<div class="menu-console-global-message nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
|
|
||||||
<button type="button" class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
|
|
||||||
<button type="button" class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
|
|
||||||
</div>
|
|
||||||
<div class="main-console-global-message nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
|
||||||
<div class="title-console-global-message">
|
|
||||||
<h2>Global Message</h2>
|
|
||||||
<button type="button" class="nes-btn is-error" on:click|preventDefault={closeConsoleGlobalMessage}><i class="nes-icon close is-small"></i></button>
|
|
||||||
</div>
|
|
||||||
<div class="content-console-global-message">
|
|
||||||
{#if inputSendTextActive}
|
|
||||||
<InputTextGlobalMessage gameManager={gameManager} bind:handleSending={handleSendText}/>
|
|
||||||
{/if}
|
|
||||||
{#if uploadMusicActive}
|
|
||||||
<UploadAudioGlobalMessage gameManager={gameManager} bind:handleSending={handleSendAudio}/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="footer-console-global-message">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld}>
|
|
||||||
<span>Broadcast to all rooms of the world</span>
|
|
||||||
</label>
|
|
||||||
<button class="nes-btn is-primary" on:click|preventDefault={send}>Send</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
.nes-container {
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.console-global-message {
|
|
||||||
top: 20vh;
|
|
||||||
width: 50vw;
|
|
||||||
height: 50vh;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
div.menu-console-global-message {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
max-width: 180px;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
background-color: #333333;
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 136px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.main-console-global-message {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
background-color: #333333;
|
|
||||||
|
|
||||||
div.title-console-global-message {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
height: 50px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
color: whitesmoke;
|
|
||||||
|
|
||||||
.nes-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content-console-global-message {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
max-height: calc(100% - 120px);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer-console-global-message {
|
|
||||||
height: 50px;
|
|
||||||
margin-top: 10px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
max-width: 30%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
147
front/src/Components/Menu/AboutRoomSubMenu.svelte
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
|
let gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
|
let HTMLShareLink: HTMLInputElement;
|
||||||
|
let expandedMapCopyright = false;
|
||||||
|
let expandedTilesetCopyright = false;
|
||||||
|
|
||||||
|
let mapName: string = "";
|
||||||
|
let mapDescription: string = "";
|
||||||
|
let mapCopyright: string = "The map creator did not declare a copyright for the map.";
|
||||||
|
let tilesetCopyright: string[] = [];
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (gameScene.mapFile.properties !== undefined) {
|
||||||
|
const propertyName = gameScene.mapFile.properties.find((property) => property.name === 'mapName')
|
||||||
|
if ( propertyName !== undefined && typeof propertyName.value === 'string') {
|
||||||
|
mapName = propertyName.value;
|
||||||
|
}
|
||||||
|
const propertyDescription = gameScene.mapFile.properties.find((property) => property.name === 'mapDescription')
|
||||||
|
if (propertyDescription !== undefined && typeof propertyDescription.value === 'string') {
|
||||||
|
mapDescription = propertyDescription.value;
|
||||||
|
}
|
||||||
|
const propertyCopyright = gameScene.mapFile.properties.find((property) => property.name === 'mapCopyright')
|
||||||
|
if (propertyCopyright !== undefined && typeof propertyCopyright.value === 'string') {
|
||||||
|
mapCopyright = propertyCopyright.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tileset of gameScene.mapFile.tilesets) {
|
||||||
|
if (tileset.properties !== undefined) {
|
||||||
|
const propertyTilesetCopyright = tileset.properties.find((property) => property.name === 'tilesetCopyright')
|
||||||
|
if (propertyTilesetCopyright !== undefined && typeof propertyTilesetCopyright.value === 'string') {
|
||||||
|
tilesetCopyright = [...tilesetCopyright, propertyTilesetCopyright.value]; //Assignment needed to trigger Svelte's reactivity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function copyLink() {
|
||||||
|
HTMLShareLink.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareLink() {
|
||||||
|
const shareData = {url: location.toString()};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.share(shareData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="about-room-main">
|
||||||
|
<section class="share-url not-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<input type="text" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||||
|
</section>
|
||||||
|
<section class="is-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||||
|
</section>
|
||||||
|
<h2>Information on the map</h2>
|
||||||
|
<section class="container-overflow">
|
||||||
|
<h3>{mapName}</h3>
|
||||||
|
<p class="string-HTML">{mapDescription}</p>
|
||||||
|
<h3 class="nes-pointer hoverable" on:click={() => expandedMapCopyright = !expandedMapCopyright}>Copyrights of the map</h3>
|
||||||
|
<p class="string-HTML" hidden="{!expandedMapCopyright}">{mapCopyright}</p>
|
||||||
|
<h3 class="nes-pointer hoverable" on:click={() => expandedTilesetCopyright = !expandedTilesetCopyright}>Copyrights of the tilesets</h3>
|
||||||
|
<section hidden="{!expandedTilesetCopyright}">
|
||||||
|
{#each tilesetCopyright as copyright}
|
||||||
|
<p class="string-HTML">{copyright}</p>
|
||||||
|
{:else}
|
||||||
|
<p>The map creator did not declare a copyright for the tilesets. Warning, This doesn't mean that those tilesets have no license.</p>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.string-HTML{
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.about-room-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
section.share-url {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 85%;
|
||||||
|
border-radius: 32px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
input::selection {
|
||||||
|
background-color: #209cee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3.hoverable:hover {
|
||||||
|
background-color: #3c3e40;
|
||||||
|
border-radius: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: calc(100% - 220px);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||||
|
div.about-room-main {
|
||||||
|
section.share-url.not-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: calc(100% - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,17 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import type { GameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
|
||||||
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import uploadFile from "../images/music-file.svg";
|
import uploadFile from "../images/music-file.svg";
|
||||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
|
|
||||||
interface EventTargetFiles extends EventTarget {
|
interface EventTargetFiles extends EventTarget {
|
||||||
files: Array<File>;
|
files: Array<File>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let gameManager: GameManager;
|
|
||||||
|
|
||||||
let gameScene = gameManager.getCurrentGameScene();
|
let gameScene = gameManager.getCurrentGameScene();
|
||||||
let fileInput: HTMLInputElement;
|
let fileInput: HTMLInputElement;
|
||||||
let fileName: string;
|
let fileName: string;
|
||||||
@ -43,7 +40,6 @@
|
|||||||
}
|
}
|
||||||
inputAudio.value = '';
|
inputAudio.value = '';
|
||||||
gameScene.connection?.emitGlobalMessage(audioGlobalMessage);
|
gameScene.connection?.emitGlobalMessage(audioGlobalMessage);
|
||||||
disableConsole();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +70,6 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableConsole() {
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
|
||||||
consoleGlobalMessageManagerFocusStore.set(false);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@ -103,24 +94,17 @@
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
flex: 1 1 auto;
|
|
||||||
|
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
&.err {
|
&.err {
|
||||||
color: #ce372b;
|
color: #ce372b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
15
front/src/Components/Menu/ContactSubMenu.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {CONTACT_URL} from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<iframe title="contact" src="{CONTACT_URL}"></iframe>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
51
front/src/Components/Menu/CreateMapSubMenu.svelte
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
function goToGettingStarted() {
|
||||||
|
const sparkHost = "https://workadventu.re/getting-started";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToBuildingMap() {
|
||||||
|
const sparkHost = "https://workadventu.re/map-building";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="create-map-main">
|
||||||
|
<section class="container-overflow">
|
||||||
|
<section>
|
||||||
|
<h3>Getting started</h3>
|
||||||
|
<p>
|
||||||
|
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
||||||
|
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
||||||
|
</p>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Create your map</h3>
|
||||||
|
<p>You can also create your own custom map by following the step of the documentation.</p>
|
||||||
|
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.create-map-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
33
front/src/Components/Menu/CustomSubMenu.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import {iframeListener} from "../../Api/IframeListener";
|
||||||
|
|
||||||
|
export let url: string;
|
||||||
|
export let allowApi: boolean;
|
||||||
|
|
||||||
|
let HTMLIframe: HTMLIFrameElement;
|
||||||
|
|
||||||
|
onMount( () => {
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.registerIframe(HTMLIframe);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy( () => {
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.unregisterIframe(HTMLIframe);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<iframe title="customSubMenu" src="{url}" bind:this={HTMLIframe}></iframe>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
118
front/src/Components/Menu/GlobalMessagesSubMenu.svelte
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import TextGlobalMessage from './TextGlobalMessage.svelte';
|
||||||
|
import AudioGlobalMessage from './AudioGlobalMessage.svelte';
|
||||||
|
|
||||||
|
let handleSendText: { sendTextMessage(broadcast: boolean): void };
|
||||||
|
let handleSendAudio: { sendAudioMessage(broadcast: boolean): Promise<void> };
|
||||||
|
|
||||||
|
let inputSendTextActive = true;
|
||||||
|
let uploadAudioActive = !inputSendTextActive;
|
||||||
|
let broadcastToWorld = false;
|
||||||
|
|
||||||
|
function activateInputText() {
|
||||||
|
inputSendTextActive = true;
|
||||||
|
uploadAudioActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateUploadAudio() {
|
||||||
|
inputSendTextActive = false;
|
||||||
|
uploadAudioActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function send() {
|
||||||
|
if (inputSendTextActive) {
|
||||||
|
handleSendText.sendTextMessage(broadcastToWorld);
|
||||||
|
}
|
||||||
|
if (uploadAudioActive) {
|
||||||
|
handleSendAudio.sendAudioMessage(broadcastToWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="global-message-main">
|
||||||
|
<div class="global-message-subOptions">
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={activateInputText}>Text</button>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn {uploadAudioActive ? 'is-disabled' : ''}" on:click|preventDefault={activateUploadAudio}>Audio</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="global-message-content">
|
||||||
|
{#if inputSendTextActive}
|
||||||
|
<TextGlobalMessage bind:handleSending={handleSendText}/>
|
||||||
|
{/if}
|
||||||
|
{#if uploadAudioActive}
|
||||||
|
<AudioGlobalMessage bind:handleSending={handleSendAudio}/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="global-message-footer">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld}>
|
||||||
|
<span>Broadcast to all rooms of the world</span>
|
||||||
|
</label>
|
||||||
|
<section>
|
||||||
|
<button class="nes-btn is-primary" on:click|preventDefault={send}>Send</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.global-message-main {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 15% 65% 20%;
|
||||||
|
|
||||||
|
div.global-message-subOptions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.global-message-footer {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||||
|
.global-message-content {
|
||||||
|
height: calc(100% - 5px);
|
||||||
|
}
|
||||||
|
.global-message-footer {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
154
front/src/Components/Menu/Menu.svelte
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {fly} from "svelte/transition";
|
||||||
|
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||||
|
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||||
|
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
|
||||||
|
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||||
|
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||||
|
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||||
|
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||||
|
import {customMenuIframe, menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore";
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
import type {Unsubscriber} from "svelte/store";
|
||||||
|
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||||
|
|
||||||
|
let activeSubMenu: string = SubMenusInterface.settings;
|
||||||
|
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
||||||
|
let props: { url: string, allowApi: boolean };
|
||||||
|
let unsubscriberSubMenuStore: Unsubscriber;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||||
|
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||||
|
switchMenu(SubMenusInterface.settings);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
switchMenu(SubMenusInterface.settings);
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if(unsubscriberSubMenuStore) {
|
||||||
|
unsubscriberSubMenuStore();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function switchMenu(menu: string) {
|
||||||
|
if (get(subMenusStore).find((subMenu) => subMenu === menu)) {
|
||||||
|
activeSubMenu = menu;
|
||||||
|
switch (menu) {
|
||||||
|
case SubMenusInterface.settings:
|
||||||
|
activeComponent = SettingsSubMenu;
|
||||||
|
break;
|
||||||
|
case SubMenusInterface.profile:
|
||||||
|
activeComponent = ProfileSubMenu;
|
||||||
|
break;
|
||||||
|
case SubMenusInterface.createMap:
|
||||||
|
activeComponent = CreateMapSubMenu;
|
||||||
|
break;
|
||||||
|
case SubMenusInterface.aboutRoom:
|
||||||
|
activeComponent = AboutRoomSubMenu;
|
||||||
|
break;
|
||||||
|
case SubMenusInterface.globalMessages:
|
||||||
|
activeComponent = GlobalMessageSubMenu;
|
||||||
|
break;
|
||||||
|
case SubMenusInterface.contact:
|
||||||
|
activeComponent = ContactSubMenu;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
const customMenu = customMenuIframe.get(menu);
|
||||||
|
if (customMenu !== undefined) {
|
||||||
|
props = { url: customMenu.url, allowApi: customMenu.allowApi };
|
||||||
|
activeComponent = CustomSubMenu;
|
||||||
|
} else {
|
||||||
|
sendMenuClickedEvent(menu);
|
||||||
|
menuVisiblilityStore.set(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else throw ("There is no menu called " + menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
menuVisiblilityStore.set(false);
|
||||||
|
}
|
||||||
|
function onKeyDown(e:KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="menu-container-main">
|
||||||
|
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
|
||||||
|
<h2>Menu</h2>
|
||||||
|
<nav>
|
||||||
|
{#each $subMenusStore as submenu}
|
||||||
|
<button type="button" class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}" on:click|preventDefault={() => switchMenu(submenu)}>
|
||||||
|
{submenu}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="menu-submenu-container nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
||||||
|
<h2>{activeSubMenu}</h2>
|
||||||
|
<svelte:component this={activeComponent} {...props}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.nes-container {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu-container-main {
|
||||||
|
--size-first-columns-grid: 200px;
|
||||||
|
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
pointer-events: auto;
|
||||||
|
height: 80vh;
|
||||||
|
width: 75vw;
|
||||||
|
top: 10vh;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
||||||
|
grid-template-rows: 100%;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu-nav-sidebar {
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
nav button {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu-submenu-container {
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
div.menu-container-main {
|
||||||
|
--size-first-columns-grid: 120px;
|
||||||
|
height: 70vh;
|
||||||
|
top: 55px;
|
||||||
|
width: 100vw;
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,33 +1,40 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import logoWA from "../images/logo-WA-min.png"
|
||||||
|
import {menuVisiblilityStore} from "../../Stores/MenuStore";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
|
||||||
|
function showMenu(){
|
||||||
|
menuVisiblilityStore.set(!get(menuVisiblilityStore))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Tab") {
|
||||||
|
showMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
<main class="menuIcon">
|
<main class="menuIcon">
|
||||||
<section>
|
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu}>
|
||||||
<button>
|
|
||||||
<img src="/static/images/menu.svg" alt="Open menu">
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.menuIcon button {
|
.menuIcon {
|
||||||
background-color: black;
|
pointer-events: auto;
|
||||||
color: white;
|
margin: 25px;
|
||||||
border-radius: 7px;
|
img {
|
||||||
padding: 2px 8px;
|
width: 60px;
|
||||||
img {
|
padding-top: 0;
|
||||||
width: 14px;
|
|
||||||
padding-top: 0;
|
|
||||||
/*cursor: url('/resources/logos/cursor_pointer.png'), pointer;*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.menuIcon section {
|
}
|
||||||
margin: 10px;
|
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||||
}
|
.menuIcon {
|
||||||
@media only screen and (max-height: 700px) {
|
margin: 3px;
|
||||||
.menuIcon section {
|
img {
|
||||||
margin: 2px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
78
front/src/Components/Menu/ProfileSubMenu.svelte
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||||
|
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||||
|
import {menuIconVisiblilityStore, menuVisiblilityStore} from "../../Stores/MenuStore";
|
||||||
|
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
|
||||||
|
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||||
|
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
|
||||||
|
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
||||||
|
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||||
|
//import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
|
|
||||||
|
|
||||||
|
function disableMenuStores(){
|
||||||
|
menuVisiblilityStore.set(false);
|
||||||
|
menuIconVisiblilityStore.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditCompanionScene(){
|
||||||
|
disableMenuStores();
|
||||||
|
selectCompanionSceneVisibleStore.set(true);
|
||||||
|
gameManager.leaveGame(SelectCompanionSceneName,new SelectCompanionScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditNameScene(){
|
||||||
|
disableMenuStores();
|
||||||
|
loginSceneVisibleStore.set(true);
|
||||||
|
gameManager.leaveGame(LoginSceneName,new LoginScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditSkinScene(){
|
||||||
|
disableMenuStores();
|
||||||
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
|
gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Uncomment when login will be completely developed
|
||||||
|
/*function clickLogin() {
|
||||||
|
connectionManager.loadOpenIDScreen();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="customize-main">
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn is-rounded" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||||
|
</section>
|
||||||
|
<!-- <section>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click|preventDefault={clickLogin}>Login</button>
|
||||||
|
</section>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.customize-main{
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 50px;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
div.customize-main section button {
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
140
front/src/Components/Menu/SettingsSubMenu.svelte
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||||
|
import {videoConstraintStore} from "../../Stores/MediaStore";
|
||||||
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
|
import {isMobile} from "../../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
|
let fullscreen : boolean = localUserStore.getFullscreen();
|
||||||
|
let notification : boolean = localUserStore.getNotification() === 'granted';
|
||||||
|
let valueGame : number = localUserStore.getGameQualityValue();
|
||||||
|
let valueVideo : number = localUserStore.getVideoQualityValue();
|
||||||
|
let previewValueGame = valueGame;
|
||||||
|
let previewValueVideo = valueVideo;
|
||||||
|
|
||||||
|
function saveSetting(){
|
||||||
|
if (valueGame !== previewValueGame) {
|
||||||
|
previewValueGame = valueGame;
|
||||||
|
localUserStore.setGameQualityValue(valueGame);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueVideo !== previewValueVideo) {
|
||||||
|
previewValueVideo = valueVideo;
|
||||||
|
videoConstraintStore.setFrameRate(valueVideo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeFullscreen() {
|
||||||
|
const body = HtmlUtils.querySelectorOrFail('body');
|
||||||
|
if (body) {
|
||||||
|
if (document.fullscreenElement !== null && !fullscreen) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
} else {
|
||||||
|
body.requestFullscreen();
|
||||||
|
}
|
||||||
|
localUserStore.setFullscreen(fullscreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeNotification() {
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
localUserStore.setNotification(notification ? 'granted' : 'denied');
|
||||||
|
} else {
|
||||||
|
Notification.requestPermission().then((response) => {
|
||||||
|
if (response === 'granted') {
|
||||||
|
localUserStore.setNotification(notification ? 'granted' : 'denied');
|
||||||
|
} else {
|
||||||
|
localUserStore.setNotification('denied');
|
||||||
|
notification = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
||||||
|
<section>
|
||||||
|
<h3>Game quality</h3>
|
||||||
|
<div class="nes-select is-dark">
|
||||||
|
<select bind:value={valueGame}>
|
||||||
|
<option value="{120}">{isMobile() ? 'High (120 fps)' : 'High video quality (120 fps)'}</option>
|
||||||
|
<option value="{60}">{isMobile() ? 'Medium (60 fps)' : 'Medium video quality (60 fps, recommended)'}</option>
|
||||||
|
<option value="{40}">{isMobile() ? 'Minimum (40 fps)' : 'Minimum video quality (40 fps)'}</option>
|
||||||
|
<option value="{20}">{isMobile() ? 'Small (20 fps)' : 'Small video quality (20 fps)'}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>Video quality</h3>
|
||||||
|
<div class="nes-select is-dark">
|
||||||
|
<select bind:value={valueVideo}>
|
||||||
|
<option value="{30}">{isMobile() ? 'High (30 fps)' : 'High video quality (30 fps)'}</option>
|
||||||
|
<option value="{20}">{isMobile() ? 'Medium (20 fps)' : 'Medium video quality (20 fps, recommended)'}</option>
|
||||||
|
<option value="{10}">{isMobile() ? 'Minimum (10 fps)' : 'Minimum video quality (10 fps)'}</option>
|
||||||
|
<option value="{5}">{isMobile() ? 'Small (5 fps)' : 'Small video quality (5 fps)'}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="settings-section-save">
|
||||||
|
<p>(Saving these settings will restart the game)</p>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click|preventDefault={saveSetting}>Save</button>
|
||||||
|
</section>
|
||||||
|
<section class="settings-section-noSaveOption">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={fullscreen} on:change={changeFullscreen}/>
|
||||||
|
<span>Fullscreen</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={notification} on:change={changeNotification}>
|
||||||
|
<span>Notifications</span>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.settings-main {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
|
||||||
|
section {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px 20px 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
div.nes-select select:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section.settings-section-save {
|
||||||
|
text-align: center;
|
||||||
|
p {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section.settings-section-noSaveOption {
|
||||||
|
--nb-noSaveOptions: 2; //number of sub-element in the section
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: calc(100% / var(--nb-noSaveOptions)) calc(100% / var(--nb-noSaveOptions)); //Same size for every sub-element
|
||||||
|
|
||||||
|
label {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||||
|
div.settings-main {
|
||||||
|
section {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.settings-section-noSaveOption {
|
||||||
|
height: 80px;
|
||||||
|
grid-template-columns: none;
|
||||||
|
grid-template-rows: calc(100% / var(--nb-noSaveOptions)) calc(100% / var(--nb-noSaveOptions)); //Same size for every sub-element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
import { menuInputFocusStore } from "../../Stores/MenuStore";
|
||||||
import {onDestroy, onMount} from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import type { GameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import type { Quill } from "quill";
|
import type { Quill } from "quill";
|
||||||
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
@ -24,19 +24,15 @@
|
|||||||
[{'font': []}],
|
[{'font': []}],
|
||||||
[{'align': []}],
|
[{'align': []}],
|
||||||
|
|
||||||
['clean'],
|
['clean'], // remove formatting button
|
||||||
|
|
||||||
['link', 'image', 'video']
|
['link', 'image', 'video']
|
||||||
// remove formatting button
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export let gameManager: GameManager;
|
|
||||||
|
|
||||||
const gameScene = gameManager.getCurrentGameScene();
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
let quill: Quill;
|
|
||||||
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
|
|
||||||
|
|
||||||
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||||
|
let quill: Quill;
|
||||||
|
let QUILL_EDITOR: HTMLDivElement;
|
||||||
|
|
||||||
export const handleSending = {
|
export const handleSending = {
|
||||||
sendTextMessage(broadcastToWorld: boolean) {
|
sendTextMessage(broadcastToWorld: boolean) {
|
||||||
@ -53,39 +49,31 @@
|
|||||||
|
|
||||||
quill.deleteText(0, quill.getLength());
|
quill.deleteText(0, quill.getLength());
|
||||||
gameScene.connection?.emitGlobalMessage(textGlobalMessage);
|
gameScene.connection?.emitGlobalMessage(textGlobalMessage);
|
||||||
disableConsole();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Quill
|
//Quill
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
||||||
// Import quill
|
// Import quill
|
||||||
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
|
quill = new Quill(QUILL_EDITOR, {
|
||||||
placeholder: 'Enter your message here...',
|
placeholder: 'Enter your message here...',
|
||||||
theme: 'snow',
|
theme: 'snow',
|
||||||
modules: {
|
modules: {
|
||||||
toolbar: toolbarOptions
|
toolbar: toolbarOptions
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
menuInputFocusStore.set(true);
|
||||||
consoleGlobalMessageManagerFocusStore.set(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
consoleGlobalMessageManagerFocusStore.set(false);
|
menuInputFocusStore.set(false);
|
||||||
})
|
})
|
||||||
|
|
||||||
function disableConsole() {
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
|
||||||
consoleGlobalMessageManagerFocusStore.set(false);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="section-input-send-text">
|
<section class="section-input-send-text">
|
||||||
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
|
<div class="input-send-text" bind:this={QUILL_EDITOR}></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -1,27 +1,11 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import {obtainedMediaConstraintStore} from "../Stores/MediaStore";
|
||||||
import {localStreamStore} from "../Stores/MediaStore";
|
import {localStreamStore} from "../Stores/MediaStore";
|
||||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||||
import {onDestroy} from "svelte";
|
import {onDestroy} from "svelte";
|
||||||
|
import {srcObject} from "./Video/utils";
|
||||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
|
||||||
node.srcObject = stream;
|
|
||||||
return {
|
|
||||||
update(newStream: MediaStream) {
|
|
||||||
if (node.srcObject != newStream) {
|
|
||||||
node.srcObject = newStream
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream : MediaStream|null;
|
let stream : MediaStream|null;
|
||||||
/*$: {
|
|
||||||
if ($localStreamStore.type === 'success') {
|
|
||||||
stream = $localStreamStore.stream;
|
|
||||||
} else {
|
|
||||||
stream = null;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
const unsubscribe = localStreamStore.subscribe(value => {
|
const unsubscribe = localStreamStore.subscribe(value => {
|
||||||
if (value.type === 'success') {
|
if (value.type === 'success') {
|
||||||
@ -37,9 +21,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
|
<div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video}>
|
||||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream }
|
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||||
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline></video>
|
||||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
44
front/src/Components/ReportMenu/BlockSubMenu.svelte
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {blackListManager} from "../../WebRtc/BlackListManager";
|
||||||
|
import {showReportScreenStore, userReportEmpty} from "../../Stores/ShowReportScreenStore";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
|
export let userUUID: string | undefined;
|
||||||
|
export let userName: string;
|
||||||
|
let userIsBlocked = false;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (userUUID === undefined) {
|
||||||
|
userIsBlocked = false;
|
||||||
|
console.error("There is no user to block");
|
||||||
|
} else {
|
||||||
|
userIsBlocked = blackListManager.isBlackListed(userUUID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function blockUser(): void {
|
||||||
|
if (userUUID === undefined) {
|
||||||
|
console.error("There is no user to block");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blackListManager.isBlackListed(userUUID)
|
||||||
|
? blackListManager.cancelBlackList(userUUID)
|
||||||
|
: blackListManager.blackList(userUUID);
|
||||||
|
showReportScreenStore.set(userReportEmpty); //close the report menu
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="block-container">
|
||||||
|
<h3>Block</h3>
|
||||||
|
<p>Block any communication from and to {userName}. This can be reverted.</p>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={blockUser}>
|
||||||
|
{userIsBlocked ? 'Unblock this user' : 'Block this user'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.block-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
141
front/src/Components/ReportMenu/ReportMenu.svelte
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {showReportScreenStore, userReportEmpty} from "../../Stores/ShowReportScreenStore";
|
||||||
|
import BlockSubMenu from "./BlockSubMenu.svelte";
|
||||||
|
import ReportSubMenu from "./ReportSubMenu.svelte";
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import type {Unsubscriber} from "svelte/store";
|
||||||
|
import {playersStore} from "../../Stores/PlayersStore";
|
||||||
|
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||||
|
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||||
|
import {get} from "svelte/store";
|
||||||
|
|
||||||
|
let blockActive = true;
|
||||||
|
let reportActive = !blockActive;
|
||||||
|
let anonymous: boolean = false;
|
||||||
|
let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid;
|
||||||
|
let userName = "No name";
|
||||||
|
let unsubscriber: Unsubscriber
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
unsubscriber = showReportScreenStore.subscribe((reportScreenStore) => {
|
||||||
|
if (reportScreenStore != null) {
|
||||||
|
userName = reportScreenStore.userName;
|
||||||
|
userUUID = playersStore.getPlayerById(reportScreenStore.userId)?.userUuid;
|
||||||
|
if (userUUID === undefined && reportScreenStore !== userReportEmpty) {
|
||||||
|
console.error("Could not find UUID for user with ID " + reportScreenStore.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
anonymous = connectionManager.getConnexionType === GameConnexionTypes.anonymous;
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (unsubscriber) {
|
||||||
|
unsubscriber();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
showReportScreenStore.set(userReportEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateBlock() {
|
||||||
|
blockActive = true;
|
||||||
|
reportActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateReport() {
|
||||||
|
blockActive = false;
|
||||||
|
reportActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e:KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown}/>
|
||||||
|
|
||||||
|
<div class="report-menu-main nes-container is-rounded">
|
||||||
|
<section class="report-menu-title">
|
||||||
|
<h2>Moderate {userName}</h2>
|
||||||
|
<section class="justify-center">
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="report-menu-action {anonymous ? 'hidden' : ''}">
|
||||||
|
<section class="justify-center">
|
||||||
|
<button type="button" class="nes-btn {blockActive ? 'is-disabled' : ''}" on:click|preventDefault={activateBlock}>Block</button>
|
||||||
|
</section>
|
||||||
|
<section class="justify-center">
|
||||||
|
<button type="button" class="nes-btn {reportActive ? 'is-disabled' : ''}" on:click|preventDefault={activateReport}>Report</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="report-menu-content">
|
||||||
|
{#if blockActive}
|
||||||
|
<BlockSubMenu userUUID="{userUUID}" userName="{userName}"/>
|
||||||
|
{:else if reportActive}
|
||||||
|
<ReportSubMenu userUUID="{userUUID}"/>
|
||||||
|
{:else }
|
||||||
|
<p>ERROR : There is no action selected.</p>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.nes-container {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.justify-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.report-menu-main {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
pointer-events: auto;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 70vh;
|
||||||
|
width: 50vw;
|
||||||
|
top: 10vh;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
section.report-menu-title {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: calc(100% - 45px) 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.report-menu-action {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.report-menu-action.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 800px) {
|
||||||
|
div.report-menu-main {
|
||||||
|
top: 21vh;
|
||||||
|
height: 60vh;
|
||||||
|
width: 100vw;
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
55
front/src/Components/ReportMenu/ReportSubMenu.svelte
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {showReportScreenStore, userReportEmpty} from "../../Stores/ShowReportScreenStore";
|
||||||
|
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||||
|
|
||||||
|
export let userUUID: string | undefined;
|
||||||
|
let reportMessage: string;
|
||||||
|
let hiddenError = true;
|
||||||
|
|
||||||
|
function submitReport() {
|
||||||
|
if (reportMessage === '') {
|
||||||
|
hiddenError = true;
|
||||||
|
} else {
|
||||||
|
hiddenError = false;
|
||||||
|
if( userUUID === undefined) {
|
||||||
|
console.error('User UUID is not valid.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gameManager.getCurrentGameScene().connection?.emitReportPlayerMessage(userUUID, reportMessage);
|
||||||
|
showReportScreenStore.set(userReportEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="report-container-main">
|
||||||
|
<h3>Report</h3>
|
||||||
|
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
|
||||||
|
<form>
|
||||||
|
<section>
|
||||||
|
<label>
|
||||||
|
<span>Your message: </span>
|
||||||
|
<textarea type="text" class="nes-textarea" bind:value={reportMessage}></textarea>
|
||||||
|
</label>
|
||||||
|
<p hidden="{hiddenError}">Report message cannot to be empty.</p>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<button type="submit" class="nes-btn is-error" on:click={submitReport}>Report this user</button>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.report-container-main {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: clamp(100px, 15vh, 300px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-height: 630px) {
|
||||||
|
div.report-container-main textarea {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
BIN
front/src/Components/images/logo-WA-min.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
@ -16,8 +16,9 @@ const lastRoomUrl = "lastRoomUrl";
|
|||||||
const authToken = "authToken";
|
const authToken = "authToken";
|
||||||
const state = "state";
|
const state = "state";
|
||||||
const nonce = "nonce";
|
const nonce = "nonce";
|
||||||
|
const notification = "notificationPermission";
|
||||||
|
|
||||||
const cacheAPIIndex = "workavdenture-cache-v1";
|
const cacheAPIIndex = "workavdenture-cache";
|
||||||
|
|
||||||
class LocalUserStore {
|
class LocalUserStore {
|
||||||
saveUser(localUser: LocalUser) {
|
saveUser(localUser: LocalUser) {
|
||||||
@ -143,6 +144,14 @@ class LocalUserStore {
|
|||||||
return localStorage.getItem(authToken);
|
return localStorage.getItem(authToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setNotification(value: string): void {
|
||||||
|
localStorage.setItem(notification, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNotification(): string | null {
|
||||||
|
return localStorage.getItem(notification);
|
||||||
|
}
|
||||||
|
|
||||||
generateState(): string {
|
generateState(): string {
|
||||||
const newState = uuidv4();
|
const newState = uuidv4();
|
||||||
localStorage.setItem(state, newState);
|
localStorage.setItem(state, newState);
|
||||||
|
@ -18,6 +18,7 @@ export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "
|
|||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
export const NODE_ENV = process.env.NODE_ENV || "development";
|
||||||
|
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||||
|
|
||||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||||
|
|
||||||
|
@ -1,48 +1,63 @@
|
|||||||
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||||
|
import { DirtyScene } from "../Game/DirtyScene";
|
||||||
|
|
||||||
const LogoNameIndex: string = 'logoLoading';
|
const LogoNameIndex: string = "logoLoading";
|
||||||
const TextName: string = 'Loading...';
|
const TextName: string = "Loading...";
|
||||||
const LogoResource: string = 'resources/logos/logo.png';
|
const LogoResource: string = "resources/logos/logo.png";
|
||||||
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
|
const LogoFrame: ImageFrameConfig = { frameWidth: 307, frameHeight: 59 };
|
||||||
|
|
||||||
export const addLoader = (scene: Phaser.Scene): void => {
|
export const addLoader = (scene: Phaser.Scene): void => {
|
||||||
// If there is nothing to load, do not display the loader.
|
// If there is nothing to load, do not display the loader.
|
||||||
if (scene.load.list.entries.length === 0) {
|
if (scene.load.list.entries.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let loadingText: Phaser.GameObjects.Text|null = null;
|
let loadingText: Phaser.GameObjects.Text | null = null;
|
||||||
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
||||||
const loadingBarHeight: number = 16;
|
const loadingBarHeight: number = 16;
|
||||||
const padding: number = 5;
|
const padding: number = 5;
|
||||||
|
|
||||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||||
if(scene.load.textureManager.exists(LogoNameIndex)){
|
if (scene.load.textureManager.exists(LogoNameIndex)) {
|
||||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
return res(
|
||||||
}else{
|
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
//add loading if logo image is not ready
|
//add loading if logo image is not ready
|
||||||
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
||||||
}
|
}
|
||||||
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||||
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||||
if(loadingText){
|
if (loadingText) {
|
||||||
loadingText.destroy();
|
loadingText.destroy();
|
||||||
}
|
}
|
||||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
return res(
|
||||||
|
scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const progressContainer = scene.add.graphics();
|
const progressContainer = scene.add.graphics();
|
||||||
const progress = scene.add.graphics();
|
const progress = scene.add.graphics();
|
||||||
progressContainer.fillStyle(0x444444, 0.8);
|
progressContainer.fillStyle(0x444444, 0.8);
|
||||||
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
|
progressContainer.fillRect(
|
||||||
|
(scene.game.renderer.width - loadingBarWidth) / 2 - padding,
|
||||||
|
scene.game.renderer.height / 2 + 50 - padding,
|
||||||
|
loadingBarWidth + padding * 2,
|
||||||
|
loadingBarHeight + padding * 2
|
||||||
|
);
|
||||||
|
|
||||||
scene.load.on('progress', (value: number) => {
|
scene.load.on("progress", (value: number) => {
|
||||||
progress.clear();
|
progress.clear();
|
||||||
progress.fillStyle(0xBBBBBB, 1);
|
progress.fillStyle(0xbbbbbb, 1);
|
||||||
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
|
progress.fillRect(
|
||||||
|
(scene.game.renderer.width - loadingBarWidth) / 2,
|
||||||
|
scene.game.renderer.height / 2 + 50,
|
||||||
|
loadingBarWidth * value,
|
||||||
|
loadingBarHeight
|
||||||
|
);
|
||||||
});
|
});
|
||||||
scene.load.on('complete', () => {
|
scene.load.on("complete", () => {
|
||||||
if(loadingText){
|
if (loadingText) {
|
||||||
loadingText.destroy();
|
loadingText.destroy();
|
||||||
}
|
}
|
||||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||||
@ -50,5 +65,8 @@ export const addLoader = (scene: Phaser.Scene): void => {
|
|||||||
});
|
});
|
||||||
progress.destroy();
|
progress.destroy();
|
||||||
progressContainer.destroy();
|
progressContainer.destroy();
|
||||||
|
if (scene instanceof DirtyScene) {
|
||||||
|
scene.markDirty();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import type {GameScene} from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
||||||
import {Character} from "../Entity/Character";
|
import { Character } from "../Entity/Character";
|
||||||
import type {PlayerAnimationDirections} from "../Player/Animation";
|
import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||||
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||||
*/
|
*/
|
||||||
export class RemotePlayer extends Character {
|
export class RemotePlayer extends Character {
|
||||||
userId: number;
|
userId: number;
|
||||||
private visitCardUrl: string|null;
|
private visitCardUrl: string | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
userId: number,
|
userId: number,
|
||||||
@ -21,19 +20,33 @@ export class RemotePlayer extends Character {
|
|||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: Promise<string[]>,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
visitCardUrl: string|null,
|
visitCardUrl: string | null,
|
||||||
companion: string|null,
|
companion: string | null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: Promise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise);
|
super(
|
||||||
|
Scene,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
texturesPromise,
|
||||||
|
name,
|
||||||
|
direction,
|
||||||
|
moving,
|
||||||
|
1,
|
||||||
|
!!visitCardUrl,
|
||||||
|
companion,
|
||||||
|
companionTexturePromise
|
||||||
|
);
|
||||||
|
|
||||||
//set data
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.visitCardUrl = visitCardUrl;
|
this.visitCardUrl = visitCardUrl;
|
||||||
|
|
||||||
this.on('pointerdown', () => {
|
this.on("pointerdown", (event: Phaser.Input.Pointer) => {
|
||||||
requestVisitCardsStore.set(this.visitCardUrl);
|
if (event.downElement.nodeName === "CANVAS") {
|
||||||
})
|
requestVisitCardsStore.set(this.visitCardUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
updatePosition(position: PointInterface): void {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { GameScene } from "./GameScene";
|
import { GameScene } from "./GameScene";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import type { Room } from "../../Connexion/Room";
|
import type { Room } from "../../Connexion/Room";
|
||||||
import { MenuScene, MenuSceneName } from "../Menu/MenuScene";
|
|
||||||
import { LoginSceneName } from "../Login/LoginScene";
|
import { LoginSceneName } from "../Login/LoginScene";
|
||||||
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
import { SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||||
import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
import { EnableCameraSceneName } from "../Login/EnableCameraScene";
|
||||||
@ -9,6 +8,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||||
|
import { menuIconVisiblilityStore } from "../../Stores/MenuStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class should be responsible for any scene starting/stopping
|
* This class should be responsible for any scene starting/stopping
|
||||||
@ -18,8 +18,9 @@ export class GameManager {
|
|||||||
private characterLayers: string[] | null;
|
private characterLayers: string[] | null;
|
||||||
private companion: string | null;
|
private companion: string | null;
|
||||||
private startRoom!: Room;
|
private startRoom!: Room;
|
||||||
private scenePlugin!: Phaser.Scenes.ScenePlugin;
|
|
||||||
currentGameSceneName: string | null = null;
|
currentGameSceneName: string | null = null;
|
||||||
|
// Note: this scenePlugin is the scenePlugin of the EntryScene. We should always provide a key in methods called on this scenePlugin.
|
||||||
|
private scenePlugin!: Phaser.Scenes.ScenePlugin;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.playerName = localUserStore.getName();
|
this.playerName = localUserStore.getName();
|
||||||
@ -83,7 +84,6 @@ export class GameManager {
|
|||||||
public goToStartingMap(): void {
|
public goToStartingMap(): void {
|
||||||
console.log("starting " + (this.currentGameSceneName || this.startRoom.key));
|
console.log("starting " + (this.currentGameSceneName || this.startRoom.key));
|
||||||
this.scenePlugin.start(this.currentGameSceneName || this.startRoom.key);
|
this.scenePlugin.start(this.currentGameSceneName || this.startRoom.key);
|
||||||
this.scenePlugin.launch(MenuSceneName);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!localUserStore.getHelpCameraSettingsShown() &&
|
!localUserStore.getHelpCameraSettingsShown() &&
|
||||||
@ -96,8 +96,7 @@ export class GameManager {
|
|||||||
|
|
||||||
public gameSceneIsCreated(scene: GameScene) {
|
public gameSceneIsCreated(scene: GameScene) {
|
||||||
this.currentGameSceneName = scene.scene.key;
|
this.currentGameSceneName = scene.scene.key;
|
||||||
const menuScene: MenuScene = scene.scene.get(MenuSceneName) as MenuScene;
|
menuIconVisiblilityStore.set(true);
|
||||||
menuScene.revealMenuIcon();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,8 +107,8 @@ export class GameManager {
|
|||||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||||
const gameScene: GameScene = this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
const gameScene: GameScene = this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||||
gameScene.cleanupClosingScene();
|
gameScene.cleanupClosingScene();
|
||||||
this.scenePlugin.stop(this.currentGameSceneName);
|
gameScene.createSuccessorGameScene(false, false);
|
||||||
this.scenePlugin.sleep(MenuSceneName);
|
menuIconVisiblilityStore.set(false);
|
||||||
if (!this.scenePlugin.get(targetSceneName)) {
|
if (!this.scenePlugin.get(targetSceneName)) {
|
||||||
this.scenePlugin.add(targetSceneName, sceneClass, false);
|
this.scenePlugin.add(targetSceneName, sceneClass, false);
|
||||||
}
|
}
|
||||||
@ -122,7 +121,7 @@ export class GameManager {
|
|||||||
tryResumingGame(fallbackSceneName: string) {
|
tryResumingGame(fallbackSceneName: string) {
|
||||||
if (this.currentGameSceneName) {
|
if (this.currentGameSceneName) {
|
||||||
this.scenePlugin.start(this.currentGameSceneName);
|
this.scenePlugin.start(this.currentGameSceneName);
|
||||||
this.scenePlugin.wake(MenuSceneName);
|
menuIconVisiblilityStore.set(true);
|
||||||
} else {
|
} else {
|
||||||
this.scenePlugin.run(fallbackSceneName);
|
this.scenePlugin.run(fallbackSceneName);
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,11 @@ export class GameMap {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.triggerAll();
|
||||||
|
}
|
||||||
|
|
||||||
const newProps = this.getProperties(key);
|
private triggerAll(): void {
|
||||||
|
const newProps = this.getProperties(this.key ?? 0);
|
||||||
const oldProps = this.lastProperties;
|
const oldProps = this.lastProperties;
|
||||||
this.lastProperties = newProps;
|
this.lastProperties = newProps;
|
||||||
|
|
||||||
@ -253,4 +256,34 @@ export class GameMap {
|
|||||||
}
|
}
|
||||||
return this.tileNameMap.get(tile);
|
return this.tileNameMap.get(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setLayerProperty(
|
||||||
|
layerName: string,
|
||||||
|
propertyName: string,
|
||||||
|
propertyValue: string | number | undefined | boolean
|
||||||
|
) {
|
||||||
|
const layer = this.findLayer(layerName);
|
||||||
|
if (layer === undefined) {
|
||||||
|
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (layer.properties === undefined) {
|
||||||
|
layer.properties = [];
|
||||||
|
}
|
||||||
|
const property = layer.properties.find((property) => property.name === propertyName);
|
||||||
|
if (property === undefined) {
|
||||||
|
if (propertyValue === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (propertyValue === undefined) {
|
||||||
|
const index = layer.properties.indexOf(property);
|
||||||
|
layer.properties.splice(index, 1);
|
||||||
|
}
|
||||||
|
property.value = propertyValue;
|
||||||
|
|
||||||
|
this.triggerAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,6 @@ import type { ActionableItem } from "../Items/ActionableItem";
|
|||||||
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||||
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
||||||
import { MenuScene, MenuSceneName } from "../Menu/MenuScene";
|
|
||||||
import { PlayerAnimationDirections } from "../Player/Animation";
|
import { PlayerAnimationDirections } from "../Player/Animation";
|
||||||
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
|
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
|
||||||
import { ErrorSceneName } from "../Reconnecting/ErrorScene";
|
import { ErrorSceneName } from "../Reconnecting/ErrorScene";
|
||||||
@ -92,9 +91,7 @@ import { PropertyUtils } from "../Map/PropertyUtils";
|
|||||||
import Tileset = Phaser.Tilemaps.Tileset;
|
import Tileset = Phaser.Tilemaps.Tileset;
|
||||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -405,12 +402,6 @@ export class GameScene extends DirtyScene {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, let's load the script, if any
|
|
||||||
const scripts = this.getScriptUrls(this.mapFile);
|
|
||||||
for (const script of scripts) {
|
|
||||||
iframeListener.registerScript(script);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook initialisation
|
//hook initialisation
|
||||||
@ -569,6 +560,12 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.createPromiseResolve();
|
this.createPromiseResolve();
|
||||||
|
// Now, let's load the script, if any
|
||||||
|
const scripts = this.getScriptUrls(this.mapFile);
|
||||||
|
const scriptPromises = [];
|
||||||
|
for (const script of scripts) {
|
||||||
|
scriptPromises.push(iframeListener.registerScript(script));
|
||||||
|
}
|
||||||
|
|
||||||
this.userInputManager.spaceEvent(() => {
|
this.userInputManager.spaceEvent(() => {
|
||||||
this.outlinedItem?.activate();
|
this.outlinedItem?.activate();
|
||||||
@ -586,6 +583,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.triggerOnMapLayerPropertyChange();
|
this.triggerOnMapLayerPropertyChange();
|
||||||
|
|
||||||
if (!this.room.isDisconnected()) {
|
if (!this.room.isDisconnected()) {
|
||||||
|
this.scene.sleep();
|
||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,6 +607,10 @@ export class GameScene extends DirtyScene {
|
|||||||
this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => {
|
this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => {
|
||||||
this.openChatIcon.setVisible(!v);
|
this.openChatIcon.setVisible(!v);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => {
|
||||||
|
this.scene.wake();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -686,19 +688,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.connection.onServerDisconnected(() => {
|
this.connection.onServerDisconnected(() => {
|
||||||
console.log("Player disconnected from server. Reloading scene.");
|
console.log("Player disconnected from server. Reloading scene.");
|
||||||
this.cleanupClosingScene();
|
this.cleanupClosingScene();
|
||||||
|
this.createSuccessorGameScene(true, true);
|
||||||
const gameSceneKey = "somekey" + Math.round(Math.random() * 10000);
|
|
||||||
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
|
||||||
this.scene.add(gameSceneKey, game, true, {
|
|
||||||
initPosition: {
|
|
||||||
x: this.CurrentPlayer.x,
|
|
||||||
y: this.CurrentPlayer.y,
|
|
||||||
},
|
|
||||||
reconnecting: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scene.stop(this.scene.key);
|
|
||||||
this.scene.remove(this.scene.key);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.connection.onActionableEvent((message) => {
|
this.connection.onActionableEvent((message) => {
|
||||||
@ -722,7 +712,7 @@ export class GameScene extends DirtyScene {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// When connection is performed, let's connect SimplePeer
|
// When connection is performed, let's connect SimplePeer
|
||||||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
this.simplePeer = new SimplePeer(this.connection);
|
||||||
peerStore.connectToSimplePeer(this.simplePeer);
|
peerStore.connectToSimplePeer(this.simplePeer);
|
||||||
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
||||||
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
||||||
@ -760,8 +750,9 @@ export class GameScene extends DirtyScene {
|
|||||||
this.connectionAnswerPromiseResolve(onConnect.room);
|
this.connectionAnswerPromiseResolve(onConnect.room);
|
||||||
// Analyze tags to find if we are admin. If yes, show console.
|
// Analyze tags to find if we are admin. If yes, show console.
|
||||||
|
|
||||||
this.scene.wake();
|
if (this.scene.isSleeping()) {
|
||||||
this.scene.stop(ReconnectingSceneName);
|
this.scene.stop(ReconnectingSceneName);
|
||||||
|
}
|
||||||
|
|
||||||
//init user position and play trigger to check layers properties
|
//init user position and play trigger to check layers properties
|
||||||
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||||
@ -844,7 +835,8 @@ export class GameScene extends DirtyScene {
|
|||||||
newValue as string,
|
newValue as string,
|
||||||
this.MapUrlFile,
|
this.MapUrlFile,
|
||||||
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
||||||
allProps.get("openWebsitePolicy") as string | undefined
|
allProps.get("openWebsitePolicy") as string | undefined,
|
||||||
|
allProps.get("openWebsiteWidth") as number | undefined
|
||||||
);
|
);
|
||||||
layoutManagerActionStore.removeAction("openWebsite");
|
layoutManagerActionStore.removeAction("openWebsite");
|
||||||
};
|
};
|
||||||
@ -953,9 +945,13 @@ export class GameScene extends DirtyScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
|
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
|
||||||
let html = `<div id="container" hidden><div class="nes-container with-title is-centered">
|
let html = '<div id="container" hidden>';
|
||||||
|
if (escapedMessage) {
|
||||||
|
html += `<div class="nes-container with-title is-centered">
|
||||||
${escapedMessage}
|
${escapedMessage}
|
||||||
</div> `;
|
</div> `;
|
||||||
|
}
|
||||||
|
|
||||||
const buttonContainer = '<div class="buttonContainer"</div>';
|
const buttonContainer = '<div class="buttonContainer"</div>';
|
||||||
html += buttonContainer;
|
html += buttonContainer;
|
||||||
let id = 0;
|
let id = 0;
|
||||||
@ -985,7 +981,11 @@ ${escapedMessage}
|
|||||||
const btnId = id;
|
const btnId = id;
|
||||||
button.onclick = () => {
|
button.onclick = () => {
|
||||||
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId);
|
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId);
|
||||||
|
// Disable for a short amount of time to let time to the script to remove the popup
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
button.disabled = false;
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
id++;
|
id++;
|
||||||
}
|
}
|
||||||
@ -1241,30 +1241,10 @@ ${escapedMessage}
|
|||||||
propertyName: string,
|
propertyName: string,
|
||||||
propertyValue: string | number | boolean | undefined
|
propertyValue: string | number | boolean | undefined
|
||||||
): void {
|
): void {
|
||||||
const layer = this.gameMap.findLayer(layerName);
|
|
||||||
if (layer === undefined) {
|
|
||||||
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (propertyName === "exitUrl" && typeof propertyValue === "string") {
|
if (propertyName === "exitUrl" && typeof propertyValue === "string") {
|
||||||
this.loadNextGameFromExitUrl(propertyValue);
|
this.loadNextGameFromExitUrl(propertyValue);
|
||||||
}
|
}
|
||||||
if (layer.properties === undefined) {
|
this.gameMap.setLayerProperty(layerName, propertyName, propertyValue);
|
||||||
layer.properties = [];
|
|
||||||
}
|
|
||||||
const property = layer.properties.find((property) => property.name === propertyName);
|
|
||||||
if (property === undefined) {
|
|
||||||
if (propertyValue === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (propertyValue === undefined) {
|
|
||||||
const index = layer.properties.indexOf(property);
|
|
||||||
layer.properties.splice(index, 1);
|
|
||||||
}
|
|
||||||
property.value = propertyValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setLayerVisibility(layerName: string, visible: boolean): void {
|
private setLayerVisibility(layerName: string, visible: boolean): void {
|
||||||
@ -1323,9 +1303,6 @@ ${escapedMessage}
|
|||||||
urlManager.pushStartLayerNameToUrl(roomUrl.hash);
|
urlManager.pushStartLayerNameToUrl(roomUrl.hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuScene: MenuScene = this.scene.get(MenuSceneName) as MenuScene;
|
|
||||||
menuScene.reset();
|
|
||||||
|
|
||||||
if (!targetRoom.isEqual(this.room)) {
|
if (!targetRoom.isEqual(this.room)) {
|
||||||
if (this.scene.get(targetRoom.key) === null) {
|
if (this.scene.get(targetRoom.key) === null) {
|
||||||
console.error("next room not loaded", targetRoom.key);
|
console.error("next room not loaded", targetRoom.key);
|
||||||
@ -1863,8 +1840,9 @@ ${escapedMessage}
|
|||||||
"jitsiInterfaceConfig"
|
"jitsiInterfaceConfig"
|
||||||
);
|
);
|
||||||
const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
|
const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
|
||||||
|
const jitsiWidth = allProps.get("jitsiWidth") as number | undefined;
|
||||||
|
|
||||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth);
|
||||||
this.connection?.setSilent(true);
|
this.connection?.setSilent(true);
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
@ -1920,4 +1898,24 @@ ${escapedMessage}
|
|||||||
waScaleManager.zoomModifier *= zoomFactor;
|
waScaleManager.zoomModifier *= zoomFactor;
|
||||||
biggestAvailableAreaStore.recompute();
|
biggestAvailableAreaStore.recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public createSuccessorGameScene(autostart: boolean, reconnecting: boolean) {
|
||||||
|
const gameSceneKey = "somekey" + Math.round(Math.random() * 10000);
|
||||||
|
const game = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
||||||
|
this.scene.add(gameSceneKey, game, autostart, {
|
||||||
|
initPosition: {
|
||||||
|
x: this.CurrentPlayer.x,
|
||||||
|
y: this.CurrentPlayer.y,
|
||||||
|
},
|
||||||
|
reconnecting: reconnecting,
|
||||||
|
});
|
||||||
|
|
||||||
|
//If new gameScene doesn't start automatically then we change the gameScene in gameManager so that it can start the new gameScene
|
||||||
|
if (!autostart) {
|
||||||
|
gameManager.gameSceneIsCreated(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene.stop(this.scene.key);
|
||||||
|
this.scene.remove(this.scene.key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { TextField } from "../Components/TextField";
|
|
||||||
import Image = Phaser.GameObjects.Image;
|
|
||||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
|
||||||
import { SoundMeter } from "../Components/SoundMeter";
|
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
|
||||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
|
||||||
import { PinchManager } from "../UserInput/PinchManager";
|
|
||||||
import Zone = Phaser.GameObjects.Zone;
|
|
||||||
import { MenuScene } from "../Menu/MenuScene";
|
|
||||||
import { ResizableScene } from "./ResizableScene";
|
import { ResizableScene } from "./ResizableScene";
|
||||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import type { CompanionResourceDescriptionInterface } from "../Companion/Compani
|
|||||||
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
|
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
|
||||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||||
import { PinchManager } from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import { MenuScene } from "../Menu/MenuScene";
|
|
||||||
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||||
|
@ -1,430 +0,0 @@
|
|||||||
import { LoginScene, LoginSceneName } from "../Login/LoginScene";
|
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
|
||||||
import { SelectCompanionScene, SelectCompanionSceneName } from "../Login/SelectCompanionScene";
|
|
||||||
import { gameManager } from "../Game/GameManager";
|
|
||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
|
||||||
import { gameReportKey, gameReportRessource, ReportMenu } from "./ReportMenu";
|
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
|
||||||
import { GameConnexionTypes } from "../../Url/UrlManager";
|
|
||||||
import { menuIconVisible } from "../../Stores/MenuStore";
|
|
||||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
|
||||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
|
||||||
import { iframeListener } from "../../Api/IframeListener";
|
|
||||||
import { Subscription } from "rxjs";
|
|
||||||
import { registerMenuCommandStream } from "../../Api/Events/ui/MenuItemRegisterEvent";
|
|
||||||
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
|
||||||
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import { playersStore } from "../../Stores/PlayersStore";
|
|
||||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
|
||||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
|
||||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
export const MenuSceneName = "MenuScene";
|
|
||||||
const gameMenuKey = "gameMenu";
|
|
||||||
const gameMenuIconKey = "gameMenuIcon";
|
|
||||||
const gameSettingsMenuKey = "gameSettingsMenu";
|
|
||||||
const gameShare = "gameShare";
|
|
||||||
|
|
||||||
const closedSideMenuX = -1000;
|
|
||||||
const openedSideMenuX = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scene that manages the game menu, rendered using a DOM element.
|
|
||||||
*/
|
|
||||||
export class MenuScene extends Phaser.Scene {
|
|
||||||
private menuElement!: Phaser.GameObjects.DOMElement;
|
|
||||||
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
|
|
||||||
private gameShareElement!: Phaser.GameObjects.DOMElement;
|
|
||||||
private gameReportElement!: ReportMenu;
|
|
||||||
private sideMenuOpened = false;
|
|
||||||
private settingsMenuOpened = false;
|
|
||||||
private gameShareOpened = false;
|
|
||||||
private gameQualityValue: number;
|
|
||||||
private videoQualityValue: number;
|
|
||||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
|
||||||
private subscriptions = new Subscription();
|
|
||||||
constructor() {
|
|
||||||
super({ key: MenuSceneName });
|
|
||||||
|
|
||||||
this.gameQualityValue = localUserStore.getGameQualityValue();
|
|
||||||
this.videoQualityValue = localUserStore.getVideoQualityValue();
|
|
||||||
|
|
||||||
this.subscriptions.add(
|
|
||||||
registerMenuCommandStream.subscribe((menuCommand) => {
|
|
||||||
this.addMenuOption(menuCommand);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.subscriptions.add(
|
|
||||||
iframeListener.unregisterMenuCommandStream.subscribe((menuCommand) => {
|
|
||||||
this.destroyMenu(menuCommand);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
const addedMenuItems = [...this.menuElement.node.querySelectorAll(".fromApi")];
|
|
||||||
for (let index = addedMenuItems.length - 1; index >= 0; index--) {
|
|
||||||
addedMenuItems[index].remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public addMenuOption(menuText: string) {
|
|
||||||
const wrappingSection = document.createElement("section");
|
|
||||||
const escapedHtml = HtmlUtils.escapeHtml(menuText);
|
|
||||||
wrappingSection.innerHTML = `<button class="fromApi" id="${escapedHtml}">${escapedHtml}</button>`;
|
|
||||||
const menuItemContainer = this.menuElement.node.querySelector("#gameMenu main");
|
|
||||||
if (menuItemContainer) {
|
|
||||||
menuItemContainer.querySelector(`#${escapedHtml}.fromApi`)?.remove();
|
|
||||||
menuItemContainer.insertBefore(wrappingSection, menuItemContainer.querySelector("#socialLinks"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preload() {
|
|
||||||
this.load.html(gameMenuKey, "resources/html/gameMenu.html");
|
|
||||||
this.load.html(gameMenuIconKey, "resources/html/gameMenuIcon.html");
|
|
||||||
this.load.html(gameSettingsMenuKey, "resources/html/gameQualityMenu.html");
|
|
||||||
this.load.html(gameShare, "resources/html/gameShare.html");
|
|
||||||
this.load.html(gameReportKey, gameReportRessource);
|
|
||||||
}
|
|
||||||
|
|
||||||
create() {
|
|
||||||
menuIconVisible.set(true);
|
|
||||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
|
||||||
this.menuElement.setOrigin(0);
|
|
||||||
MenuScene.revealMenusAfterInit(this.menuElement, "gameMenu");
|
|
||||||
|
|
||||||
if (mediaManager.hasNotification()) {
|
|
||||||
HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const middleX = window.innerWidth / 3 - 298;
|
|
||||||
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
|
|
||||||
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, "gameQuality");
|
|
||||||
|
|
||||||
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
|
|
||||||
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
|
|
||||||
this.gameShareElement.addListener("click");
|
|
||||||
this.gameShareElement.on("click", (event: MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if ((event?.target as HTMLInputElement).id === "gameShareFormSubmit") {
|
|
||||||
this.copyLink();
|
|
||||||
} else if ((event?.target as HTMLInputElement).id === "gameShareFormCancel") {
|
|
||||||
this.closeGameShare();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.gameReportElement = new ReportMenu(
|
|
||||||
this,
|
|
||||||
connectionManager.getConnexionType === GameConnexionTypes.anonymous
|
|
||||||
);
|
|
||||||
showReportScreenStore.subscribe((user) => {
|
|
||||||
if (user !== null) {
|
|
||||||
this.closeAll();
|
|
||||||
const uuid = playersStore.getPlayerById(user.userId)?.userUuid;
|
|
||||||
if (uuid === undefined) {
|
|
||||||
throw new Error("Could not find UUID for user with ID " + user.userId);
|
|
||||||
}
|
|
||||||
this.gameReportElement.open(uuid, user.userName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.input.keyboard.on("keyup-TAB", () => {
|
|
||||||
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
|
||||||
});
|
|
||||||
this.menuButton = this.add.dom(0, 0).createFromCache(gameMenuIconKey);
|
|
||||||
this.menuButton.addListener("click");
|
|
||||||
this.menuButton.on("click", () => {
|
|
||||||
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.menuElement.addListener("click");
|
|
||||||
this.menuElement.on("click", this.onMenuClick.bind(this));
|
|
||||||
|
|
||||||
chatVisibilityStore.subscribe((v) => {
|
|
||||||
this.menuButton.setVisible(!v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo put this method in a parent menuElement class
|
|
||||||
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
|
||||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
|
||||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
|
||||||
setTimeout(() => {
|
|
||||||
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
|
|
||||||
}, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
public revealMenuIcon(): void {
|
|
||||||
//TODO fix me: add try catch because at the same time, 'this.menuButton' variable doesn't exist and there is error on 'getChildByID' function
|
|
||||||
try {
|
|
||||||
(this.menuButton.getChildByID("menuIcon") as HTMLElement).hidden = false;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openSideMenu() {
|
|
||||||
if (this.sideMenuOpened) return;
|
|
||||||
this.closeAll();
|
|
||||||
this.sideMenuOpened = true;
|
|
||||||
this.menuButton.getChildByID("openMenuButton").innerHTML = "X";
|
|
||||||
const connection = gameManager.getCurrentGameScene().connection;
|
|
||||||
if (connection && connection.isAdmin()) {
|
|
||||||
const adminSection = this.menuElement.getChildByID("adminConsoleSection") as HTMLElement;
|
|
||||||
adminSection.hidden = false;
|
|
||||||
}
|
|
||||||
//TODO bind with future metadata of card
|
|
||||||
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
|
|
||||||
const adminSection = this.menuElement.getChildByID("socialLinks") as HTMLElement;
|
|
||||||
adminSection.hidden = false;
|
|
||||||
//}
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.menuElement,
|
|
||||||
x: openedSideMenuX,
|
|
||||||
duration: 500,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeSideMenu(): void {
|
|
||||||
if (!this.sideMenuOpened) return;
|
|
||||||
this.sideMenuOpened = false;
|
|
||||||
this.closeAll();
|
|
||||||
this.menuButton.getChildByID("openMenuButton").innerHTML = `<img src="/static/images/menu.svg">`;
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.menuElement,
|
|
||||||
x: closedSideMenuX,
|
|
||||||
duration: 500,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private openGameSettingsMenu(): void {
|
|
||||||
if (this.settingsMenuOpened) {
|
|
||||||
this.closeGameQualityMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//close all
|
|
||||||
this.closeAll();
|
|
||||||
|
|
||||||
this.settingsMenuOpened = true;
|
|
||||||
|
|
||||||
const gameQualitySelect = this.gameQualityMenuElement.getChildByID("select-game-quality") as HTMLInputElement;
|
|
||||||
gameQualitySelect.value = "" + this.gameQualityValue;
|
|
||||||
const videoQualitySelect = this.gameQualityMenuElement.getChildByID("select-video-quality") as HTMLInputElement;
|
|
||||||
videoQualitySelect.value = "" + this.videoQualityValue;
|
|
||||||
|
|
||||||
this.gameQualityMenuElement.addListener("click");
|
|
||||||
this.gameQualityMenuElement.on("click", (event: MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if ((event?.target as HTMLInputElement).id === "gameQualityFormSubmit") {
|
|
||||||
const gameQualitySelect = this.gameQualityMenuElement.getChildByID(
|
|
||||||
"select-game-quality"
|
|
||||||
) as HTMLInputElement;
|
|
||||||
const videoQualitySelect = this.gameQualityMenuElement.getChildByID(
|
|
||||||
"select-video-quality"
|
|
||||||
) as HTMLInputElement;
|
|
||||||
this.saveSetting(parseInt(gameQualitySelect.value), parseInt(videoQualitySelect.value));
|
|
||||||
} else if ((event?.target as HTMLInputElement).id === "gameQualityFormCancel") {
|
|
||||||
this.closeGameQualityMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let middleY = this.scale.height / 2 - 392 / 2;
|
|
||||||
if (middleY < 0) {
|
|
||||||
middleY = 0;
|
|
||||||
}
|
|
||||||
let middleX = this.scale.width / 2 - 457 / 2;
|
|
||||||
if (middleX < 0) {
|
|
||||||
middleX = 0;
|
|
||||||
}
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.gameQualityMenuElement,
|
|
||||||
y: middleY,
|
|
||||||
x: middleX,
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeGameQualityMenu(): void {
|
|
||||||
if (!this.settingsMenuOpened) return;
|
|
||||||
this.settingsMenuOpened = false;
|
|
||||||
|
|
||||||
this.gameQualityMenuElement.removeListener("click");
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.gameQualityMenuElement,
|
|
||||||
y: -400,
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private openGameShare(): void {
|
|
||||||
if (this.gameShareOpened) {
|
|
||||||
this.closeGameShare();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//close all
|
|
||||||
this.closeAll();
|
|
||||||
|
|
||||||
const gameShareLink = this.gameShareElement.getChildByID("gameShareLink") as HTMLInputElement;
|
|
||||||
gameShareLink.value = location.toString();
|
|
||||||
|
|
||||||
this.gameShareOpened = true;
|
|
||||||
|
|
||||||
let middleY = this.scale.height / 2 - 85;
|
|
||||||
if (middleY < 0) {
|
|
||||||
middleY = 0;
|
|
||||||
}
|
|
||||||
let middleX = this.scale.width / 2 - 200;
|
|
||||||
if (middleX < 0) {
|
|
||||||
middleX = 0;
|
|
||||||
}
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.gameShareElement,
|
|
||||||
y: middleY,
|
|
||||||
x: middleX,
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeGameShare(): void {
|
|
||||||
const gameShareInfo = this.gameShareElement.getChildByID("gameShareInfo") as HTMLParagraphElement;
|
|
||||||
gameShareInfo.innerText = "";
|
|
||||||
gameShareInfo.style.display = "none";
|
|
||||||
this.gameShareOpened = false;
|
|
||||||
this.tweens.add({
|
|
||||||
targets: this.gameShareElement,
|
|
||||||
y: -400,
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onMenuClick(event: MouseEvent) {
|
|
||||||
const htmlMenuItem = event?.target as HTMLInputElement;
|
|
||||||
if (htmlMenuItem.classList.contains("not-button")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (htmlMenuItem.classList.contains("fromApi")) {
|
|
||||||
sendMenuClickedEvent(htmlMenuItem.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ((event?.target as HTMLInputElement).id) {
|
|
||||||
case "changeNameButton":
|
|
||||||
this.closeSideMenu();
|
|
||||||
gameManager.leaveGame(LoginSceneName, new LoginScene());
|
|
||||||
break;
|
|
||||||
case "sparkButton":
|
|
||||||
this.gotToCreateMapPage();
|
|
||||||
break;
|
|
||||||
case "changeSkinButton":
|
|
||||||
this.closeSideMenu();
|
|
||||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
|
||||||
break;
|
|
||||||
case "changeCompanionButton":
|
|
||||||
this.closeSideMenu();
|
|
||||||
gameManager.leaveGame(SelectCompanionSceneName, new SelectCompanionScene());
|
|
||||||
break;
|
|
||||||
case "closeButton":
|
|
||||||
this.closeSideMenu();
|
|
||||||
break;
|
|
||||||
case "shareButton":
|
|
||||||
this.openGameShare();
|
|
||||||
break;
|
|
||||||
case "editGameSettingsButton":
|
|
||||||
this.openGameSettingsMenu();
|
|
||||||
break;
|
|
||||||
case "oidcLogin":
|
|
||||||
connectionManager.loadOpenIDScreen();
|
|
||||||
break;
|
|
||||||
case "toggleFullscreen":
|
|
||||||
this.toggleFullscreen();
|
|
||||||
break;
|
|
||||||
case "enableNotification":
|
|
||||||
this.enableNotification();
|
|
||||||
break;
|
|
||||||
case "adminConsoleButton":
|
|
||||||
if (get(consoleGlobalMessageManagerVisibleStore)) {
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
|
||||||
} else {
|
|
||||||
consoleGlobalMessageManagerVisibleStore.set(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async copyLink() {
|
|
||||||
await navigator.clipboard.writeText(location.toString());
|
|
||||||
const gameShareInfo = this.gameShareElement.getChildByID("gameShareInfo") as HTMLParagraphElement;
|
|
||||||
gameShareInfo.innerText = "Link copied, you can share it now!";
|
|
||||||
gameShareInfo.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
private saveSetting(valueGame: number, valueVideo: number) {
|
|
||||||
if (valueGame !== this.gameQualityValue) {
|
|
||||||
this.gameQualityValue = valueGame;
|
|
||||||
localUserStore.setGameQualityValue(valueGame);
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
if (valueVideo !== this.videoQualityValue) {
|
|
||||||
this.videoQualityValue = valueVideo;
|
|
||||||
localUserStore.setVideoQualityValue(valueVideo);
|
|
||||||
videoConstraintStore.setFrameRate(valueVideo);
|
|
||||||
}
|
|
||||||
this.closeGameQualityMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
private gotToCreateMapPage() {
|
|
||||||
//const sparkHost = 'https://'+window.location.host.replace('play.', '')+'/choose-map.html';
|
|
||||||
//TODO fix me: this button can to send us on WorkAdventure BO.
|
|
||||||
//const sparkHost = ADMIN_URL + "/getting-started";
|
|
||||||
|
|
||||||
//The redirection must be only on workadventu.re domain
|
|
||||||
//To day the domain staging cannot be use by customer
|
|
||||||
const sparkHost = "https://workadventu.re/getting-started";
|
|
||||||
window.open(sparkHost, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
private closeAll() {
|
|
||||||
this.closeGameQualityMenu();
|
|
||||||
this.closeGameShare();
|
|
||||||
this.gameReportElement.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleFullscreen() {
|
|
||||||
const body = document.querySelector("body");
|
|
||||||
if (body) {
|
|
||||||
if (document.fullscreenElement ?? document.fullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
body.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroyMenu(menu: string) {
|
|
||||||
this.menuElement.getChildByID(menu).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public isDirty(): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enableNotification() {
|
|
||||||
mediaManager.requestNotification().then(() => {
|
|
||||||
if (mediaManager.hasNotification()) {
|
|
||||||
HtmlUtils.getElementByIdOrFail("enableNotification").hidden = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
import { MenuScene } from "./MenuScene";
|
|
||||||
import { gameManager } from "../Game/GameManager";
|
|
||||||
import { blackListManager } from "../../WebRtc/BlackListManager";
|
|
||||||
import { playersStore } from "../../Stores/PlayersStore";
|
|
||||||
|
|
||||||
export const gameReportKey = "gameReport";
|
|
||||||
export const gameReportRessource = "resources/html/gameReport.html";
|
|
||||||
|
|
||||||
export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
|
||||||
private opened: boolean = false;
|
|
||||||
|
|
||||||
private userUuid!: string;
|
|
||||||
private userName!: string | undefined;
|
|
||||||
private anonymous: boolean;
|
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene, anonymous: boolean) {
|
|
||||||
super(scene, -2000, -2000);
|
|
||||||
this.anonymous = anonymous;
|
|
||||||
this.createFromCache(gameReportKey);
|
|
||||||
|
|
||||||
if (this.anonymous) {
|
|
||||||
const divToHide = this.getChildByID("reportSection") as HTMLElement;
|
|
||||||
divToHide.hidden = true;
|
|
||||||
const textToHide = this.getChildByID("askActionP") as HTMLElement;
|
|
||||||
textToHide.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.add.existing(this);
|
|
||||||
MenuScene.revealMenusAfterInit(this, gameReportKey);
|
|
||||||
|
|
||||||
this.addListener("click");
|
|
||||||
this.on("click", (event: MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if ((event?.target as HTMLInputElement).id === "gameReportFormSubmit") {
|
|
||||||
this.submitReport();
|
|
||||||
} else if ((event?.target as HTMLInputElement).id === "gameReportFormCancel") {
|
|
||||||
this.close();
|
|
||||||
} else if ((event?.target as HTMLInputElement).id === "toggleBlockButton") {
|
|
||||||
this.toggleBlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public open(userUuid: string, userName: string | undefined): void {
|
|
||||||
if (this.opened) {
|
|
||||||
this.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.userUuid = userUuid;
|
|
||||||
this.userName = userName;
|
|
||||||
|
|
||||||
const mainEl = this.getChildByID("gameReport") as HTMLElement;
|
|
||||||
this.x = this.getCenteredX(mainEl);
|
|
||||||
this.y = this.getHiddenY(mainEl);
|
|
||||||
|
|
||||||
const gameTitleReport = this.getChildByID("nameReported") as HTMLElement;
|
|
||||||
gameTitleReport.innerText = userName || "";
|
|
||||||
|
|
||||||
const blockButton = this.getChildByID("toggleBlockButton") as HTMLElement;
|
|
||||||
blockButton.innerText = blackListManager.isBlackListed(this.userUuid) ? "Unblock this user" : "Block this user";
|
|
||||||
|
|
||||||
this.opened = true;
|
|
||||||
|
|
||||||
gameManager.getCurrentGameScene().userInputManager.disableControls();
|
|
||||||
|
|
||||||
this.scene.tweens.add({
|
|
||||||
targets: this,
|
|
||||||
y: this.getCenteredY(mainEl),
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
gameManager.getCurrentGameScene().userInputManager.restoreControls();
|
|
||||||
this.opened = false;
|
|
||||||
const mainEl = this.getChildByID("gameReport") as HTMLElement;
|
|
||||||
this.scene.tweens.add({
|
|
||||||
targets: this,
|
|
||||||
y: this.getHiddenY(mainEl),
|
|
||||||
duration: 1000,
|
|
||||||
ease: "Power3",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: into a parent class?
|
|
||||||
private getCenteredX(mainEl: HTMLElement): number {
|
|
||||||
return window.innerWidth / 4 - mainEl.clientWidth / 2;
|
|
||||||
}
|
|
||||||
private getHiddenY(mainEl: HTMLElement): number {
|
|
||||||
return -mainEl.clientHeight - 50;
|
|
||||||
}
|
|
||||||
private getCenteredY(mainEl: HTMLElement): number {
|
|
||||||
return window.innerHeight / 4 - mainEl.clientHeight / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleBlock(): void {
|
|
||||||
!blackListManager.isBlackListed(this.userUuid)
|
|
||||||
? blackListManager.blackList(this.userUuid)
|
|
||||||
: blackListManager.cancelBlackList(this.userUuid);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private submitReport(): void {
|
|
||||||
const gamePError = this.getChildByID("gameReportErr") as HTMLParagraphElement;
|
|
||||||
gamePError.innerText = "";
|
|
||||||
gamePError.style.display = "none";
|
|
||||||
const gameTextArea = this.getChildByID("gameReportInput") as HTMLInputElement;
|
|
||||||
if (!gameTextArea || !gameTextArea.value) {
|
|
||||||
gamePError.innerText = "Report message cannot to be empty.";
|
|
||||||
gamePError.style.display = "block";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gameManager.getCurrentGameScene().connection?.emitReportPlayerMessage(this.userUuid, gameTextArea.value);
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -170,7 +170,7 @@ function createVideoConstraintStore() {
|
|||||||
setFrameRate: (frameRate: number) =>
|
setFrameRate: (frameRate: number) =>
|
||||||
update((constraints) => {
|
update((constraints) => {
|
||||||
constraints.frameRate = { ideal: frameRate };
|
constraints.frameRate = { ideal: frameRate };
|
||||||
|
localUserStore.setVideoQualityValue(frameRate);
|
||||||
return constraints;
|
return constraints;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -324,14 +324,11 @@ export type LocalStreamStoreValue = StreamSuccessValue | StreamErrorValue;
|
|||||||
interface StreamSuccessValue {
|
interface StreamSuccessValue {
|
||||||
type: "success";
|
type: "success";
|
||||||
stream: MediaStream | null;
|
stream: MediaStream | null;
|
||||||
// The constraints that we got (and not the one that have been requested)
|
|
||||||
constraints: MediaStreamConstraints;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StreamErrorValue {
|
interface StreamErrorValue {
|
||||||
type: "error";
|
type: "error";
|
||||||
error: Error;
|
error: Error;
|
||||||
constraints: MediaStreamConstraints;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentStream: MediaStream | null = null;
|
let currentStream: MediaStream | null = null;
|
||||||
@ -339,10 +336,13 @@ let currentStream: MediaStream | null = null;
|
|||||||
/**
|
/**
|
||||||
* Stops the camera from filming
|
* Stops the camera from filming
|
||||||
*/
|
*/
|
||||||
function stopCamera(): void {
|
function applyCameraConstraints(currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean): void {
|
||||||
if (currentStream) {
|
if (currentStream) {
|
||||||
for (const track of currentStream.getVideoTracks()) {
|
for (const track of currentStream.getVideoTracks()) {
|
||||||
track.stop();
|
track.enabled = constraints !== false;
|
||||||
|
if (constraints && constraints !== true) {
|
||||||
|
track.applyConstraints(constraints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,10 +350,16 @@ function stopCamera(): void {
|
|||||||
/**
|
/**
|
||||||
* Stops the microphone from listening
|
* Stops the microphone from listening
|
||||||
*/
|
*/
|
||||||
function stopMicrophone(): void {
|
function applyMicrophoneConstraints(
|
||||||
|
currentStream: MediaStream | null,
|
||||||
|
constraints: MediaTrackConstraints | boolean
|
||||||
|
): void {
|
||||||
if (currentStream) {
|
if (currentStream) {
|
||||||
for (const track of currentStream.getAudioTracks()) {
|
for (const track of currentStream.getAudioTracks()) {
|
||||||
track.stop();
|
track.enabled = constraints !== false;
|
||||||
|
if (constraints && constraints !== true) {
|
||||||
|
track.applyConstraints(constraints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,122 +378,96 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
|
|||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: new Error("Unable to access your camera or microphone. You need to use a HTTPS connection."),
|
error: new Error("Unable to access your camera or microphone. You need to use a HTTPS connection."),
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (isIOS()) {
|
} else if (isIOS()) {
|
||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: new WebviewOnOldIOS(),
|
error: new WebviewOnOldIOS(),
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: new BrowserTooOldError(),
|
error: new BrowserTooOldError(),
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constraints.audio === false) {
|
applyMicrophoneConstraints(currentStream, constraints.audio || false);
|
||||||
stopMicrophone();
|
applyCameraConstraints(currentStream, constraints.video || false);
|
||||||
}
|
|
||||||
if (constraints.video === false) {
|
|
||||||
stopCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constraints.audio === false && constraints.video === false) {
|
if (currentStream === null) {
|
||||||
currentStream = null;
|
// we need to assign a first value to the stream because getUserMedia is async
|
||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: null,
|
stream: null,
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
(async () => {
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
stopMicrophone();
|
|
||||||
stopCamera();
|
|
||||||
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
||||||
set({
|
|
||||||
type: "success",
|
|
||||||
stream: currentStream,
|
|
||||||
constraints,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
if (constraints.video !== false) {
|
|
||||||
console.info(
|
|
||||||
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
|
||||||
$mediaStreamConstraintsStore,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
// TODO: does it make sense to pop this error when retrying?
|
|
||||||
set({
|
|
||||||
type: "error",
|
|
||||||
error: e,
|
|
||||||
constraints,
|
|
||||||
});
|
|
||||||
// Let's try without video constraints
|
|
||||||
requestedCameraState.disableWebcam();
|
|
||||||
} else {
|
|
||||||
console.info(
|
|
||||||
"Error. Unable to get microphone and/or camera access.",
|
|
||||||
$mediaStreamConstraintsStore,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
set({
|
|
||||||
type: "error",
|
|
||||||
error: e,
|
|
||||||
constraints,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*constraints.video = false;
|
|
||||||
if (constraints.audio === false) {
|
|
||||||
console.info("Error. Unable to get microphone and/or camera access.", $mediaStreamConstraintsStore, e);
|
|
||||||
set({
|
|
||||||
type: 'error',
|
|
||||||
error: e,
|
|
||||||
constraints
|
|
||||||
});
|
|
||||||
// Let's make as if the user did not ask.
|
|
||||||
requestedCameraState.disableWebcam();
|
|
||||||
} else {
|
|
||||||
console.info("Error. Unable to get microphone and/or camera access. Trying audio only.", $mediaStreamConstraintsStore, e);
|
|
||||||
try {
|
try {
|
||||||
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
set({
|
set({
|
||||||
type: 'success',
|
type: "success",
|
||||||
stream: currentStream,
|
stream: currentStream,
|
||||||
constraints
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} catch (e2) {
|
} catch (e) {
|
||||||
console.info("Error. Unable to get microphone fallback access.", $mediaStreamConstraintsStore, e2);
|
if (constraints.video !== false) {
|
||||||
set({
|
console.info(
|
||||||
type: 'error',
|
"Error. Unable to get microphone and/or camera access. Trying audio only.",
|
||||||
error: e,
|
$mediaStreamConstraintsStore,
|
||||||
constraints
|
e
|
||||||
});
|
);
|
||||||
|
// TODO: does it make sense to pop this error when retrying?
|
||||||
|
set({
|
||||||
|
type: "error",
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
// Let's try without video constraints
|
||||||
|
requestedCameraState.disableWebcam();
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
"Error. Unable to get microphone and/or camera access.",
|
||||||
|
$mediaStreamConstraintsStore,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
set({
|
||||||
|
type: "error",
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}*/
|
})();
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export interface ObtainedMediaStreamConstraints {
|
||||||
|
video: boolean;
|
||||||
|
audio: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let obtainedMediaConstraint: ObtainedMediaStreamConstraints = {
|
||||||
|
audio: false,
|
||||||
|
video: false,
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* A store containing the real active media constrained (not the one requested by the user, but the one we got from the system)
|
* A store containing the actual states of audio and video (activated or deactivated)
|
||||||
*/
|
*/
|
||||||
export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStreamStore) => {
|
export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstraints>, ObtainedMediaStreamConstraints>(
|
||||||
return $localStreamStore.constraints;
|
mediaStreamConstraintsStore,
|
||||||
});
|
($mediaStreamConstraintsStore, set) => {
|
||||||
|
const newObtainedMediaConstraint = {
|
||||||
|
video: !!$mediaStreamConstraintsStore.video,
|
||||||
|
audio: !!$mediaStreamConstraintsStore.audio,
|
||||||
|
};
|
||||||
|
if (newObtainedMediaConstraint !== obtainedMediaConstraint) {
|
||||||
|
obtainedMediaConstraint = newObtainedMediaConstraint;
|
||||||
|
set(obtainedMediaConstraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device list
|
* Device list
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
import Timeout = NodeJS.Timeout;
|
import Timeout = NodeJS.Timeout;
|
||||||
|
import { userIsAdminStore } from "./GameStore";
|
||||||
|
import { CONTACT_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export const menuIconVisible = writable(false);
|
export const menuIconVisiblilityStore = writable(false);
|
||||||
|
export const menuVisiblilityStore = writable(false);
|
||||||
|
export const menuInputFocusStore = writable(false);
|
||||||
|
|
||||||
let warningContainerTimeout: Timeout | null = null;
|
let warningContainerTimeout: Timeout | null = null;
|
||||||
function createWarningContainerStore() {
|
function createWarningContainerStore() {
|
||||||
@ -21,3 +25,90 @@ function createWarningContainerStore() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const warningContainerStore = createWarningContainerStore();
|
export const warningContainerStore = createWarningContainerStore();
|
||||||
|
|
||||||
|
export enum SubMenusInterface {
|
||||||
|
settings = "Settings",
|
||||||
|
profile = "Profile",
|
||||||
|
createMap = "Create a Map",
|
||||||
|
aboutRoom = "About the Room",
|
||||||
|
globalMessages = "Global Messages",
|
||||||
|
contact = "Contact",
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubMenusStore() {
|
||||||
|
const { subscribe, update } = writable<string[]>([
|
||||||
|
SubMenusInterface.settings,
|
||||||
|
SubMenusInterface.profile,
|
||||||
|
SubMenusInterface.createMap,
|
||||||
|
SubMenusInterface.aboutRoom,
|
||||||
|
SubMenusInterface.globalMessages,
|
||||||
|
SubMenusInterface.contact,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
addMenu(menuCommand: string) {
|
||||||
|
update((menuList: string[]) => {
|
||||||
|
if (!menuList.find((menu) => menu === menuCommand)) {
|
||||||
|
menuList.push(menuCommand);
|
||||||
|
}
|
||||||
|
return menuList;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeMenu(menuCommand: string) {
|
||||||
|
update((menuList: string[]) => {
|
||||||
|
const index = menuList.findIndex((menu) => menu === menuCommand);
|
||||||
|
if (index !== -1) {
|
||||||
|
menuList.splice(index, 1);
|
||||||
|
}
|
||||||
|
return menuList;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const subMenusStore = createSubMenusStore();
|
||||||
|
|
||||||
|
function checkSubMenuToShow() {
|
||||||
|
if (!get(userIsAdminStore)) {
|
||||||
|
subMenusStore.removeMenu(SubMenusInterface.globalMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONTACT_URL === undefined) {
|
||||||
|
subMenusStore.removeMenu(SubMenusInterface.contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSubMenuToShow();
|
||||||
|
|
||||||
|
export const customMenuIframe = new Map<string, { url: string; allowApi: boolean }>();
|
||||||
|
|
||||||
|
export function handleMenuRegistrationEvent(
|
||||||
|
menuName: string,
|
||||||
|
iframeUrl: string | undefined = undefined,
|
||||||
|
source: string | undefined = undefined,
|
||||||
|
options: { allowApi: boolean }
|
||||||
|
) {
|
||||||
|
if (get(subMenusStore).includes(menuName)) {
|
||||||
|
console.warn("The menu " + menuName + " already exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subMenusStore.addMenu(menuName);
|
||||||
|
|
||||||
|
if (iframeUrl !== undefined) {
|
||||||
|
const url = new URL(iframeUrl, source);
|
||||||
|
customMenuIframe.set(menuName, { url: url.toString(), allowApi: options.allowApi });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleMenuUnregisterEvent(menuName: string) {
|
||||||
|
const subMenuGeneral: string[] = Object.values(SubMenusInterface);
|
||||||
|
if (subMenuGeneral.includes(menuName)) {
|
||||||
|
console.warn("The menu " + menuName + " is a mandatory menu. It can't be remove");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subMenusStore.removeMenu(menuName);
|
||||||
|
customMenuIframe.delete(menuName);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { derived, get, Readable, readable, writable, Writable } from "svelte/store";
|
import { derived, Readable, readable, writable } from "svelte/store";
|
||||||
import { peerStore } from "./PeerStore";
|
import { peerStore } from "./PeerStore";
|
||||||
import type { LocalStreamStoreValue } from "./MediaStore";
|
import type { LocalStreamStoreValue } from "./MediaStore";
|
||||||
import { DivImportance } from "../WebRtc/LayoutManager";
|
|
||||||
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
||||||
|
|
||||||
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
@ -106,7 +105,6 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: null,
|
stream: null,
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -121,7 +119,6 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: new Error("Your browser does not support sharing screen"),
|
error: new Error("Your browser does not support sharing screen"),
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -141,10 +138,6 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: null,
|
stream: null,
|
||||||
constraints: {
|
|
||||||
video: false,
|
|
||||||
audio: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -152,7 +145,6 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
set({
|
set({
|
||||||
type: "success",
|
type: "success",
|
||||||
stream: currentStream,
|
stream: currentStream,
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -162,7 +154,6 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
set({
|
set({
|
||||||
type: "error",
|
type: "error",
|
||||||
error: e,
|
error: e,
|
||||||
constraints,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@ -184,7 +175,6 @@ export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set)
|
|||||||
export interface ScreenSharingLocalMedia {
|
export interface ScreenSharingLocalMedia {
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
stream: MediaStream | null;
|
stream: MediaStream | null;
|
||||||
//subscribe(this: void, run: Subscriber<ScreenSharingLocalMedia>, invalidate?: (value?: ScreenSharingLocalMedia) => void): Unsubscriber;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export const showReportScreenStore = writable<{ userId: number; userName: string } | null>(null);
|
export const userReportEmpty = {
|
||||||
|
userId: 0,
|
||||||
|
userName: "Empty",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showReportScreenStore = writable<{ userId: number; userName: string }>(userReportEmpty);
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { derived } from "svelte/store";
|
import { derived } from "svelte/store";
|
||||||
import { consoleGlobalMessageManagerFocusStore } from "./ConsoleGlobalMessageManagerStore";
|
import { menuInputFocusStore } from "./MenuStore";
|
||||||
import { chatInputFocusStore } from "./ChatStore";
|
import { chatInputFocusStore } from "./ChatStore";
|
||||||
|
import { showReportScreenStore, userReportEmpty } from "./ShowReportScreenStore";
|
||||||
|
|
||||||
//derived from the focus on Menu, ConsoleGlobal, Chat and ...
|
//derived from the focus on Menu, ConsoleGlobal, Chat and ...
|
||||||
export const enableUserInputsStore = derived(
|
export const enableUserInputsStore = derived(
|
||||||
[consoleGlobalMessageManagerFocusStore, chatInputFocusStore],
|
[menuInputFocusStore, chatInputFocusStore, showReportScreenStore],
|
||||||
([$consoleGlobalMessageManagerFocusStore, $chatInputFocusStore]) => {
|
([$menuInputFocusStore, $chatInputFocusStore, $showReportScreenStore]) => {
|
||||||
return !$consoleGlobalMessageManagerFocusStore && !$chatInputFocusStore;
|
return !$menuInputFocusStore && !$chatInputFocusStore && !($showReportScreenStore !== userReportEmpty);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -48,6 +48,10 @@ class CoWebsiteManager {
|
|||||||
this.cowebsiteDiv.style.width = width + "px";
|
this.cowebsiteDiv.style.width = width + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set widthPercent(width: number) {
|
||||||
|
this.cowebsiteDiv.style.width = width + "%";
|
||||||
|
}
|
||||||
|
|
||||||
get height(): number {
|
get height(): number {
|
||||||
return this.cowebsiteDiv.clientHeight;
|
return this.cowebsiteDiv.clientHeight;
|
||||||
}
|
}
|
||||||
@ -162,7 +166,7 @@ class CoWebsiteManager {
|
|||||||
return iframe;
|
return iframe;
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string): void {
|
public loadCoWebsite(url: string, base: string, allowApi?: boolean, allowPolicy?: string, widthPercent?: number): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteMainDom.innerHTML = ``;
|
this.cowebsiteMainDom.innerHTML = ``;
|
||||||
|
|
||||||
@ -186,6 +190,9 @@ class CoWebsiteManager {
|
|||||||
.then(() => Promise.race([onloadPromise, onTimeoutPromise]))
|
.then(() => Promise.race([onloadPromise, onTimeoutPromise]))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.open();
|
this.open();
|
||||||
|
if (widthPercent) {
|
||||||
|
this.widthPercent = widthPercent;
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fire();
|
this.fire();
|
||||||
}, animationTime);
|
}, animationTime);
|
||||||
@ -199,13 +206,16 @@ class CoWebsiteManager {
|
|||||||
/**
|
/**
|
||||||
* Just like loadCoWebsite but the div can be filled by the user.
|
* Just like loadCoWebsite but the div can be filled by the user.
|
||||||
*/
|
*/
|
||||||
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
|
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>, widthPercent?: number): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteMainDom.innerHTML = ``;
|
this.cowebsiteMainDom.innerHTML = ``;
|
||||||
this.currentOperationPromise = this.currentOperationPromise
|
this.currentOperationPromise = this.currentOperationPromise
|
||||||
.then(() => callback(this.cowebsiteMainDom))
|
.then(() => callback(this.cowebsiteMainDom))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.open();
|
this.open();
|
||||||
|
if (widthPercent) {
|
||||||
|
this.widthPercent = widthPercent;
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fire();
|
this.fire();
|
||||||
}, animationTime);
|
}, animationTime);
|
||||||
|
@ -25,7 +25,7 @@ export class HtmlUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static escapeHtml(html: string): string {
|
public static escapeHtml(html: string): string {
|
||||||
const text = document.createTextNode(html.replace(/(\r\n|\r|\n)/g, "<br/>"));
|
const text = document.createTextNode(html);
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
p.appendChild(text);
|
p.appendChild(text);
|
||||||
return p.innerHTML;
|
return p.innerHTML;
|
||||||
|
@ -82,7 +82,7 @@ class JitsiFactory {
|
|||||||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string, jitsiWidth?: number): void {
|
||||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||||
// Jitsi meet external API maintains some data in local storage
|
// Jitsi meet external API maintains some data in local storage
|
||||||
// which is sent via the appData URL parameter when joining a
|
// which is sent via the appData URL parameter when joining a
|
||||||
@ -120,7 +120,7 @@ class JitsiFactory {
|
|||||||
this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback);
|
this.jitsiApi.addListener('audioMuteStatusChanged', this.audioCallback);
|
||||||
this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback);
|
this.jitsiApi.addListener('videoMuteStatusChanged', this.videoCallback);
|
||||||
});
|
});
|
||||||
}));
|
}), jitsiWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
|
@ -11,6 +11,7 @@ import { cowebsiteCloseButtonId } from "./CoWebsiteManager";
|
|||||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
||||||
import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||||
|
|
||||||
export class MediaManager {
|
export class MediaManager {
|
||||||
startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||||
@ -187,7 +188,11 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public hasNotification(): boolean {
|
public hasNotification(): boolean {
|
||||||
return Notification.permission === "granted";
|
if (Notification.permission === "granted") {
|
||||||
|
return localUserStore.getNotification() === "granted";
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public requestNotification() {
|
public requestNotification() {
|
||||||
|
@ -4,16 +4,12 @@ import type {
|
|||||||
} from "../Connexion/ConnexionModels";
|
} from "../Connexion/ConnexionModels";
|
||||||
import { mediaManager, StartScreenSharingCallback, StopScreenSharingCallback } from "./MediaManager";
|
import { mediaManager, StartScreenSharingCallback, StopScreenSharingCallback } from "./MediaManager";
|
||||||
import { ScreenSharingPeer } from "./ScreenSharingPeer";
|
import { ScreenSharingPeer } from "./ScreenSharingPeer";
|
||||||
import { MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer } from "./VideoPeer";
|
import { VideoPeer } from "./VideoPeer";
|
||||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||||
import { blackListManager } from "./BlackListManager";
|
import { blackListManager } from "./BlackListManager";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
|
||||||
import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore";
|
import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore";
|
||||||
import { discussionManager } from "./DiscussionManager";
|
|
||||||
import { playersStore } from "../Stores/PlayersStore";
|
import { playersStore } from "../Stores/PlayersStore";
|
||||||
import { newChatMessageStore } from "../Stores/ChatStore";
|
|
||||||
import { isMobile } from "../Enum/EnvironmentVariable";
|
|
||||||
|
|
||||||
export interface UserSimplePeerInterface {
|
export interface UserSimplePeerInterface {
|
||||||
userId: number;
|
userId: number;
|
||||||
@ -46,19 +42,14 @@ export class SimplePeer {
|
|||||||
private lastWebrtcUserName: string | undefined;
|
private lastWebrtcUserName: string | undefined;
|
||||||
private lastWebrtcPassword: string | undefined;
|
private lastWebrtcPassword: string | undefined;
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
constructor(private Connection: RoomConnection) {
|
||||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||||
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
||||||
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
|
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
|
||||||
|
|
||||||
this.unsubscribers.push(
|
|
||||||
localStreamStore.subscribe((streamResult) => {
|
|
||||||
this.sendLocalVideoStream(streamResult);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let localScreenCapture: MediaStream | null = null;
|
let localScreenCapture: MediaStream | null = null;
|
||||||
|
|
||||||
|
//todo
|
||||||
this.unsubscribers.push(
|
this.unsubscribers.push(
|
||||||
screenSharingLocalStreamStore.subscribe((streamResult) => {
|
screenSharingLocalStreamStore.subscribe((streamResult) => {
|
||||||
if (streamResult.type === "error") {
|
if (streamResult.type === "error") {
|
||||||
@ -126,19 +117,14 @@ export class SimplePeer {
|
|||||||
if (!user.initiator) {
|
if (!user.initiator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const streamResult = get(localStreamStore);
|
|
||||||
let stream: MediaStream | null = null;
|
|
||||||
if (streamResult.type === "success" && streamResult.stream) {
|
|
||||||
stream = streamResult.stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createPeerConnection(user, stream);
|
this.createPeerConnection(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create peer connection to bind users
|
* create peer connection to bind users
|
||||||
*/
|
*/
|
||||||
private createPeerConnection(user: UserSimplePeerInterface, localStream: MediaStream | null): VideoPeer | null {
|
private createPeerConnection(user: UserSimplePeerInterface): VideoPeer | null {
|
||||||
const peerConnection = this.PeerConnectionArray.get(user.userId);
|
const peerConnection = this.PeerConnectionArray.get(user.userId);
|
||||||
if (peerConnection) {
|
if (peerConnection) {
|
||||||
if (peerConnection.destroyed) {
|
if (peerConnection.destroyed) {
|
||||||
@ -160,7 +146,7 @@ export class SimplePeer {
|
|||||||
this.lastWebrtcUserName = user.webRtcUser;
|
this.lastWebrtcUserName = user.webRtcUser;
|
||||||
this.lastWebrtcPassword = user.webRtcPassword;
|
this.lastWebrtcPassword = user.webRtcPassword;
|
||||||
|
|
||||||
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection, localStream);
|
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection);
|
||||||
|
|
||||||
peer.toClose = false;
|
peer.toClose = false;
|
||||||
// When a connection is established to a video stream, and if a screen sharing is taking place,
|
// When a connection is established to a video stream, and if a screen sharing is taking place,
|
||||||
@ -204,7 +190,7 @@ export class SimplePeer {
|
|||||||
if (!peerConnexionDeleted) {
|
if (!peerConnexionDeleted) {
|
||||||
throw "Error to delete peer connection";
|
throw "Error to delete peer connection";
|
||||||
}
|
}
|
||||||
this.createPeerConnection(user, stream);
|
this.createPeerConnection(user);
|
||||||
} else {
|
} else {
|
||||||
peerConnection.toClose = false;
|
peerConnection.toClose = false;
|
||||||
}
|
}
|
||||||
@ -282,7 +268,6 @@ export class SimplePeer {
|
|||||||
*/
|
*/
|
||||||
private closeScreenSharingConnection(userId: number) {
|
private closeScreenSharingConnection(userId: number) {
|
||||||
try {
|
try {
|
||||||
//mediaManager.removeActiveScreenSharingVideo("" + userId);
|
|
||||||
const peer = this.PeerScreenSharingConnectionArray.get(userId);
|
const peer = this.PeerScreenSharingConnectionArray.get(userId);
|
||||||
if (peer === undefined) {
|
if (peer === undefined) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -295,12 +280,6 @@ export class SimplePeer {
|
|||||||
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
||||||
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
|
|
||||||
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
|
||||||
/*if(!this.PeerScreenSharingConnectionArray.delete(userId)){
|
|
||||||
throw 'Couln\'t delete peer screen sharing connexion';
|
|
||||||
}*/
|
|
||||||
//console.log('Nb users in peerConnectionArray '+this.PeerConnectionArray.size);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("closeConnection", err);
|
console.error("closeConnection", err);
|
||||||
}
|
}
|
||||||
@ -330,13 +309,7 @@ export class SimplePeer {
|
|||||||
try {
|
try {
|
||||||
//if offer type, create peer connection
|
//if offer type, create peer connection
|
||||||
if (data.signal.type === "offer") {
|
if (data.signal.type === "offer") {
|
||||||
const streamResult = get(localStreamStore);
|
this.createPeerConnection(data);
|
||||||
let stream: MediaStream | null = null;
|
|
||||||
if (streamResult.type === "success" && streamResult.stream) {
|
|
||||||
stream = streamResult.stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createPeerConnection(data, stream);
|
|
||||||
}
|
}
|
||||||
const peer = this.PeerConnectionArray.get(data.userId);
|
const peer = this.PeerConnectionArray.get(data.userId);
|
||||||
if (peer !== undefined) {
|
if (peer !== undefined) {
|
||||||
@ -379,48 +352,10 @@ export class SimplePeer {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
|
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
|
||||||
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
||||||
//this.PeerScreenSharingConnectionArray.delete(data.userId);
|
|
||||||
this.receiveWebrtcScreenSharingSignal(data);
|
this.receiveWebrtcScreenSharingSignal(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) {
|
|
||||||
try {
|
|
||||||
const PeerConnection = this.PeerConnectionArray.get(userId);
|
|
||||||
if (!PeerConnection) {
|
|
||||||
throw new Error("While adding media, cannot find user with ID " + userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerConnection.write(
|
|
||||||
new Buffer(
|
|
||||||
JSON.stringify({
|
|
||||||
type: MESSAGE_TYPE_CONSTRAINT,
|
|
||||||
...streamResult.constraints,
|
|
||||||
isMobile: isMobile(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (streamResult.type === "error") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const localStream: MediaStream | null = streamResult.stream;
|
|
||||||
|
|
||||||
if (!localStream) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const track of localStream.getTracks()) {
|
|
||||||
//todo: this is a ugly hack to reduce the amount of error in console. Find a better way.
|
|
||||||
if ((track as any).added !== undefined) continue; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
(track as any).added = true; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
||||||
PeerConnection.addTrack(track, localStream);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`pushVideoToRemoteUser => ${userId}`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) {
|
private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) {
|
||||||
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
|
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
|
||||||
if (!PeerConnection) {
|
if (!PeerConnection) {
|
||||||
@ -433,12 +368,6 @@ export class SimplePeer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendLocalVideoStream(streamResult: LocalStreamStoreValue) {
|
|
||||||
for (const user of this.Users) {
|
|
||||||
this.pushVideoToRemoteUser(user.userId, streamResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered locally when clicking on the screen sharing button
|
* Triggered locally when clicking on the screen sharing button
|
||||||
*/
|
*/
|
||||||
@ -492,8 +421,6 @@ export class SimplePeer {
|
|||||||
|
|
||||||
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
|
if (!PeerConnectionScreenSharing.isReceivingScreenSharingStream()) {
|
||||||
PeerConnectionScreenSharing.destroy();
|
PeerConnectionScreenSharing.destroy();
|
||||||
//Comment this peer connection because if we delete and try to reshare screen, the RTCPeerConnection send renegotiate event. This array will be remove when user left circle discussion
|
|
||||||
//this.PeerScreenSharingConnectionArray.delete(userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,12 @@ import { blackListManager } from "./BlackListManager";
|
|||||||
import type { Subscription } from "rxjs";
|
import type { Subscription } from "rxjs";
|
||||||
import type { UserSimplePeerInterface } from "./SimplePeer";
|
import type { UserSimplePeerInterface } from "./SimplePeer";
|
||||||
import { get, readable, Readable, Unsubscriber } from "svelte/store";
|
import { get, readable, Readable, Unsubscriber } from "svelte/store";
|
||||||
import { obtainedMediaConstraintIsMobileStore, obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
import {
|
||||||
|
localStreamStore,
|
||||||
|
obtainedMediaConstraintIsMobileStore,
|
||||||
|
obtainedMediaConstraintStore,
|
||||||
|
ObtainedMediaStreamConstraints,
|
||||||
|
} from "../Stores/MediaStore";
|
||||||
import { playersStore } from "../Stores/PlayersStore";
|
import { playersStore } from "../Stores/PlayersStore";
|
||||||
import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore";
|
import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore";
|
||||||
import { getIceServersConfig } from "../Components/Video/utils";
|
import { getIceServersConfig } from "../Components/Video/utils";
|
||||||
@ -34,16 +39,17 @@ export class VideoPeer extends Peer {
|
|||||||
private onUnBlockSubscribe: Subscription;
|
private onUnBlockSubscribe: Subscription;
|
||||||
public readonly streamStore: Readable<MediaStream | null>;
|
public readonly streamStore: Readable<MediaStream | null>;
|
||||||
public readonly statusStore: Readable<PeerStatus>;
|
public readonly statusStore: Readable<PeerStatus>;
|
||||||
public readonly constraintsStore: Readable<MediaStreamConstraints | null>;
|
public readonly constraintsStore: Readable<ObtainedMediaStreamConstraints | null>;
|
||||||
private newMessageunsubscriber: Unsubscriber | null = null;
|
private newMessageunsubscriber: Unsubscriber | null = null;
|
||||||
private closing: Boolean = false; //this is used to prevent destroy() from being called twice
|
private closing: Boolean = false; //this is used to prevent destroy() from being called twice
|
||||||
|
private localStreamStoreSubscribe: Unsubscriber;
|
||||||
|
private obtainedMediaConstraintStoreSubscribe: Unsubscriber;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public user: UserSimplePeerInterface,
|
public user: UserSimplePeerInterface,
|
||||||
initiator: boolean,
|
initiator: boolean,
|
||||||
public readonly userName: string,
|
public readonly userName: string,
|
||||||
private connection: RoomConnection,
|
private connection: RoomConnection
|
||||||
localStream: MediaStream | null
|
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
initiator,
|
initiator,
|
||||||
@ -60,27 +66,15 @@ export class VideoPeer extends Peer {
|
|||||||
const onStream = (stream: MediaStream | null) => {
|
const onStream = (stream: MediaStream | null) => {
|
||||||
set(stream);
|
set(stream);
|
||||||
};
|
};
|
||||||
const onData = (chunk: Buffer) => {
|
|
||||||
this.on("data", (chunk: Buffer) => {
|
|
||||||
const message = JSON.parse(chunk.toString("utf8"));
|
|
||||||
if (message.type === MESSAGE_TYPE_CONSTRAINT) {
|
|
||||||
if (!message.video) {
|
|
||||||
set(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.on("stream", onStream);
|
this.on("stream", onStream);
|
||||||
this.on("data", onData);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
this.off("stream", onStream);
|
this.off("stream", onStream);
|
||||||
this.off("data", onData);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.constraintsStore = readable<MediaStreamConstraints | null>(null, (set) => {
|
this.constraintsStore = readable<ObtainedMediaStreamConstraints | null>(null, (set) => {
|
||||||
const onData = (chunk: Buffer) => {
|
const onData = (chunk: Buffer) => {
|
||||||
const message = JSON.parse(chunk.toString("utf8"));
|
const message = JSON.parse(chunk.toString("utf8"));
|
||||||
if (message.type === MESSAGE_TYPE_CONSTRAINT) {
|
if (message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||||
@ -191,7 +185,6 @@ export class VideoPeer extends Peer {
|
|||||||
this._onFinish();
|
this._onFinish();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pushVideoToRemoteUser(localStream);
|
|
||||||
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userUuid) => {
|
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userUuid) => {
|
||||||
if (userUuid === this.userUuid) {
|
if (userUuid === this.userUuid) {
|
||||||
this.toggleRemoteStream(false);
|
this.toggleRemoteStream(false);
|
||||||
@ -208,6 +201,21 @@ export class VideoPeer extends Peer {
|
|||||||
if (blackListManager.isBlackListed(this.userUuid)) {
|
if (blackListManager.isBlackListed(this.userUuid)) {
|
||||||
this.sendBlockMessage(true);
|
this.sendBlockMessage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.localStreamStoreSubscribe = localStreamStore.subscribe((streamValue) => {
|
||||||
|
if (streamValue.type === "success" && streamValue.stream) this.addStream(streamValue.stream);
|
||||||
|
});
|
||||||
|
this.obtainedMediaConstraintStoreSubscribe = obtainedMediaConstraintStore.subscribe((constraints) => {
|
||||||
|
this.write(
|
||||||
|
new Buffer(
|
||||||
|
JSON.stringify({
|
||||||
|
type: MESSAGE_TYPE_CONSTRAINT,
|
||||||
|
...constraints,
|
||||||
|
isMobile: isMobile(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendBlockMessage(blocking: boolean) {
|
private sendBlockMessage(blocking: boolean) {
|
||||||
@ -264,6 +272,8 @@ export class VideoPeer extends Peer {
|
|||||||
this.onUnBlockSubscribe.unsubscribe();
|
this.onUnBlockSubscribe.unsubscribe();
|
||||||
if (this.newMessageunsubscriber) this.newMessageunsubscriber();
|
if (this.newMessageunsubscriber) this.newMessageunsubscriber();
|
||||||
chatMessagesStore.addOutcomingUser(this.userId);
|
chatMessagesStore.addOutcomingUser(this.userId);
|
||||||
|
if (this.localStreamStoreSubscribe) this.localStreamStoreSubscribe();
|
||||||
|
if (this.obtainedMediaConstraintStoreSubscribe) this.obtainedMediaConstraintStoreSubscribe();
|
||||||
super.destroy();
|
super.destroy();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("VideoPeer::destroy", err);
|
console.error("VideoPeer::destroy", err);
|
||||||
@ -281,28 +291,4 @@ export class VideoPeer extends Peer {
|
|||||||
this.once("connect", destroySoon);
|
this.once("connect", destroySoon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private pushVideoToRemoteUser(localStream: MediaStream | null) {
|
|
||||||
try {
|
|
||||||
this.write(
|
|
||||||
new Buffer(
|
|
||||||
JSON.stringify({
|
|
||||||
type: MESSAGE_TYPE_CONSTRAINT,
|
|
||||||
...get(obtainedMediaConstraintStore),
|
|
||||||
isMobile: isMobile(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!localStream) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const track of localStream.getTracks()) {
|
|
||||||
this.addTrack(track, localStream);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`pushVideoToRemoteUser => ${this.userId}`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import WebFontLoaderPlugin from "phaser3-rex-plugins/plugins/webfontloader-plugi
|
|||||||
import OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
import OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||||
import { EntryScene } from "./Phaser/Login/EntryScene";
|
import { EntryScene } from "./Phaser/Login/EntryScene";
|
||||||
import { coWebsiteManager } from "./WebRtc/CoWebsiteManager";
|
import { coWebsiteManager } from "./WebRtc/CoWebsiteManager";
|
||||||
import { MenuScene } from "./Phaser/Menu/MenuScene";
|
|
||||||
import { localUserStore } from "./Connexion/LocalUserStore";
|
import { localUserStore } from "./Connexion/LocalUserStore";
|
||||||
import { ErrorScene } from "./Phaser/Reconnecting/ErrorScene";
|
import { ErrorScene } from "./Phaser/Reconnecting/ErrorScene";
|
||||||
import { iframeListener } from "./Api/IframeListener";
|
import { iframeListener } from "./Api/IframeListener";
|
||||||
@ -97,7 +96,6 @@ const config: GameConfig = {
|
|||||||
ReconnectingScene,
|
ReconnectingScene,
|
||||||
ErrorScene,
|
ErrorScene,
|
||||||
CustomizeScene,
|
CustomizeScene,
|
||||||
MenuScene,
|
|
||||||
],
|
],
|
||||||
//resolution: window.devicePixelRatio / 2,
|
//resolution: window.devicePixelRatio / 2,
|
||||||
fps: fps,
|
fps: fps,
|
||||||
|