diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d23f4b9..710b85fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## 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 - New scripting API features : - Use `WA.room.loadTileset(url: string) : Promise` to load a tileset from a JSON file. diff --git a/README.md b/README.md index 322f06ba..ba9e70ce 100644 --- a/README.md +++ b/README.md @@ -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 ``` +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 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). diff --git a/back/package.json b/back/package.json index 8a1e445e..58399ad7 100644 --- a/back/package.json +++ b/back/package.json @@ -54,7 +54,6 @@ "prom-client": "^12.0.0", "query-string": "^6.13.3", "redis": "^3.1.2", - "systeminformation": "^4.31.1", "uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0", "uuidv4": "^6.0.7" }, diff --git a/back/yarn.lock b/back/yarn.lock index 98d675ee..2265f4da 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -554,7 +554,7 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.1.2" -chownr@^1.1.1: +chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -1159,7 +1159,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fs-minipass@^1.2.5: +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" 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" 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" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" 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" yallist "^3.0.0" -minizlib@^1.2.1: +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== @@ -1992,7 +1992,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" 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" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 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== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^1.0.0: version "1.1.0" @@ -2578,7 +2578,7 @@ rxjs@^6.6.7: dependencies: 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" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -2962,11 +2962,6 @@ supports-color@^7.1.0: dependencies: 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: version "5.4.6" 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" tar@^4.4.2: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" tdigest@^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" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== -yallist@^3.0.0, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json index 2d25c58a..5d9ef0c6 100644 --- a/benchmark/package-lock.json +++ b/benchmark/package-lock.json @@ -429,9 +429,9 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-type": { "version": "1.1.0", diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 8dcffe52..92541451 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -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" path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" path-type@^1.0.0: version "1.1.0" diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 494c72b8..a2e8970a 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -101,6 +101,7 @@ }, "redis": { "image": "redis:6", + "ports": [6379] } }, "config": { diff --git a/docs/maps/animations.md b/docs/maps/animations.md index 276f2332..6ac69e0c 100644 --- a/docs/maps/animations.md +++ b/docs/maps/animations.md @@ -7,14 +7,14 @@ not overwrite existing ones) and click on the animation editor:
- +
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:
- +
The tile animation editor
@@ -24,7 +24,7 @@ You can preview animations directly in Tiled, using the "Show tile animations" o
- +
The Show Tile Animations option
diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md index 930caebe..f2b582a5 100644 --- a/docs/maps/api-deprecated.md +++ b/docs/maps/api-deprecated.md @@ -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.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.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`. \ No newline at end of file diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 96b62fd2..d1a26d2f 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -7,7 +7,7 @@ If you use group layers in your map, to reference a layer in a group you will ne Example :
- +
@@ -28,7 +28,7 @@ Listens to the position of the current user. The event is triggered when the use
- +
The `zone` property, applied on a layer
@@ -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`.
- +
diff --git a/docs/maps/api-state.md b/docs/maps/api-state.md index 87a8b3aa..1cc4f7fb 100644 --- a/docs/maps/api-state.md +++ b/docs/maps/api-state.md @@ -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.loadVariable(key : string) : unknown +WA.state.hasVariable(key : string) : boolean WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription WA.state.[any property]: unknown ``` @@ -70,7 +71,7 @@ Each object will represent a variable.
- +
diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md index 89d46932..dc701500 100644 --- a/docs/maps/api-ui.md +++ b/docs/maps/api-ui.md @@ -9,10 +9,10 @@ You can position this popup by using a "rectangle" object in Tiled that you will
- +
- +
@@ -68,25 +68,53 @@ WA.room.onLeaveZone('myZone', () => { ### 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. -Example: +
+
+ +
+
+ +
+
+Example: ```javascript +const menu = WA.ui.registerMenuCommand('menu test', + { + callback: () => { + WA.chat.sendChatMessage('test'); + } + }) -WA.ui.registerMenuCommand("test", () => { - WA.chat.sendChatMessage("test clicked", "menu cmd") -}) - +// Some time later, if you want to remove the menu: +menu.remove(); ``` -
- -
+Please note that `registerMenuCommand` returns an object of the `Menu` class. + +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).
- +
Example: diff --git a/docs/maps/images/anims/animation_editor.png b/docs/maps/images/anims/animation_editor.png new file mode 100644 index 00000000..92d00aa7 Binary files /dev/null and b/docs/maps/images/anims/animation_editor.png differ diff --git a/docs/maps/images/anims/camera.png b/docs/maps/images/anims/camera.png new file mode 100644 index 00000000..c96d0b52 Binary files /dev/null and b/docs/maps/images/anims/camera.png differ diff --git a/docs/maps/images/anims/settings_show_animations.png b/docs/maps/images/anims/settings_show_animations.png new file mode 100644 index 00000000..bf45e116 Binary files /dev/null and b/docs/maps/images/anims/settings_show_animations.png differ diff --git a/docs/maps/images/collides-1.png b/docs/maps/images/collides-1.png new file mode 100644 index 00000000..54582d05 Binary files /dev/null and b/docs/maps/images/collides-1.png differ diff --git a/docs/maps/images/collides-2.png b/docs/maps/images/collides-2.png new file mode 100644 index 00000000..a34b18dc Binary files /dev/null and b/docs/maps/images/collides-2.png differ diff --git a/docs/maps/images/collides-3.png b/docs/maps/images/collides-3.png new file mode 100644 index 00000000..e775e396 Binary files /dev/null and b/docs/maps/images/collides-3.png differ diff --git a/docs/maps/images/collides-4.png b/docs/maps/images/collides-4.png new file mode 100644 index 00000000..024e1b86 Binary files /dev/null and b/docs/maps/images/collides-4.png differ diff --git a/docs/maps/images/custom-menu-iframe.png b/docs/maps/images/custom-menu-iframe.png new file mode 100644 index 00000000..9df2aa5f Binary files /dev/null and b/docs/maps/images/custom-menu-iframe.png differ diff --git a/docs/maps/images/custom-menu-navbar.png b/docs/maps/images/custom-menu-navbar.png new file mode 100644 index 00000000..c2440956 Binary files /dev/null and b/docs/maps/images/custom-menu-navbar.png differ diff --git a/docs/maps/images/groupLayer.png b/docs/maps/images/groupLayer.png new file mode 100644 index 00000000..83e3cdd5 Binary files /dev/null and b/docs/maps/images/groupLayer.png differ diff --git a/docs/maps/images/menu-command.png b/docs/maps/images/menu-command.png new file mode 100644 index 00000000..0caf75c9 Binary files /dev/null and b/docs/maps/images/menu-command.png differ diff --git a/docs/maps/images/nameIndexProperty.png b/docs/maps/images/nameIndexProperty.png new file mode 100644 index 00000000..f434c659 Binary files /dev/null and b/docs/maps/images/nameIndexProperty.png differ diff --git a/docs/maps/images/object_variable.png b/docs/maps/images/object_variable.png new file mode 100644 index 00000000..434f6191 Binary files /dev/null and b/docs/maps/images/object_variable.png differ diff --git a/docs/maps/images/open_website_allow_api.png b/docs/maps/images/open_website_allow_api.png new file mode 100644 index 00000000..1289611b Binary files /dev/null and b/docs/maps/images/open_website_allow_api.png differ diff --git a/docs/maps/images/screen_popup_in_game.png b/docs/maps/images/screen_popup_in_game.png new file mode 100644 index 00000000..6378bec1 Binary files /dev/null and b/docs/maps/images/screen_popup_in_game.png differ diff --git a/docs/maps/images/screen_popup_tiled.png b/docs/maps/images/screen_popup_tiled.png new file mode 100644 index 00000000..66a42f8f Binary files /dev/null and b/docs/maps/images/screen_popup_tiled.png differ diff --git a/docs/maps/images/script_property.png b/docs/maps/images/script_property.png new file mode 100644 index 00000000..a374ff11 Binary files /dev/null and b/docs/maps/images/script_property.png differ diff --git a/docs/maps/images/text-object.png b/docs/maps/images/text-object.png new file mode 100644 index 00000000..7e5b3da7 Binary files /dev/null and b/docs/maps/images/text-object.png differ diff --git a/docs/maps/images/tiled_screenshot_1.png b/docs/maps/images/tiled_screenshot_1.png new file mode 100644 index 00000000..108dfed8 Binary files /dev/null and b/docs/maps/images/tiled_screenshot_1.png differ diff --git a/docs/maps/images/trigger_event.png b/docs/maps/images/trigger_event.png new file mode 100644 index 00000000..b695476a Binary files /dev/null and b/docs/maps/images/trigger_event.png differ diff --git a/docs/maps/images/trigger_message.png b/docs/maps/images/trigger_message.png new file mode 100644 index 00000000..a116294b Binary files /dev/null and b/docs/maps/images/trigger_message.png differ diff --git a/docs/maps/images/website_allowapi_property.png b/docs/maps/images/website_allowapi_property.png new file mode 100644 index 00000000..25e4df66 Binary files /dev/null and b/docs/maps/images/website_allowapi_property.png differ diff --git a/docs/maps/images/website_url_property.png b/docs/maps/images/website_url_property.png new file mode 100644 index 00000000..331135b8 Binary files /dev/null and b/docs/maps/images/website_url_property.png differ diff --git a/docs/maps/scripting.md b/docs/maps/scripting.md index 5be57ee1..8b11fe74 100644 --- a/docs/maps/scripting.md +++ b/docs/maps/scripting.md @@ -46,7 +46,7 @@ You can put relative URLs. If your script file is next to your map, you can simp
- +
The script property
@@ -72,7 +72,7 @@ In order to allow communication with WorkAdventure, you need to add an additiona
- +
The `openWebsiteAllowApi` property
diff --git a/docs/maps/text.md b/docs/maps/text.md index df3b2660..c9ee647a 100644 --- a/docs/maps/text.md +++ b/docs/maps/text.md @@ -29,7 +29,7 @@ font that has support for a variety of accents. It renders great when used at *8
- +
The "font-family" property
diff --git a/docs/maps/wa-maps.md b/docs/maps/wa-maps.md index 0eb94dbf..bdaf8a42 100644 --- a/docs/maps/wa-maps.md +++ b/docs/maps/wa-maps.md @@ -49,7 +49,7 @@ A few things to notice:
- +
"floorLayer" is compulsory
@@ -62,21 +62,21 @@ To make a tile "collidable", you should: 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: - ![](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": - ![](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**. 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". diff --git a/docs/maps/website-in-map.md b/docs/maps/website-in-map.md index 7c7f8025..d7f6a653 100644 --- a/docs/maps/website-in-map.md +++ b/docs/maps/website-in-map.md @@ -11,7 +11,7 @@ To do this in Tiled:
- +
A "website" object
@@ -34,7 +34,7 @@ to explicitly allow it, by setting an additional `allowApi` property to `true`.
- +
A "website" object that can communicate using the Iframe API
diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index 187e513a..0c89b611 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -34,7 +34,6 @@ WorkAdventure -
@@ -62,31 +61,6 @@
- -
diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html deleted file mode 100644 index 73c62918..00000000 --- a/front/dist/resources/html/gameMenu.html +++ /dev/null @@ -1,78 +0,0 @@ - - - diff --git a/front/dist/resources/html/gameMenuIcon.html b/front/dist/resources/html/gameMenuIcon.html deleted file mode 100644 index 22fe9867..00000000 --- a/front/dist/resources/html/gameMenuIcon.html +++ /dev/null @@ -1,28 +0,0 @@ - -
-
- -
-
\ No newline at end of file diff --git a/front/dist/resources/html/gameQualityMenu.html b/front/dist/resources/html/gameQualityMenu.html deleted file mode 100644 index babb3f0e..00000000 --- a/front/dist/resources/html/gameQualityMenu.html +++ /dev/null @@ -1,81 +0,0 @@ - - - diff --git a/front/dist/resources/html/gameReport.html b/front/dist/resources/html/gameReport.html deleted file mode 100644 index d35ae556..00000000 --- a/front/dist/resources/html/gameReport.html +++ /dev/null @@ -1,115 +0,0 @@ - - -
-
- -

Moderate

-

What action do you want to take?

-
-
-

Block:

-

Block any communication from and to this user. This can be reverted.

-
- -
-
-
-

Report:

-

Send a report message to the administrators of this room. They may later ban this user.

-
-
-
Your message:
- -

-
-
- -
-
-
-
- diff --git a/front/dist/resources/html/gameShare.html b/front/dist/resources/html/gameShare.html deleted file mode 100644 index 404c8680..00000000 --- a/front/dist/resources/html/gameShare.html +++ /dev/null @@ -1,96 +0,0 @@ - - - diff --git a/front/dist/resources/logos/tcm_full.png b/front/dist/resources/logos/tcm_full.png new file mode 100644 index 00000000..3ea27990 Binary files /dev/null and b/front/dist/resources/logos/tcm_full.png differ diff --git a/front/dist/resources/logos/tcm_short.png b/front/dist/resources/logos/tcm_short.png new file mode 100644 index 00000000..ed55c836 Binary files /dev/null and b/front/dist/resources/logos/tcm_short.png differ diff --git a/front/dist/service-worker-prod.js b/front/dist/service-worker-prod.js index 919b0b91..80486623 100644 --- a/front/dist/service-worker-prod.js +++ b/front/dist/service-worker-prod.js @@ -1,4 +1,4 @@ -let CACHE_NAME = 'workavdenture-cache-v1.4.14'; +let CACHE_NAME = 'workavdenture-cache'; let urlsToCache = [ '/' ]; @@ -14,7 +14,8 @@ self.addEventListener('install', function(event) { }); self.addEventListener('fetch', function(event) { - event.respondWith( + //TODO mamnage fetch data and cache management + /*event.respondWith( caches.match(event.request) .then(function(response) { // Cache hit - return response @@ -44,7 +45,7 @@ self.addEventListener('fetch', function(event) { } ); }) - ); + );*/ }); self.addEventListener('wait', function(event) { diff --git a/front/dist/resources/logos/logo-WA-min.png b/front/dist/static/images/logo-WA-min.png similarity index 100% rename from front/dist/resources/logos/logo-WA-min.png rename to front/dist/static/images/logo-WA-min.png diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ed723241..861acc22 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -15,7 +15,6 @@ import type { SetPropertyEvent } from "./setPropertyEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent"; import type { PlaySoundEvent } from "./PlaySoundEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; -import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; import type { SetTilesEvent } from "./SetTilesEvent"; import type { SetVariableEvent } from "./SetVariableEvent"; @@ -33,6 +32,7 @@ import type { TriggerActionMessageEvent, } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; +import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -63,7 +63,8 @@ export type IframeEventMap = { stopSound: null; getState: undefined; loadTileset: LoadTilesetEvent; - registerMenuCommand: MenuItemRegisterEvent; + registerMenu: MenuRegisterEvent; + unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact }; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 3b4e9c85..3e2303b3 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -1,5 +1,4 @@ import * as tg from "generic-type-guard"; -import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; export const isSetVariableEvent = new tg.IsInterface() .withProperties({ diff --git a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts b/front/src/Api/Events/ui/MenuItemRegisterEvent.ts deleted file mode 100644 index 404bdb13..00000000 --- a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts +++ /dev/null @@ -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; - -export const isMenuItemRegisterIframeEvent = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString("registerMenuCommand"), - data: isMenuItemRegisterEvent, - }) - .get(); - -const _registerMenuCommandStream: Subject = new Subject(); -export const registerMenuCommandStream = _registerMenuCommandStream.asObservable(); - -export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) { - _registerMenuCommandStream.next(event.menutItem); -} diff --git a/front/src/Api/Events/ui/MenuRegisterEvent.ts b/front/src/Api/Events/ui/MenuRegisterEvent.ts new file mode 100644 index 00000000..f620745f --- /dev/null +++ b/front/src/Api/Events/ui/MenuRegisterEvent.ts @@ -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; + +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; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 4dde1b7d..140d2f34 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -29,11 +29,12 @@ import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent" import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; 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 type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite"; +import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -93,12 +94,6 @@ class IframeListener { private readonly _setPropertyStream: Subject = new Subject(); public readonly setPropertyStream = this._setPropertyStream.asObservable(); - private readonly _registerMenuCommandStream: Subject = new Subject(); - public readonly registerMenuCommandStream = this._registerMenuCommandStream.asObservable(); - - private readonly _unregisterMenuCommandStream: Subject = new Subject(); - public readonly unregisterMenuCommandStream = this._unregisterMenuCommandStream.asObservable(); - private readonly _playSoundStream: Subject = new Subject(); public readonly playSoundStream = this._playSoundStream.asObservable(); @@ -260,17 +255,23 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { 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)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(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); } - registerScript(scriptUrl: string): void { - console.log("Loading map related script at ", scriptUrl); + registerScript(scriptUrl: string): Promise { + return new Promise((resolve, reject) => { + console.log("Loading map related script at ", scriptUrl); - if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { - // Using external iframe mode ( - const iframe = document.createElement("iframe"); - iframe.id = IframeListener.getIFrameId(scriptUrl); - iframe.style.display = "none"; - iframe.src = "/iframe.html?script=" + encodeURIComponent(scriptUrl); + if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { + // Using external iframe mode ( + const iframe = document.createElement("iframe"); + iframe.id = IframeListener.getIFrameId(scriptUrl); + iframe.style.display = "none"; + 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. - iframe.sandbox.add("allow-scripts"); - iframe.sandbox.add("allow-top-navigation-by-user-activation"); + // 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-top-navigation-by-user-activation"); - document.body.prepend(iframe); + iframe.addEventListener("load", () => { + resolve(); + }); - this.scripts.set(scriptUrl, iframe); - this.registerIframe(iframe); - } else { - // production code - const iframe = document.createElement("iframe"); - iframe.id = IframeListener.getIFrameId(scriptUrl); - iframe.style.display = "none"; + document.body.prepend(iframe); - // 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-top-navigation-by-user-activation"); + this.scripts.set(scriptUrl, iframe); + this.registerIframe(iframe); + } 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); - iframe.srcdoc = - "\n" + - "\n" + - '\n' + - "\n" + - '\n' + - '\n' + - "\n" + - "\n" + - "\n"; + // 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-top-navigation-by-user-activation"); - document.body.prepend(iframe); + //iframe.src = "data:text/html;charset=utf-8," + escape(html); + iframe.srcdoc = + "\n" + + "\n" + + '\n' + + "\n" + + '\n' + + '\n' + + "\n" + + "\n" + + "\n"; - this.scripts.set(scriptUrl, iframe); - this.registerIframe(iframe); - } + iframe.addEventListener("load", () => { + resolve(); + }); + + document.body.prepend(iframe); + + this.scripts.set(scriptUrl, iframe); + this.registerIframe(iframe); + } + }); } private getBaseUrl(src: string, source: MessageEventSource | null): string { diff --git a/front/src/Api/iframe/Sound/Sound.ts b/front/src/Api/iframe/Sound/Sound.ts index 3bb3251a..132176c2 100644 --- a/front/src/Api/iframe/Sound/Sound.ts +++ b/front/src/Api/iframe/Sound/Sound.ts @@ -1,38 +1,35 @@ -import {sendToWorkadventure} from "../IframeApiContribution"; -import type {LoadSoundEvent} from "../../Events/LoadSoundEvent"; -import type {PlaySoundEvent} from "../../Events/PlaySoundEvent"; -import type {StopSoundEvent} from "../../Events/StopSoundEvent"; +import { sendToWorkadventure } from "../IframeApiContribution"; +import type { LoadSoundEvent } from "../../Events/LoadSoundEvent"; +import type { PlaySoundEvent } from "../../Events/PlaySoundEvent"; +import type { StopSoundEvent } from "../../Events/StopSoundEvent"; import SoundConfig = Phaser.Types.Sound.SoundConfig; export class Sound { constructor(private url: string) { sendToWorkadventure({ - "type": 'loadSound', - "data": { + type: "loadSound", + data: { url: this.url, - } as LoadSoundEvent - + } as LoadSoundEvent, }); } - public play(config: SoundConfig) { + public play(config: SoundConfig | undefined) { sendToWorkadventure({ - "type": 'playSound', - "data": { + type: "playSound", + data: { url: this.url, - config - } as PlaySoundEvent - + config, + } as PlaySoundEvent, }); return this.url; } public stop() { sendToWorkadventure({ - "type": 'stopSound', - "data": { + type: "stopSound", + data: { url: this.url, - } as StopSoundEvent - + } as StopSoundEvent, }); return this.url; } diff --git a/front/src/Api/iframe/Ui/Menu.ts b/front/src/Api/iframe/Ui/Menu.ts new file mode 100644 index 00000000..c0fe772e --- /dev/null +++ b/front/src/Api/iframe/Ui/Menu.ts @@ -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, + }, + }); + } +} diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index 3b551864..a875f3e0 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -62,6 +62,10 @@ export class WorkadventureStateCommands extends IframeApiContribution { let subject = variableSubscribers.get(key); if (subject === undefined) { @@ -85,6 +89,12 @@ const proxyCommand = new Proxy(new WorkadventureStateCommands(), { target.saveVariable(p.toString(), value); return true; }, + has(target: WorkadventureStateCommands, p: PropertyKey): boolean { + if (p in target) { + return true; + } + return target.hasVariable(p.toString()); + }, }) as WorkadventureStateCommands & { [key: string]: unknown }; export default proxyCommand; diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index ab5b2007..c4d40d16 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -6,6 +6,8 @@ import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescrip import { Popup } from "./Ui/Popup"; import { ActionMessage } from "./Ui/ActionMessage"; import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent"; +import { Menu } from "./Ui/Menu"; +import type { RequireOnlyOne } from "../types"; let popupId = 0; const popups: Map = new Map(); @@ -14,9 +16,18 @@ const popupCallbacks: Map> = new Map< Map >(); +const menus: Map = new Map(); const menuCallbacks: Map void> = new Map(); const actionMessages = new Map(); +interface MenuDescriptor { + callback?: (commandDescriptor: string) => void; + iframe?: string; + allowApi?: boolean; +} + +export type MenuOptions = RequireOnlyOne; + interface ZonedPopupOptions { zone: string; objectLayerName?: string; @@ -52,6 +63,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution { 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) { callback(event.menuItem); } @@ -104,14 +119,53 @@ export class WorkAdventureUiCommands extends IframeApiContribution void) { - menuCallbacks.set(commandDescriptor, callback); - sendToWorkadventure({ - type: "registerMenuCommand", - data: { - menutItem: commandDescriptor, - }, - }); + registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu { + const menu = new Menu(commandDescriptor); + + if (typeof options === "function") { + menuCallbacks.set(commandDescriptor, options); + 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 { diff --git a/front/src/Api/types.ts b/front/src/Api/types.ts new file mode 100644 index 00000000..7d1a2107 --- /dev/null +++ b/front/src/Api/types.ts @@ -0,0 +1,4 @@ +export type RequireOnlyOne = Pick> & + { + [K in keys]-?: Required> & Partial, undefined>>; + }[keys]; diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 806eaea1..8b033e5f 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -1,4 +1,6 @@ - - - -
- -
-
-

Global Message

- -
-
- {#if inputSendTextActive} - - {/if} - {#if uploadMusicActive} - - {/if} -
- -
-
- - - - diff --git a/front/src/Components/Menu/AboutRoomSubMenu.svelte b/front/src/Components/Menu/AboutRoomSubMenu.svelte new file mode 100644 index 00000000..3ccc9669 --- /dev/null +++ b/front/src/Components/Menu/AboutRoomSubMenu.svelte @@ -0,0 +1,147 @@ + + +
+ +
+

Share the link of the room !

+ +
+

Information on the map

+
+

{mapName}

+

{mapDescription}

+

expandedMapCopyright = !expandedMapCopyright}>Copyrights of the map

+ +

expandedTilesetCopyright = !expandedTilesetCopyright}>Copyrights of the tilesets

+ +
+
+ + + \ No newline at end of file diff --git a/front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte b/front/src/Components/Menu/AudioGlobalMessage.svelte similarity index 85% rename from front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte rename to front/src/Components/Menu/AudioGlobalMessage.svelte index 4718d246..1704e732 100644 --- a/front/src/Components/ConsoleGlobalMessageManager/UploadAudioGlobalMessage.svelte +++ b/front/src/Components/Menu/AudioGlobalMessage.svelte @@ -1,17 +1,14 @@ @@ -103,24 +94,17 @@ img { flex: 1 1 auto; - max-height: 80%; margin-bottom: 20px; } - p { - flex: 1 1 auto; - margin-bottom: 5px; - color: whitesmoke; font-size: 1rem; - &.err { color: #ce372b; } } - input { display: none; } diff --git a/front/src/Components/Menu/ContactSubMenu.svelte b/front/src/Components/Menu/ContactSubMenu.svelte new file mode 100644 index 00000000..6cca0609 --- /dev/null +++ b/front/src/Components/Menu/ContactSubMenu.svelte @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/front/src/Components/Menu/CreateMapSubMenu.svelte b/front/src/Components/Menu/CreateMapSubMenu.svelte new file mode 100644 index 00000000..6cce71ac --- /dev/null +++ b/front/src/Components/Menu/CreateMapSubMenu.svelte @@ -0,0 +1,51 @@ + + +
+
+
+

Getting started

+

+ 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. +

+ +
+
+

Create your map

+

You can also create your own custom map by following the step of the documentation.

+ +
+
+
+ + \ No newline at end of file diff --git a/front/src/Components/Menu/CustomSubMenu.svelte b/front/src/Components/Menu/CustomSubMenu.svelte new file mode 100644 index 00000000..f85499c3 --- /dev/null +++ b/front/src/Components/Menu/CustomSubMenu.svelte @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/front/src/Components/Menu/GlobalMessagesSubMenu.svelte b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte new file mode 100644 index 00000000..8ec66de9 --- /dev/null +++ b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte @@ -0,0 +1,118 @@ + + +
+
+
+ +
+
+ +
+
+
+ {#if inputSendTextActive} + + {/if} + {#if uploadAudioActive} + + {/if} +
+ +
+ + + + + \ No newline at end of file diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte new file mode 100644 index 00000000..4086a9ae --- /dev/null +++ b/front/src/Components/Menu/Menu.svelte @@ -0,0 +1,154 @@ + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Menu/MenuIcon.svelte b/front/src/Components/Menu/MenuIcon.svelte index 241bf45f..2da9e870 100644 --- a/front/src/Components/Menu/MenuIcon.svelte +++ b/front/src/Components/Menu/MenuIcon.svelte @@ -1,33 +1,40 @@ + +
-
- -
+ open menu
diff --git a/front/src/Components/Menu/ProfileSubMenu.svelte b/front/src/Components/Menu/ProfileSubMenu.svelte new file mode 100644 index 00000000..516baf6b --- /dev/null +++ b/front/src/Components/Menu/ProfileSubMenu.svelte @@ -0,0 +1,78 @@ + + +
+
+ +
+
+ +
+
+ +
+ +
+ + \ No newline at end of file diff --git a/front/src/Components/Menu/SettingsSubMenu.svelte b/front/src/Components/Menu/SettingsSubMenu.svelte new file mode 100644 index 00000000..4c0c62dd --- /dev/null +++ b/front/src/Components/Menu/SettingsSubMenu.svelte @@ -0,0 +1,140 @@ + + +
+
+

Game quality

+
+ +
+
+
+

Video quality

+
+ +
+
+
+

(Saving these settings will restart the game)

+ +
+
+ + +
+
+ + \ No newline at end of file diff --git a/front/src/Components/ConsoleGlobalMessageManager/InputTextGlobalMessage.svelte b/front/src/Components/Menu/TextGlobalMessage.svelte similarity index 73% rename from front/src/Components/ConsoleGlobalMessageManager/InputTextGlobalMessage.svelte rename to front/src/Components/Menu/TextGlobalMessage.svelte index a9817a98..1749ba7d 100644 --- a/front/src/Components/ConsoleGlobalMessageManager/InputTextGlobalMessage.svelte +++ b/front/src/Components/Menu/TextGlobalMessage.svelte @@ -1,7 +1,7 @@
-
+
diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte index ed4154a9..836a9108 100644 --- a/front/src/Components/MyCamera.svelte +++ b/front/src/Components/MyCamera.svelte @@ -1,27 +1,11 @@ + +
+

Block

+

Block any communication from and to {userName}. This can be reverted.

+ +
+ + + \ No newline at end of file diff --git a/front/src/Components/ReportMenu/ReportMenu.svelte b/front/src/Components/ReportMenu/ReportMenu.svelte new file mode 100644 index 00000000..7594b1c9 --- /dev/null +++ b/front/src/Components/ReportMenu/ReportMenu.svelte @@ -0,0 +1,141 @@ + + + + +
+
+

Moderate {userName}

+
+ +
+
+
+
+ +
+
+ +
+
+
+ {#if blockActive} + + {:else if reportActive} + + {:else } +

ERROR : There is no action selected.

+ {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/ReportMenu/ReportSubMenu.svelte b/front/src/Components/ReportMenu/ReportSubMenu.svelte new file mode 100644 index 00000000..45167cc0 --- /dev/null +++ b/front/src/Components/ReportMenu/ReportSubMenu.svelte @@ -0,0 +1,55 @@ + + +
+

Report

+

Send a report message to the administrators of this room. They may later ban this user.

+
+
+ + +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/front/src/Components/images/logo-WA-min.png b/front/src/Components/images/logo-WA-min.png new file mode 100644 index 00000000..fe213151 Binary files /dev/null and b/front/src/Components/images/logo-WA-min.png differ diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 25b673ac..db4c3083 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -16,8 +16,9 @@ const lastRoomUrl = "lastRoomUrl"; const authToken = "authToken"; const state = "state"; const nonce = "nonce"; +const notification = "notificationPermission"; -const cacheAPIIndex = "workavdenture-cache-v1"; +const cacheAPIIndex = "workavdenture-cache"; class LocalUserStore { saveUser(localUser: LocalUser) { @@ -143,6 +144,14 @@ class LocalUserStore { return localStorage.getItem(authToken); } + setNotification(value: string): void { + localStorage.setItem(notification, value); + } + + getNotification(): string | null { + return localStorage.getItem(notification); + } + generateState(): string { const newState = uuidv4(); localStorage.setItem(state, newState); diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index b63f4f07..907162d4 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -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 DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true"; 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; diff --git a/front/src/Phaser/Components/Loader.ts b/front/src/Phaser/Components/Loader.ts index 1ee18b32..c80e31b8 100644 --- a/front/src/Phaser/Components/Loader.ts +++ b/front/src/Phaser/Components/Loader.ts @@ -1,48 +1,63 @@ import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig; +import { DirtyScene } from "../Game/DirtyScene"; -const LogoNameIndex: string = 'logoLoading'; -const TextName: string = 'Loading...'; -const LogoResource: string = 'resources/logos/logo.png'; -const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59}; +const LogoNameIndex: string = "logoLoading"; +const TextName: string = "Loading..."; +const LogoResource: string = "resources/logos/logo.png"; +const LogoFrame: ImageFrameConfig = { frameWidth: 307, frameHeight: 59 }; export const addLoader = (scene: Phaser.Scene): void => { // If there is nothing to load, do not display the loader. if (scene.load.list.entries.length === 0) { 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 loadingBarHeight: number = 16; const padding: number = 5; const promiseLoadLogoTexture = new Promise((res) => { - if(scene.load.textureManager.exists(LogoNameIndex)){ - return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex)); - }else{ + if (scene.load.textureManager.exists(LogoNameIndex)) { + return res( + scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex) + ); + } else { //add loading if logo image is not ready loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName); } scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame); scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => { - if(loadingText){ + if (loadingText) { 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 progress = scene.add.graphics(); 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.fillStyle(0xBBBBBB, 1); - progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight); + progress.fillStyle(0xbbbbbb, 1); + progress.fillRect( + (scene.game.renderer.width - loadingBarWidth) / 2, + scene.game.renderer.height / 2 + 50, + loadingBarWidth * value, + loadingBarHeight + ); }); - scene.load.on('complete', () => { - if(loadingText){ + scene.load.on("complete", () => { + if (loadingText) { loadingText.destroy(); } promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => { @@ -50,5 +65,8 @@ export const addLoader = (scene: Phaser.Scene): void => { }); progress.destroy(); progressContainer.destroy(); + if (scene instanceof DirtyScene) { + scene.markDirty(); + } }); -} +}; diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 32881e97..1cd03d12 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -1,16 +1,15 @@ -import type {GameScene} from "../Game/GameScene"; -import type {PointInterface} from "../../Connexion/ConnexionModels"; -import {Character} from "../Entity/Character"; -import type {PlayerAnimationDirections} from "../Player/Animation"; -import {requestVisitCardsStore} from "../../Stores/GameStore"; - +import type { GameScene } from "../Game/GameScene"; +import type { PointInterface } from "../../Connexion/ConnexionModels"; +import { Character } from "../Entity/Character"; +import type { PlayerAnimationDirections } from "../Player/Animation"; +import { requestVisitCardsStore } from "../../Stores/GameStore"; /** * Class representing the sprite of a remote player (a player that plays on another computer) */ export class RemotePlayer extends Character { userId: number; - private visitCardUrl: string|null; + private visitCardUrl: string | null; constructor( userId: number, @@ -21,19 +20,33 @@ export class RemotePlayer extends Character { texturesPromise: Promise, direction: PlayerAnimationDirections, moving: boolean, - visitCardUrl: string|null, - companion: string|null, + visitCardUrl: string | null, + companion: string | null, companionTexturePromise?: Promise ) { - 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 this.userId = userId; this.visitCardUrl = visitCardUrl; - - this.on('pointerdown', () => { - requestVisitCardsStore.set(this.visitCardUrl); - }) + + this.on("pointerdown", (event: Phaser.Input.Pointer) => { + if (event.downElement.nodeName === "CANVAS") { + requestVisitCardsStore.set(this.visitCardUrl); + } + }); } updatePosition(position: PointInterface): void { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 12558d5d..45d0bc86 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,7 +1,6 @@ import { GameScene } from "./GameScene"; import { connectionManager } from "../../Connexion/ConnectionManager"; import type { Room } from "../../Connexion/Room"; -import { MenuScene, MenuSceneName } from "../Menu/MenuScene"; import { LoginSceneName } from "../Login/LoginScene"; import { SelectCharacterSceneName } from "../Login/SelectCharacterScene"; import { EnableCameraSceneName } from "../Login/EnableCameraScene"; @@ -9,6 +8,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore"; import { get } from "svelte/store"; import { requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore"; import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore"; +import { menuIconVisiblilityStore } from "../../Stores/MenuStore"; /** * This class should be responsible for any scene starting/stopping @@ -18,8 +18,9 @@ export class GameManager { private characterLayers: string[] | null; private companion: string | null; private startRoom!: Room; - private scenePlugin!: Phaser.Scenes.ScenePlugin; 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() { this.playerName = localUserStore.getName(); @@ -83,7 +84,6 @@ export class GameManager { public goToStartingMap(): void { console.log("starting " + (this.currentGameSceneName || this.startRoom.key)); this.scenePlugin.start(this.currentGameSceneName || this.startRoom.key); - this.scenePlugin.launch(MenuSceneName); if ( !localUserStore.getHelpCameraSettingsShown() && @@ -96,8 +96,7 @@ export class GameManager { public gameSceneIsCreated(scene: GameScene) { this.currentGameSceneName = scene.scene.key; - const menuScene: MenuScene = scene.scene.get(MenuSceneName) as MenuScene; - menuScene.revealMenuIcon(); + menuIconVisiblilityStore.set(true); } /** @@ -108,8 +107,8 @@ export class GameManager { if (this.currentGameSceneName === null) throw "No current scene id set!"; const gameScene: GameScene = this.scenePlugin.get(this.currentGameSceneName) as GameScene; gameScene.cleanupClosingScene(); - this.scenePlugin.stop(this.currentGameSceneName); - this.scenePlugin.sleep(MenuSceneName); + gameScene.createSuccessorGameScene(false, false); + menuIconVisiblilityStore.set(false); if (!this.scenePlugin.get(targetSceneName)) { this.scenePlugin.add(targetSceneName, sceneClass, false); } @@ -122,7 +121,7 @@ export class GameManager { tryResumingGame(fallbackSceneName: string) { if (this.currentGameSceneName) { this.scenePlugin.start(this.currentGameSceneName); - this.scenePlugin.wake(MenuSceneName); + menuIconVisiblilityStore.set(true); } else { this.scenePlugin.run(fallbackSceneName); } diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 98583cba..23e5c4e9 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -80,8 +80,11 @@ export class GameMap { return; } this.key = key; + this.triggerAll(); + } - const newProps = this.getProperties(key); + private triggerAll(): void { + const newProps = this.getProperties(this.key ?? 0); const oldProps = this.lastProperties; this.lastProperties = newProps; @@ -253,4 +256,34 @@ export class GameMap { } 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(); + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d396c58b..0ec749d2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -45,7 +45,6 @@ import type { ActionableItem } from "../Items/ActionableItem"; import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap"; -import { MenuScene, MenuSceneName } from "../Menu/MenuScene"; import { PlayerAnimationDirections } from "../Player/Animation"; import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; import { ErrorSceneName } from "../Reconnecting/ErrorScene"; @@ -92,9 +91,7 @@ import { PropertyUtils } from "../Map/PropertyUtils"; import Tileset = Phaser.Tilemaps.Tileset; import { userIsAdminStore } from "../../Stores/GameStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; -import { get } from "svelte/store"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; -import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore"; export interface GameSceneInitInterface { 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 @@ -569,6 +560,12 @@ export class GameScene extends DirtyScene { } 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.outlinedItem?.activate(); @@ -586,6 +583,7 @@ export class GameScene extends DirtyScene { this.triggerOnMapLayerPropertyChange(); if (!this.room.isDisconnected()) { + this.scene.sleep(); this.connect(); } @@ -609,6 +607,10 @@ export class GameScene extends DirtyScene { this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => { this.openChatIcon.setVisible(!v); }); + + Promise.all([this.connectionAnswerPromise as Promise, ...scriptPromises]).then(() => { + this.scene.wake(); + }); } /** @@ -686,19 +688,7 @@ export class GameScene extends DirtyScene { this.connection.onServerDisconnected(() => { console.log("Player disconnected from server. Reloading scene."); this.cleanupClosingScene(); - - 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.createSuccessorGameScene(true, true); }); this.connection.onActionableEvent((message) => { @@ -722,7 +712,7 @@ export class GameScene extends DirtyScene { }); // 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); screenSharingPeerStore.connectToSimplePeer(this.simplePeer); videoFocusStore.connectToSimplePeer(this.simplePeer); @@ -760,8 +750,9 @@ export class GameScene extends DirtyScene { this.connectionAnswerPromiseResolve(onConnect.room); // Analyze tags to find if we are admin. If yes, show console. - this.scene.wake(); - this.scene.stop(ReconnectingSceneName); + if (this.scene.isSleeping()) { + this.scene.stop(ReconnectingSceneName); + } //init user position and play trigger to check layers properties this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); @@ -844,7 +835,8 @@ export class GameScene extends DirtyScene { newValue as string, this.MapUrlFile, 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"); }; @@ -953,9 +945,13 @@ export class GameScene extends DirtyScene { return; } const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message); - let html = `