Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop
This commit is contained in:
commit
47e5e360df
@ -40,6 +40,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anatine/zod-openapi": "^1.3.0",
|
||||||
"@workadventure/tiled-map-type-guard": "^1.0.3",
|
"@workadventure/tiled-map-type-guard": "^1.0.3",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
"busboy": "^0.3.1",
|
"busboy": "^0.3.1",
|
||||||
@ -50,6 +51,7 @@
|
|||||||
"ipaddr.js": "^2.0.1",
|
"ipaddr.js": "^2.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
|
"openapi3-ts": "^2.0.2",
|
||||||
"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",
|
||||||
|
@ -520,7 +520,16 @@ export class GameRoom {
|
|||||||
this.admins.delete(admin);
|
this.admins.delete(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public incrementVersion(): number {
|
public async incrementVersion(): Promise<number> {
|
||||||
|
// Let's check if the mapUrl has changed
|
||||||
|
const mapDetails = await GameRoom.getMapDetails(this.roomUrl);
|
||||||
|
if (this.mapUrl !== mapDetails.mapUrl) {
|
||||||
|
this.mapUrl = mapDetails.mapUrl;
|
||||||
|
this.mapPromise = undefined;
|
||||||
|
// Reset the variable manager
|
||||||
|
this.variableManagerPromise = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
this.versionNumber++;
|
this.versionNumber++;
|
||||||
return this.versionNumber;
|
return this.versionNumber;
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,9 @@ export class User implements Movable {
|
|||||||
if (this.outlineColor !== undefined) {
|
if (this.outlineColor !== undefined) {
|
||||||
playerDetails.setOutlinecolor(new UInt32Value().setValue(this.outlineColor));
|
playerDetails.setOutlinecolor(new UInt32Value().setValue(this.outlineColor));
|
||||||
}
|
}
|
||||||
|
if (details.getRemoveoutlinecolor()) {
|
||||||
|
playerDetails.setRemoveoutlinecolor(new BoolValue().setValue(true));
|
||||||
|
}
|
||||||
if (this.voiceIndicatorShown !== undefined) {
|
if (this.voiceIndicatorShown !== undefined) {
|
||||||
playerDetails.setShowvoiceindicator(new BoolValue().setValue(this.voiceIndicatorShown));
|
playerDetails.setShowvoiceindicator(new BoolValue().setValue(this.voiceIndicatorShown));
|
||||||
}
|
}
|
||||||
|
@ -852,14 +852,14 @@ export class SocketManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionNumber = room.incrementVersion();
|
const versionNumber = await room.incrementVersion();
|
||||||
room.getUsers().forEach((recipient) => {
|
room.getUsers().forEach((recipient) => {
|
||||||
const worldFullMessage = new RefreshRoomMessage();
|
const refreshRoomMessage = new RefreshRoomMessage();
|
||||||
worldFullMessage.setRoomid(roomId);
|
refreshRoomMessage.setRoomid(roomId);
|
||||||
worldFullMessage.setVersionnumber(versionNumber);
|
refreshRoomMessage.setVersionnumber(versionNumber);
|
||||||
|
|
||||||
const clientMessage = new ServerToClientMessage();
|
const clientMessage = new ServerToClientMessage();
|
||||||
clientMessage.setRefreshroommessage(worldFullMessage);
|
clientMessage.setRefreshroommessage(refreshRoomMessage);
|
||||||
|
|
||||||
recipient.socket.write(clientMessage);
|
recipient.socket.write(clientMessage);
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@anatine/zod-openapi@^1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@anatine/zod-openapi/-/zod-openapi-1.3.0.tgz#b5b38c3d821b79674226aa7b327c88c371860d0d"
|
||||||
|
integrity sha512-l54DypUdDsIq1Uwjv4ib9IBkTXMKZQLUj7qvdFL51EExC5LdSSqOlTOyaVVZZGYgWPKM7ZjGklhdoknLz4EC+w==
|
||||||
|
dependencies:
|
||||||
|
ts-deepmerge "^1.1.0"
|
||||||
|
validator "^13.7.0"
|
||||||
|
|
||||||
"@babel/code-frame@^7.0.0":
|
"@babel/code-frame@^7.0.0":
|
||||||
version "7.16.0"
|
version "7.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431"
|
||||||
@ -1563,6 +1571,13 @@ onetime@^5.1.0, onetime@^5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
openapi3-ts@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.2.tgz#a200dd838bf24c9086c8eedcfeb380b7eb31e82a"
|
||||||
|
integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==
|
||||||
|
dependencies:
|
||||||
|
yaml "^1.10.2"
|
||||||
|
|
||||||
optionator@^0.9.1:
|
optionator@^0.9.1:
|
||||||
version "0.9.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||||
@ -2042,6 +2057,11 @@ tree-kill@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||||
|
|
||||||
|
ts-deepmerge@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-1.1.0.tgz#4236ae102199affe2e77690dcf198a420160eef2"
|
||||||
|
integrity sha512-VvwaV/6RyYMwT9d8dClmfHIsG2PCdm6WY430QKOIbPRR50Y/1Q2ilp4i2XEZeHFcNqfaYnAQzpyUC6XA0AqqBg==
|
||||||
|
|
||||||
ts-node-dev@^1.1.8:
|
ts-node-dev@^1.1.8:
|
||||||
version "1.1.8"
|
version "1.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066"
|
resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-1.1.8.tgz#95520d8ab9d45fffa854d6668e2f8f9286241066"
|
||||||
@ -2153,6 +2173,11 @@ v8-compile-cache@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
|
||||||
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
|
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
|
||||||
|
|
||||||
|
validator@^13.7.0:
|
||||||
|
version "13.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
||||||
|
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
webidl-conversions@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||||
@ -2236,7 +2261,7 @@ yallist@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||||
|
|
||||||
yaml@^1.10.0:
|
yaml@^1.10.0, yaml@^1.10.2:
|
||||||
version "1.10.2"
|
version "1.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
@ -166,7 +166,8 @@ return [
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Troubleshooting',
|
'title' => 'Troubleshooting',
|
||||||
'url' => '/map-building/troubleshooting',
|
'url' => '/map-building/troubleshooting.md',
|
||||||
'view' => 'content.map.troubleshooting'
|
'markdown' => 'maps.troubleshooting',
|
||||||
|
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/troubleshooting.md',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -18,11 +18,18 @@ In order to create a zone that opens websites:
|
|||||||
|
|
||||||
{.alert.alert-warning}
|
{.alert.alert-warning}
|
||||||
A website can explicitly forbid another website from loading it in an iFrame using
|
A website can explicitly forbid another website from loading it in an iFrame using
|
||||||
the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
|
the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options). You can
|
||||||
|
read more about this common issue and possible workaround the [troubleshooting guide](troubleshooting.md#embedding-an-iframe-is-forbidden).
|
||||||
|
|
||||||
{.alert.alert-info}
|
{.alert.alert-info}
|
||||||
As an alternative, you may also put the `openWebsite` properties on a layer (rather than putting them on an "area" object)
|
As an alternative, you may also put the `openWebsite` properties on a layer (rather than putting them on an "area" object)
|
||||||
but we advise to stick with "area" objects for better performance!
|
but we advise sticking with "area" objects for better performance!
|
||||||
|
|
||||||
|
{.alert.alert-warning}
|
||||||
|
If the website you are embedding is using cookies, those cookies must be configured with the `SameSite=none` attribute. Otherwise,
|
||||||
|
they will be ignored by the browser. If you manage to see the website you embed but cannot log into it, the `SameSite` attribute is most
|
||||||
|
likely the culprit. You can read more about this common issue and possible workaround the [troubleshooting guide](troubleshooting.md#i-cannot-log-into-my-embedded-website).
|
||||||
|
|
||||||
|
|
||||||
## Integrating a Youtube video
|
## Integrating a Youtube video
|
||||||
|
|
||||||
|
94
docs/maps/troubleshooting.md
Normal file
94
docs/maps/troubleshooting.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{.section-title.accent.text-primary}
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## Look at the browser console
|
||||||
|
|
||||||
|
If your map is not displayed correctly (most notably if you are getting a black screen), open your browser console.
|
||||||
|
This is usually done by pressing the F12 key and selecting the "console" tab.
|
||||||
|
|
||||||
|
Scan the output. Towards the end, you might see a message explaining why your map cannot be loaded.
|
||||||
|
|
||||||
|
## Check webserver CORS settings
|
||||||
|
|
||||||
|
If you are hosting the map you built on your own webserver and if the map does not load, please check that
|
||||||
|
[your webserver CORS settings are correctly configured](hosting.md).
|
||||||
|
|
||||||
|
## Issues embedding a website
|
||||||
|
|
||||||
|
When you are embedding a website in WorkAdventure (whether it is using the [`openWebsite` property](opening-a-website.md) or
|
||||||
|
the [integrated website in a map](website-in-map.md) feature or the [Scripting API](scripting.md)), WorkAdventure
|
||||||
|
will open your website using an iFrame.
|
||||||
|
|
||||||
|
Browsers have various security measures in place, and website owners can use those measures to prevent websites from
|
||||||
|
being used inside iFrames (either partially or completely).
|
||||||
|
|
||||||
|
In the chapters below, we will list what can possibly prevent you from embedding a website, and see what are your options.
|
||||||
|
|
||||||
|
### Embedding an iFrame is forbidden
|
||||||
|
|
||||||
|
The worst that can happen is that the website you are trying to embed completely denies you the authorisation.
|
||||||
|
A website owner can do that using the [`X-Frame-Options` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options),
|
||||||
|
or the newer [`Content-Security-Policy` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy).
|
||||||
|
|
||||||
|
Take a look at the headers of the page you are trying to load.
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
You can view the headers of the web page you try to load in the developer tools of your browser (usually accessible using the F12 key
|
||||||
|
of your keyboard), in the network tab. Click on the top-most request and check the "Response Headers".
|
||||||
|
|
||||||
|
Below is what you can see when opening a Youtube video page:
|
||||||
|
|
||||||
|
![](images/x-frame-options.png)
|
||||||
|
|
||||||
|
`X-Frame-Options: DENY` or `X-Frame-Options: SAMEORIGIN` will prevent WorkAdventure from loading the page.
|
||||||
|
`Content-Security-Policy` header have also the potential to prevent WorkAdventure from loading the page.
|
||||||
|
|
||||||
|
If the website you are trying to embed has one of these headers set, here are your options:
|
||||||
|
|
||||||
|
- if you have control over the website or know the owner, you can contact the owner/administrator of the website and ask for an exception
|
||||||
|
- otherwise, you can look for an "embed" option. Some websites have special pages that can be embedded. For instance,
|
||||||
|
YouTube has special "embed" links that can be used to embed a video in your website. A lot of websites have the same feature (you
|
||||||
|
can usually find those links in the "share" section)
|
||||||
|
|
||||||
|
If none of these options are available to you, as a last resort, you can use the [`openTab` property](opening-a-website.md) instead of the `openWebsite` property.
|
||||||
|
It will open your webpage in another tab instead of opening it in an iFrame.
|
||||||
|
|
||||||
|
### I cannot log into my embedded website
|
||||||
|
|
||||||
|
When you log into a website, the website is issuing a "cookie". The cookie is a unique identifier that allows the website
|
||||||
|
to recognize you and to identify you. To improve the privacy of their users, browsers can sometimes treat cookies
|
||||||
|
inside iFrames as "third-party cookies" and discard them.
|
||||||
|
|
||||||
|
Cookies can come with a `SameSite` attribute.
|
||||||
|
|
||||||
|
The `SameSite` attribute can take these values: "Lax", "Strict" or "None". The only value that allows using the
|
||||||
|
cookie inside an iFrame is "None".
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
The `SameSite` attribute of your cookie MUST be set to "None" if you want to be able to use this cookie from an iFrame inside WorkAdventure.
|
||||||
|
|
||||||
|
**Default values**:
|
||||||
|
|
||||||
|
If the "SameSite" attribute is not explicitly set, [the behaviour depends on the browser](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#browser_compatibility).
|
||||||
|
Chrome, Edge and Opera will default to "Lax".
|
||||||
|
Firefox and Safari will default to "None" (as of 2022/04/25).
|
||||||
|
|
||||||
|
As a result, a website that does not set the `SameSite` attribute on cookies will work correctly in Firefox and Safari but
|
||||||
|
login will fail on Chrome, Edge and Opera.
|
||||||
|
|
||||||
|
If the website you are trying to embed has the `SameSite` attribute set to a value other than "None", here are your options:
|
||||||
|
|
||||||
|
- if you have control over the website or know the owner, you can contact the owner/administrator of the website and ask
|
||||||
|
the owner/administrator to change the `SameSite` settings.
|
||||||
|
- otherwise, you will have to use the [`openTab` property](opening-a-website.md) instead of the `openWebsite` property.
|
||||||
|
It will open your webpage in another tab instead of in an iFrame.
|
||||||
|
|
||||||
|
## Need some help?
|
||||||
|
|
||||||
|
<div class="card bg-red text-white"><div class="card-body">
|
||||||
|
<p>WorkAdventure is a constantly evolving project and there is plenty of room for improvement regarding map editing.</p>
|
||||||
|
<p>If you are facing any troubles, do not hesitate to seek help in
|
||||||
|
<a href="https://discord.gg/G6Xh9ZM9aR">our Discord server</a> or open an "issue" in the
|
||||||
|
<a href="https://github.com/thecodingmachine/workadventure/issues" target="_blank">GitHub WorkAdventure account</a>.
|
||||||
|
</p>
|
||||||
|
</div></div>
|
@ -1,5 +1,3 @@
|
|||||||
src/Messages/generated
|
src/Messages/generated
|
||||||
src/Messages/JsonMessages
|
src/Messages/JsonMessages
|
||||||
src/i18n/i18n-svelte.ts
|
src/i18n/i18n-*.ts
|
||||||
src/i18n/i18n-types.ts
|
|
||||||
src/i18n/i18n-util.ts
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://unpkg.com/typesafe-i18n@2.59.0/schema/typesafe-i18n.json",
|
"$schema": "https://unpkg.com/typesafe-i18n@5.4.0/schema/typesafe-i18n.json",
|
||||||
"baseLocale": "en-US",
|
"baseLocale": "en-US",
|
||||||
"adapter": "svelte"
|
"adapter": "svelte"
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
"license": "SEE LICENSE IN LICENSE.txt",
|
"license": "SEE LICENSE IN LICENSE.txt",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@geprog/vite-plugin-env-config": "^4.0.3",
|
"@geprog/vite-plugin-env-config": "^4.0.3",
|
||||||
"@home-based-studio/phaser3-utils": "^0.4.2",
|
"@home-based-studio/phaser3-utils": "^0.4.7",
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||||
"@tsconfig/svelte": "^1.0.10",
|
"@tsconfig/svelte": "^1.0.10",
|
||||||
"@types/google-protobuf": "^3.7.3",
|
"@types/google-protobuf": "^3.7.3",
|
||||||
@ -34,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@16bits/nes.css": "^2.3.2",
|
"@16bits/nes.css": "^2.3.2",
|
||||||
|
"@anatine/zod-openapi": "^1.3.0",
|
||||||
"@fontsource/press-start-2p": "^4.3.0",
|
"@fontsource/press-start-2p": "^4.3.0",
|
||||||
"@joeattardi/emoji-button": "^4.6.2",
|
"@joeattardi/emoji-button": "^4.6.2",
|
||||||
"@types/simple-peer": "^9.11.1",
|
"@types/simple-peer": "^9.11.1",
|
||||||
@ -47,6 +48,7 @@
|
|||||||
"easystarjs": "^0.4.4",
|
"easystarjs": "^0.4.4",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
|
"openapi3-ts": "^2.0.2",
|
||||||
"phaser": "3.55.1",
|
"phaser": "3.55.1",
|
||||||
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
|
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
|
||||||
"phaser3-rex-plugins": "^1.1.42",
|
"phaser3-rex-plugins": "^1.1.42",
|
||||||
@ -61,7 +63,7 @@
|
|||||||
"standardized-audio-context": "^25.2.4",
|
"standardized-audio-context": "^25.2.4",
|
||||||
"ts-deferred": "^1.0.4",
|
"ts-deferred": "^1.0.4",
|
||||||
"ts-proto": "^1.96.0",
|
"ts-proto": "^1.96.0",
|
||||||
"typesafe-i18n": "^2.59.0",
|
"typesafe-i18n": "^5.4.0",
|
||||||
"uuidv4": "^6.2.10",
|
"uuidv4": "^6.2.10",
|
||||||
"zod": "^3.14.3"
|
"zod": "^3.14.3"
|
||||||
},
|
},
|
||||||
|
@ -17,7 +17,7 @@ class AnalyticsClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
identifyUser(uuid: string, email: string | null) {
|
identifyUser(uuid: string, email: string | null): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.identify(uuid, { uuid, email, wa: true });
|
posthog.identify(uuid, { uuid, email, wa: true });
|
||||||
@ -25,7 +25,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithSso() {
|
loggedWithSso(): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-logged-sso");
|
posthog.capture("wa-logged-sso");
|
||||||
@ -33,7 +33,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedWithToken() {
|
loggedWithToken(): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-logged-token");
|
posthog.capture("wa-logged-token");
|
||||||
@ -41,7 +41,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredRoom(roomId: string, roomGroup: string | null) {
|
enteredRoom(roomId: string, roomGroup: string | null): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("$pageView", { roomId, roomGroup });
|
posthog.capture("$pageView", { roomId, roomGroup });
|
||||||
@ -50,7 +50,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
openedMenu() {
|
openedMenu(): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-opened-menu");
|
posthog.capture("wa-opened-menu");
|
||||||
@ -58,7 +58,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
launchEmote(emote: string) {
|
launchEmote(emote: string): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-emote-launch", { emote });
|
posthog.capture("wa-emote-launch", { emote });
|
||||||
@ -66,7 +66,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
enteredJitsi(roomName: string, roomId: string) {
|
enteredJitsi(roomName: string, roomId: string): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
posthog.capture("wa-entered-jitsi", { roomName, roomId });
|
||||||
@ -74,7 +74,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationName() {
|
validationName(): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-name-validation");
|
posthog.capture("wa-name-validation");
|
||||||
@ -82,7 +82,7 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationWoka(scene: string) {
|
validationWoka(scene: string): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-woka-validation", { scene });
|
posthog.capture("wa-woka-validation", { scene });
|
||||||
@ -90,12 +90,309 @@ class AnalyticsClient {
|
|||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
validationVideo() {
|
validationVideo(): void {
|
||||||
this.posthogPromise
|
this.posthogPromise
|
||||||
?.then((posthog) => {
|
?.then((posthog) => {
|
||||||
posthog.capture("wa-video-validation");
|
posthog.capture("wa-video-validation");
|
||||||
})
|
})
|
||||||
.catch((e) => console.error(e));
|
.catch((e) => console.error(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** New feature analytics **/
|
||||||
|
openedChat(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa-opened-chat");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
openRegister(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa-opened-register");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
openInvite(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa-opened-invite");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
lockDiscussion(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_lockroom");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
screenSharing(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa-screensharing");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
follow(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_follow");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
camera(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_camera");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
microphone(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_microphone");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingMicrophone(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_microphone", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingCamera(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_camera", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingNotification(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_notification", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingFullscreen(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_fullscreen", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingAskWebsite(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_ask_website", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingRequestFollow(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_request_follow", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
settingDecreaseAudioVolume(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_setting_decrease_audio_volume", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
login(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_login");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_logout");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
switchMultiIframe(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_multiiframe_switch");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMultiIframe(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_multiiframe_close");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
fullScreenMultiIframe(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_multiiframe_fullscreen");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
stackOpenCloseMultiIframe(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_multiiframe_stack_open_close");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuCredit(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_credit");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuProfile(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_profile");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuSetting() {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_setting");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuInvite(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_invite");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
globalMessage(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_globalmessage");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
menuContact(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_contact");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteCopyLink(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_invite_copylink");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteCopyLinkWalk(value: string): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_menu_invite_copylink_walk", {
|
||||||
|
checkbox: value,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
editCompanion(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_edit_companion");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
editCamera(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_edit_camera");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
editName(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_edit_name");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
editWoka(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_edit_woka");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectWoka(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_wokascene_select");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCustomWoka(): void {
|
||||||
|
this.posthogPromise
|
||||||
|
?.then((posthog) => {
|
||||||
|
posthog.capture("wa_wokascene_custom");
|
||||||
|
})
|
||||||
|
.catch((e) => console.error(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export const analyticsClient = new AnalyticsClient();
|
export const analyticsClient = new AnalyticsClient();
|
||||||
|
@ -242,7 +242,7 @@ class IframeListener {
|
|||||||
} else if (iframeEvent.type === "cameraFollowPlayer") {
|
} else if (iframeEvent.type === "cameraFollowPlayer") {
|
||||||
this._cameraFollowPlayerStream.next(iframeEvent.data);
|
this._cameraFollowPlayerStream.next(iframeEvent.data);
|
||||||
} else if (iframeEvent.type === "chat") {
|
} else if (iframeEvent.type === "chat") {
|
||||||
scriptUtils.sendAnonymousChat(iframeEvent.data);
|
scriptUtils.sendAnonymousChat(iframeEvent.data, iframe.contentWindow ?? undefined);
|
||||||
} else if (iframeEvent.type === "openPopup") {
|
} else if (iframeEvent.type === "openPopup") {
|
||||||
this._openPopupStream.next(iframeEvent.data);
|
this._openPopupStream.next(iframeEvent.data);
|
||||||
} else if (iframeEvent.type === "closePopup") {
|
} else if (iframeEvent.type === "closePopup") {
|
||||||
@ -404,13 +404,20 @@ class IframeListener {
|
|||||||
this.scripts.delete(scriptUrl);
|
this.scripts.delete(scriptUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUserInputChat(message: string) {
|
/**
|
||||||
this.postMessage({
|
* @param message The message to dispatch
|
||||||
|
* @param exceptOrigin Don't dispatch the message to exceptOrigin (to avoid infinite loops)
|
||||||
|
*/
|
||||||
|
sendUserInputChat(message: string, exceptOrigin?: Window) {
|
||||||
|
this.postMessage(
|
||||||
|
{
|
||||||
type: "userInputChat",
|
type: "userInputChat",
|
||||||
data: {
|
data: {
|
||||||
message: message,
|
message: message,
|
||||||
} as UserInputChatEvent,
|
} as UserInputChatEvent,
|
||||||
});
|
},
|
||||||
|
exceptOrigin
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEnterEvent(name: string) {
|
sendEnterEvent(name: string) {
|
||||||
@ -528,8 +535,11 @@ class IframeListener {
|
|||||||
/**
|
/**
|
||||||
* Sends the message... to all allowed iframes.
|
* Sends the message... to all allowed iframes.
|
||||||
*/
|
*/
|
||||||
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>, exceptOrigin?: Window) {
|
||||||
for (const iframe of this.iframes) {
|
for (const iframe of this.iframes) {
|
||||||
|
if (exceptOrigin === iframe.contentWindow) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
iframe.contentWindow?.postMessage(message, "*");
|
iframe.contentWindow?.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,9 @@ class ScriptUtils {
|
|||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendAnonymousChat(chatEvent: ChatEvent) {
|
public sendAnonymousChat(chatEvent: ChatEvent, origin?: Window) {
|
||||||
const userId = playersStore.addFacticePlayer(chatEvent.author);
|
const userId = playersStore.addFacticePlayer(chatEvent.author);
|
||||||
chatMessagesStore.addExternalMessage(userId, chatEvent.message);
|
chatMessagesStore.addExternalMessage(userId, chatEvent.message, origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
|
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
|
||||||
import { gameManager } from "../Phaser/Game/GameManager";
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore";
|
import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore";
|
||||||
|
import { analyticsClient } from "../Administration/AnalyticsClient";
|
||||||
|
|
||||||
const gameScene = gameManager.getCurrentGameScene();
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
@ -89,6 +90,7 @@
|
|||||||
class="btn-follow nes-btn is-dark"
|
class="btn-follow nes-btn is-dark"
|
||||||
class:hide={($peerStore.size === 0 && $followStateStore === "off") || $silentStore}
|
class:hide={($peerStore.size === 0 && $followStateStore === "off") || $silentStore}
|
||||||
class:disabled={$followStateStore !== "off"}
|
class:disabled={$followStateStore !== "off"}
|
||||||
|
on:click={() => analyticsClient.follow()}
|
||||||
on:click={followClick}
|
on:click={followClick}
|
||||||
>
|
>
|
||||||
<img class="noselect" src={followImg} alt="" />
|
<img class="noselect" src={followImg} alt="" />
|
||||||
@ -98,6 +100,7 @@
|
|||||||
class="btn-lock nes-btn is-dark"
|
class="btn-lock nes-btn is-dark"
|
||||||
class:hide={$peerStore.size === 0 || $silentStore}
|
class:hide={$peerStore.size === 0 || $silentStore}
|
||||||
class:disabled={$currentPlayerGroupLockStateStore}
|
class:disabled={$currentPlayerGroupLockStateStore}
|
||||||
|
on:click={() => analyticsClient.lockDiscussion()}
|
||||||
on:click={lockClick}
|
on:click={lockClick}
|
||||||
>
|
>
|
||||||
<img class="noselect" src={lockImg} alt="" />
|
<img class="noselect" src={lockImg} alt="" />
|
||||||
@ -105,6 +108,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="btn-monitor nes-btn is-dark"
|
class="btn-monitor nes-btn is-dark"
|
||||||
|
on:click={() => analyticsClient.screenSharing()}
|
||||||
on:click={screenSharingClick}
|
on:click={screenSharingClick}
|
||||||
class:hide={!$screenSharingAvailableStore || $silentStore}
|
class:hide={!$screenSharingAvailableStore || $silentStore}
|
||||||
class:enabled={$requestedScreenSharingState}
|
class:enabled={$requestedScreenSharingState}
|
||||||
@ -116,7 +120,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState || $silentStore}>
|
<div
|
||||||
|
class="btn-video nes-btn is-dark"
|
||||||
|
on:click={() => analyticsClient.camera()}
|
||||||
|
on:click={cameraClick}
|
||||||
|
class:disabled={!$requestedCameraState || $silentStore}
|
||||||
|
>
|
||||||
{#if $requestedCameraState && !$silentStore}
|
{#if $requestedCameraState && !$silentStore}
|
||||||
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
|
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
|
||||||
{:else}
|
{:else}
|
||||||
@ -124,7 +133,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || $silentStore}>
|
<div
|
||||||
|
class="btn-micro nes-btn is-dark"
|
||||||
|
on:click={() => analyticsClient.microphone()}
|
||||||
|
on:click={microphoneClick}
|
||||||
|
class:disabled={!$requestedMicrophoneState || $silentStore}
|
||||||
|
>
|
||||||
{#if $requestedMicrophoneState && !$silentStore}
|
{#if $requestedMicrophoneState && !$silentStore}
|
||||||
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
|
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
import { i18nJson } from "../../i18n/locales";
|
import { i18nJson } from "../../i18n/locales";
|
||||||
|
|
||||||
import uploadFile from "../images/jitsi.png";
|
import uploadFile from "../images/jitsi.png";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export let index: number;
|
export let index: number;
|
||||||
export let coWebsite: CoWebsite;
|
export let coWebsite: CoWebsite;
|
||||||
@ -103,6 +104,7 @@
|
|||||||
class:ready={$state === "ready"}
|
class:ready={$state === "ready"}
|
||||||
class:displayed={isMain || isHighlight}
|
class:displayed={isMain || isHighlight}
|
||||||
class:vertical
|
class:vertical
|
||||||
|
on:click={() => analyticsClient.stackOpenCloseMultiIframe()}
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
>
|
>
|
||||||
<div class="cowebsite-thumnail-container">
|
<div class="cowebsite-thumnail-container">
|
||||||
|
@ -19,6 +19,5 @@
|
|||||||
padding-top: 2%;
|
padding-top: 2%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 200;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -67,10 +67,11 @@
|
|||||||
{/key}
|
{/key}
|
||||||
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
||||||
{#key $highlightedEmbedScreen.embed.getId()}
|
{#key $highlightedEmbedScreen.embed.getId()}
|
||||||
|
<div class="highlighted-cowebsite-container nes-container is-rounded">
|
||||||
<div
|
<div
|
||||||
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
|
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
|
||||||
class="highlighted-cowebsite nes-container is-rounded"
|
class="highlighted-cowebsite"
|
||||||
>
|
/>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button type="button" class="nes-btn is-error close" on:click={closeCoWebsite}
|
<button type="button" class="nes-btn is-error close" on:click={closeCoWebsite}
|
||||||
>×</button
|
>×</button
|
||||||
@ -119,14 +120,22 @@
|
|||||||
margin-bottom: 3%;
|
margin-bottom: 3%;
|
||||||
|
|
||||||
.highlighted-cowebsite {
|
.highlighted-cowebsite {
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
position: relative;
|
||||||
|
z-index: 200;
|
||||||
|
|
||||||
|
&-container {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
width: 96%;
|
width: 96%;
|
||||||
background-color: rgba(#000000, 0.6);
|
background-color: rgba(#000000, 0.6);
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
.actions {
|
.actions {
|
||||||
z-index: 200;
|
z-index: 202;
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
@ -138,4 +147,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -53,11 +53,14 @@
|
|||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-top: 4%;
|
margin-top: 4%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
margin-left: 10%;
|
||||||
|
margin-right: 10%;
|
||||||
z-index: 600;
|
z-index: 600;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -68,6 +68,11 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<section id="main-layout-main">
|
<section id="main-layout-main">
|
||||||
|
<Lazy
|
||||||
|
when={$showDesktopCapturerSourcePicker}
|
||||||
|
component={() => import("./Video/DesktopCapturerSourcePicker.svelte")}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if $menuVisiblilityStore}
|
{#if $menuVisiblilityStore}
|
||||||
<Menu />
|
<Menu />
|
||||||
{/if}
|
{/if}
|
||||||
@ -120,11 +125,6 @@
|
|||||||
|
|
||||||
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
||||||
|
|
||||||
<Lazy
|
|
||||||
when={$showDesktopCapturerSourcePicker}
|
|
||||||
component={() => import("./Video/DesktopCapturerSourcePicker.svelte")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{#if hasEmbedScreen}
|
{#if hasEmbedScreen}
|
||||||
<EmbedScreensContainer />
|
<EmbedScreensContainer />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
let entryPoint: string = $startLayerNamesStore[0];
|
let entryPoint: string = $startLayerNamesStore[0];
|
||||||
let walkAutomatically: boolean = false;
|
let walkAutomatically: boolean = false;
|
||||||
@ -54,16 +55,26 @@
|
|||||||
class="link-url nes-input is-dark"
|
class="link-url nes-input is-dark"
|
||||||
value={location.toString()}
|
value={location.toString()}
|
||||||
/>
|
/>
|
||||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-primary"
|
||||||
|
on:click={() => analyticsClient.inviteCopyLink()}
|
||||||
|
on:click={copyLink}>{$LL.menu.invite.copy()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<section class="is-mobile">
|
<section class="is-mobile">
|
||||||
<h3>{$LL.menu.invite.description()}</h3>
|
<h3>{$LL.menu.invite.description()}</h3>
|
||||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn is-primary"
|
||||||
|
on:click={() => analyticsClient.inviteCopyLink()}
|
||||||
|
on:click={shareLink}>{$LL.menu.invite.share()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<h3>Select an entry point</h3>
|
<h3>{$LL.menu.invite.selectEntryPoint()}</h3>
|
||||||
<section class="nes-select is-dark starting-points">
|
<section class="nes-select is-dark starting-points">
|
||||||
<select
|
<select
|
||||||
bind:value={entryPoint}
|
bind:value={entryPoint}
|
||||||
@ -81,11 +92,12 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={walkAutomatically}
|
bind:checked={walkAutomatically}
|
||||||
|
on:change={(e) => analyticsClient.inviteCopyLinkWalk(e.currentTarget.value)}
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
updateInputFieldValue();
|
updateInputFieldValue();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.menu.invite.walk_automatically_to_position()}</span>
|
<span>{$LL.menu.invite.walkAutomaticallyToPosition()}</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import type { Unsubscriber } from "svelte/store";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
let activeSubMenu: MenuItem = $subMenusStore[0];
|
let activeSubMenu: MenuItem = $subMenusStore[0];
|
||||||
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||||
@ -48,24 +49,30 @@
|
|||||||
activeSubMenu = menu;
|
activeSubMenu = menu;
|
||||||
switch (menu.key) {
|
switch (menu.key) {
|
||||||
case SubMenusInterface.settings:
|
case SubMenusInterface.settings:
|
||||||
|
analyticsClient.menuSetting();
|
||||||
activeComponent = SettingsSubMenu;
|
activeComponent = SettingsSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.profile:
|
case SubMenusInterface.profile:
|
||||||
|
analyticsClient.menuProfile();
|
||||||
activeComponent = ProfileSubMenu;
|
activeComponent = ProfileSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.worlds:
|
case SubMenusInterface.worlds:
|
||||||
activeComponent = WorldsSubMenu;
|
activeComponent = WorldsSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.invite:
|
case SubMenusInterface.invite:
|
||||||
|
analyticsClient.menuInvite();
|
||||||
activeComponent = GuestSubMenu;
|
activeComponent = GuestSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.aboutRoom:
|
case SubMenusInterface.aboutRoom:
|
||||||
|
analyticsClient.menuCredit();
|
||||||
activeComponent = AboutRoomSubMenu;
|
activeComponent = AboutRoomSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.globalMessages:
|
case SubMenusInterface.globalMessages:
|
||||||
|
analyticsClient.globalMessage();
|
||||||
activeComponent = (await import("./GlobalMessagesSubMenu.svelte")).default;
|
activeComponent = (await import("./GlobalMessagesSubMenu.svelte")).default;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.contact:
|
case SubMenusInterface.contact:
|
||||||
|
analyticsClient.menuContact();
|
||||||
activeComponent = ContactSubMenu;
|
activeComponent = ContactSubMenu;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -92,16 +99,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function translateMenuName(menu: MenuItem) {
|
$: subMenuTranslations = $subMenusStore.map((subMenu) =>
|
||||||
if (menu.type === "scripting") {
|
subMenu.type === "scripting" ? subMenu.label : $LL.menu.sub[subMenu.key]()
|
||||||
return menu.label;
|
);
|
||||||
}
|
$: activeSubMenuTranslation =
|
||||||
|
activeSubMenu.type === "scripting" ? activeSubMenu.label : $LL.menu.sub[activeSubMenu.key]();
|
||||||
// Bypass the proxy of typesafe for getting the menu name : https://github.com/ivanhofer/typesafe-i18n/issues/156
|
|
||||||
const getMenuName = $LL.menu.sub[menu.key];
|
|
||||||
|
|
||||||
return getMenuName();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} />
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
@ -110,20 +112,20 @@
|
|||||||
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
||||||
<h2>{$LL.menu.title()}</h2>
|
<h2>{$LL.menu.title()}</h2>
|
||||||
<nav>
|
<nav>
|
||||||
{#each $subMenusStore as submenu}
|
{#each $subMenusStore as submenu, i}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}"
|
class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={() => void switchMenu(submenu)}
|
on:click|preventDefault={() => void switchMenu(submenu)}
|
||||||
>
|
>
|
||||||
{translateMenuName(submenu)}
|
{subMenuTranslations[i]}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-submenu-container nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
<div class="menu-submenu-container nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
||||||
<button type="button" class="nes-btn is-error close" on:click={closeMenu}>×</button>
|
<button type="button" class="nes-btn is-error close" on:click={closeMenu}>×</button>
|
||||||
<h2>{translateMenuName(activeSubMenu)}</h2>
|
<h2>{activeSubMenuTranslation}</h2>
|
||||||
<svelte:component this={activeComponent} {...props} />
|
<svelte:component this={activeComponent} {...props} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
||||||
@ -43,7 +44,8 @@
|
|||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
on:dragstart|preventDefault={noDrag}
|
on:dragstart|preventDefault={noDrag}
|
||||||
on:click|preventDefault={showInvite}
|
on:click={() => analyticsClient.openInvite()}
|
||||||
|
on:click={showInvite}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="nes-btn is-dark">
|
<span class="nes-btn is-dark">
|
||||||
@ -53,7 +55,8 @@
|
|||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
on:dragstart|preventDefault={noDrag}
|
on:dragstart|preventDefault={noDrag}
|
||||||
on:click|preventDefault={register}
|
on:click={() => analyticsClient.openRegister()}
|
||||||
|
on:click={register}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
@ -64,11 +67,11 @@
|
|||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
on:dragstart|preventDefault={noDrag}
|
on:dragstart|preventDefault={noDrag}
|
||||||
on:click|preventDefault={showMenu}
|
on:click={() => analyticsClient.openedMenu()}
|
||||||
|
on:click={showMenu}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="nes-btn is-dark">
|
<span class="nes-btn is-dark">
|
||||||
<img
|
<img
|
||||||
src={logoTalk}
|
src={logoTalk}
|
||||||
@ -76,7 +79,8 @@
|
|||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
on:dragstart|preventDefault={noDrag}
|
on:dragstart|preventDefault={noDrag}
|
||||||
on:click|preventDefault={showChat}
|
on:click={() => analyticsClient.openedMenu()}
|
||||||
|
on:click={showChat}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</main>
|
</main>
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
import Woka from "../Woka/Woka.svelte";
|
import Woka from "../Woka/Woka.svelte";
|
||||||
import Companion from "../Companion/Companion.svelte";
|
import Companion from "../Companion/Companion.svelte";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
function disableMenuStores() {
|
function disableMenuStores() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
@ -55,20 +56,40 @@
|
|||||||
<div class="submenu">
|
<div class="submenu">
|
||||||
<section>
|
<section>
|
||||||
<!--
|
<!--
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn"
|
||||||
|
on:click={() => analyticsClient.editName()}
|
||||||
|
on:click={openEditNameScene}
|
||||||
|
>
|
||||||
<img src={btnProfileSubMenuIdentity} alt={$LL.menu.profile.edit.name()} />
|
<img src={btnProfileSubMenuIdentity} alt={$LL.menu.profile.edit.name()} />
|
||||||
<span class="btn-hover">{$LL.menu.profile.edit.name()}</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.name()}</span>
|
||||||
</button>
|
</button>
|
||||||
-->
|
-->
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn"
|
||||||
|
on:click={() => analyticsClient.editWoka()}
|
||||||
|
on:click={openEditSkinScene}
|
||||||
|
>
|
||||||
<Woka userId={-1} placeholderSrc="" width="26px" height="26px" />
|
<Woka userId={-1} placeholderSrc="" width="26px" height="26px" />
|
||||||
<span class="btn-hover">{$LL.menu.profile.edit.woka()}</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.woka()}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn"
|
||||||
|
on:click={() => analyticsClient.editCompanion()}
|
||||||
|
on:click={openEditCompanionScene}
|
||||||
|
>
|
||||||
<Companion userId={-1} placeholderSrc={btnProfileSubMenuCompanion} width="26px" height="26px" />
|
<Companion userId={-1} placeholderSrc={btnProfileSubMenuCompanion} width="26px" height="26px" />
|
||||||
<span class="btn-hover">{$LL.menu.profile.edit.companion()}</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.companion()}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn"
|
||||||
|
on:click={() => analyticsClient.editCamera()}
|
||||||
|
on:click={openEnableCameraScene}
|
||||||
|
>
|
||||||
<img src={btnProfileSubMenuCamera} alt={$LL.menu.profile.edit.camera()} />
|
<img src={btnProfileSubMenuCamera} alt={$LL.menu.profile.edit.camera()} />
|
||||||
<span class="btn-hover">{$LL.menu.profile.edit.camera()}</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.camera()}</span>
|
||||||
</button>
|
</button>
|
||||||
@ -83,13 +104,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}
|
<button type="button" class="nes-btn" on:click={() => analyticsClient.logout()} on:click={logOut}
|
||||||
>{$LL.menu.profile.logout()}</button
|
>{$LL.menu.profile.logout()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<section>
|
<section>
|
||||||
<a type="button" class="nes-btn" href="/login">{$LL.menu.profile.login()}</a>
|
<a type="button" class="nes-btn" href="/login" on:click={() => analyticsClient.login()}
|
||||||
|
>{$LL.menu.profile.login()}</a
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { audioManagerVolumeStore } from "../../Stores/AudioManagerStore";
|
import { audioManagerVolumeStore } from "../../Stores/AudioManagerStore";
|
||||||
|
|
||||||
import infoImg from "../images/info.svg";
|
import infoImg from "../images/info.svg";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||||
@ -28,12 +29,12 @@
|
|||||||
let previewCameraPrivacySettings = valueCameraPrivacySettings;
|
let previewCameraPrivacySettings = valueCameraPrivacySettings;
|
||||||
let previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
|
let previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
|
||||||
|
|
||||||
function saveSetting() {
|
async function saveSetting() {
|
||||||
let change = false;
|
let change = false;
|
||||||
|
|
||||||
if (valueLocale !== previewValueLocale) {
|
if (valueLocale !== previewValueLocale) {
|
||||||
previewValueLocale = valueLocale;
|
previewValueLocale = valueLocale;
|
||||||
setCurrentLocale(valueLocale as Locales);
|
await setCurrentLocale(valueLocale as Locales);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueVideo !== previewValueVideo) {
|
if (valueVideo !== previewValueVideo) {
|
||||||
@ -174,7 +175,7 @@
|
|||||||
<div class="nes-select is-dark">
|
<div class="nes-select is-dark">
|
||||||
<select class="languages-switcher" bind:value={valueLocale}>
|
<select class="languages-switcher" bind:value={valueLocale}>
|
||||||
{#each displayableLocales as locale (locale.id)}
|
{#each displayableLocales as locale (locale.id)}
|
||||||
<option value={locale.id}>{`${locale.language} (${locale.country})`}</option>
|
<option value={locale.id}>{`${locale.language} (${locale.region})`}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -191,11 +192,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueCameraPrivacySettings} />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="nes-checkbox is-dark"
|
||||||
|
on:change={(event) => analyticsClient.settingCamera(event.currentTarget.value)}
|
||||||
|
bind:checked={valueCameraPrivacySettings}
|
||||||
|
/>
|
||||||
<span>{$LL.menu.settings.privacySettings.cameraToggle()}</span>
|
<span>{$LL.menu.settings.privacySettings.cameraToggle()}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="nes-checkbox is-dark" bind:checked={valueMicrophonePrivacySettings} />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="nes-checkbox is-dark"
|
||||||
|
on:change={(event) => analyticsClient.settingMicrophone(event.currentTarget.value)}
|
||||||
|
bind:checked={valueMicrophonePrivacySettings}
|
||||||
|
/>
|
||||||
<span>{$LL.menu.settings.privacySettings.microphoneToggle()}</span>
|
<span>{$LL.menu.settings.privacySettings.microphoneToggle()}</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
@ -211,6 +222,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={fullscreen}
|
bind:checked={fullscreen}
|
||||||
|
on:change={(event) => analyticsClient.settingFullscreen(event.currentTarget.value)}
|
||||||
on:change={changeFullscreen}
|
on:change={changeFullscreen}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.menu.settings.fullscreen()}</span>
|
<span>{$LL.menu.settings.fullscreen()}</span>
|
||||||
@ -220,6 +232,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={notification}
|
bind:checked={notification}
|
||||||
|
on:change={(event) => analyticsClient.settingNotification(event.currentTarget.value)}
|
||||||
on:change={changeNotification}
|
on:change={changeNotification}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.menu.settings.notifications()}</span>
|
<span>{$LL.menu.settings.notifications()}</span>
|
||||||
@ -229,6 +242,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={forceCowebsiteTrigger}
|
bind:checked={forceCowebsiteTrigger}
|
||||||
|
on:change={(event) => analyticsClient.settingAskWebsite(event.currentTarget.value)}
|
||||||
on:change={changeForceCowebsiteTrigger}
|
on:change={changeForceCowebsiteTrigger}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.menu.settings.cowebsiteTrigger()}</span>
|
<span>{$LL.menu.settings.cowebsiteTrigger()}</span>
|
||||||
@ -238,6 +252,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={ignoreFollowRequests}
|
bind:checked={ignoreFollowRequests}
|
||||||
|
on:change={(event) => analyticsClient.settingRequestFollow(event.currentTarget.value)}
|
||||||
on:change={changeIgnoreFollowRequests}
|
on:change={changeIgnoreFollowRequests}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.menu.settings.ignoreFollowRequest()}</span>
|
<span>{$LL.menu.settings.ignoreFollowRequest()}</span>
|
||||||
@ -246,6 +261,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="nes-checkbox is-dark"
|
class="nes-checkbox is-dark"
|
||||||
bind:checked={decreaseAudioPlayerVolumeWhileTalking}
|
bind:checked={decreaseAudioPlayerVolumeWhileTalking}
|
||||||
|
on:change={(event) => analyticsClient.settingDecreaseAudioVolume(event.currentTarget.value)}
|
||||||
on:change={changeDecreaseAudioPlayerVolumeWhileTalking}
|
on:change={changeDecreaseAudioPlayerVolumeWhileTalking}
|
||||||
/>
|
/>
|
||||||
<span>{$LL.audio.manager.reduce()}</span>
|
<span>{$LL.audio.manager.reduce()}</span>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
@ -11,7 +12,8 @@
|
|||||||
let errorScreen = get(errorScreenStore);
|
let errorScreen = get(errorScreenStore);
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
window.location.reload();
|
if (errorScreen.type === "unauthorized") void connectionManager.logout();
|
||||||
|
else window.location.reload();
|
||||||
}
|
}
|
||||||
let details = errorScreen.details;
|
let details = errorScreen.details;
|
||||||
let timeVar = errorScreen.timeToRetry ?? 0;
|
let timeVar = errorScreen.timeToRetry ?? 0;
|
||||||
@ -37,9 +39,9 @@
|
|||||||
<p class="details">
|
<p class="details">
|
||||||
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
|
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
|
||||||
</p>
|
</p>
|
||||||
{#if $errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual}
|
{#if ($errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual) || $errorScreenStore.type === "unauthorized"}
|
||||||
<button type="button" class="nes-btn is-primary button" on:click={click}>
|
<button type="button" class="nes-btn is-primary button" on:click={click}>
|
||||||
<img src={reload} alt="" class="reload" />
|
{#if $errorScreenStore.type === "retry"}<img src={reload} alt="" class="reload" />{/if}
|
||||||
{$errorScreenStore.buttonTitle}
|
{$errorScreenStore.buttonTitle}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import { customizeAvailableStore, selectedCollection } from "../../Stores/SelectCharacterSceneStore";
|
import { customizeAvailableStore, selectedCollection } from "../../Stores/SelectCharacterSceneStore";
|
||||||
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -40,13 +41,15 @@
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
||||||
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
on:click={() => analyticsClient.selectWoka()}
|
||||||
|
on:click={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
||||||
>
|
>
|
||||||
{#if $customizeAvailableStore}
|
{#if $customizeAvailableStore}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
||||||
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
on:click={() => analyticsClient.selectCustomWoka()}
|
||||||
|
on:click={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
@ -364,15 +364,13 @@ class ConnectionManager {
|
|||||||
|
|
||||||
if (locale) {
|
if (locale) {
|
||||||
try {
|
try {
|
||||||
if (locales.indexOf(locale) == -1) {
|
if (locales.indexOf(locale) !== -1) {
|
||||||
locales.forEach((l) => {
|
await setCurrentLocale(locale as Locales);
|
||||||
if (l.startsWith(locale.split("-")[0])) {
|
|
||||||
setCurrentLocale(l);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setCurrentLocale(locale as Locales);
|
const nonRegionSpecificLocale = locales.find((l) => l.startsWith(locale.split("-")[0]));
|
||||||
|
if (nonRegionSpecificLocale) {
|
||||||
|
await setCurrentLocale(nonRegionSpecificLocale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Could not set locale", err);
|
console.warn("Could not set locale", err);
|
||||||
|
@ -483,9 +483,15 @@ export class RoomConnection implements RoomConnection {
|
|||||||
}
|
}
|
||||||
case "errorScreenMessage": {
|
case "errorScreenMessage": {
|
||||||
this._errorScreenMessageStream.next(message.errorScreenMessage);
|
this._errorScreenMessageStream.next(message.errorScreenMessage);
|
||||||
if (message.errorScreenMessage.code !== "retry") this.closed = true;
|
console.error("An error occurred server side: " + JSON.stringify(message.errorScreenMessage));
|
||||||
console.error("An error occurred server side: " + message.errorScreenMessage.code);
|
if (message.errorScreenMessage.code !== "retry") {
|
||||||
|
this.closed = true;
|
||||||
|
}
|
||||||
|
if (message.errorScreenMessage.type === "redirect" && message.errorScreenMessage.urlToRedirect) {
|
||||||
|
window.location.assign(message.errorScreenMessage.urlToRedirect);
|
||||||
|
} else {
|
||||||
errorScreenStore.setError(message.errorScreenMessage);
|
errorScreenStore.setError(message.errorScreenMessage);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -61,9 +61,8 @@ export class CustomWokaPreviewer extends Phaser.GameObjects.Container {
|
|||||||
this.frame = this.scene.add.graphics();
|
this.frame = this.scene.add.graphics();
|
||||||
this.turnIcon = this.scene.add
|
this.turnIcon = this.scene.add
|
||||||
.image(this.background.displayWidth * 0.35, this.background.displayHeight * 0.35, "iconTurn")
|
.image(this.background.displayWidth * 0.35, this.background.displayHeight * 0.35, "iconTurn")
|
||||||
.setScale(0.25)
|
.setScale(0.2)
|
||||||
.setTintFill(0xffffff)
|
.setAlpha(0.75);
|
||||||
.setAlpha(0.5);
|
|
||||||
|
|
||||||
this.drawFrame();
|
this.drawFrame();
|
||||||
this.setSize(this.SIZE, this.SIZE);
|
this.setSize(this.SIZE, this.SIZE);
|
||||||
@ -130,11 +129,11 @@ export class CustomWokaPreviewer extends Phaser.GameObjects.Container {
|
|||||||
this.changeAnimation(direction, moving);
|
this.changeAnimation(direction, moving);
|
||||||
|
|
||||||
this.turnIconTween?.stop();
|
this.turnIconTween?.stop();
|
||||||
this.turnIcon.setScale(0.25);
|
this.turnIcon.setScale(0.2);
|
||||||
this.turnIconTween = this.scene.tweens.add({
|
this.turnIconTween = this.scene.tweens.add({
|
||||||
targets: [this.turnIcon],
|
targets: [this.turnIcon],
|
||||||
duration: 100,
|
duration: 100,
|
||||||
scale: 0.2,
|
scale: 0.15,
|
||||||
yoyo: true,
|
yoyo: true,
|
||||||
ease: Easing.SineEaseIn,
|
ease: Easing.SineEaseIn,
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ export interface IconButtonConfig {
|
|||||||
hover: IconButtonAppearanceConfig;
|
hover: IconButtonAppearanceConfig;
|
||||||
pressed: IconButtonAppearanceConfig;
|
pressed: IconButtonAppearanceConfig;
|
||||||
selected: IconButtonAppearanceConfig;
|
selected: IconButtonAppearanceConfig;
|
||||||
|
iconScale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IconButtonAppearanceConfig {
|
export interface IconButtonAppearanceConfig {
|
||||||
@ -34,7 +35,7 @@ export class IconButton extends Phaser.GameObjects.Container {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
this.background = this.scene.add.graphics();
|
this.background = this.scene.add.graphics();
|
||||||
this.icon = this.scene.add.image(0, 0, this.config.iconTextureKey);
|
this.icon = this.scene.add.image(0, 0, this.config.iconTextureKey).setScale(config.iconScale ?? 1);
|
||||||
this.drawBackground(this.config.idle);
|
this.drawBackground(this.config.idle);
|
||||||
|
|
||||||
this.add([this.background, this.icon]);
|
this.add([this.background, this.icon]);
|
||||||
|
@ -4,7 +4,7 @@ import { Character } from "../Entity/Character";
|
|||||||
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 type { PlayerAnimationDirections } from "../Player/Animation";
|
import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||||
import type { Unsubscriber } from "svelte/store";
|
import { get, Unsubscriber } from "svelte/store";
|
||||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||||
import type CancelablePromise from "cancelable-promise";
|
import type CancelablePromise from "cancelable-promise";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
@ -113,7 +113,7 @@ export class RemotePlayer extends Character implements ActivatableInterface {
|
|||||||
const actions: ActionsMenuAction[] = [];
|
const actions: ActionsMenuAction[] = [];
|
||||||
if (this.visitCardUrl) {
|
if (this.visitCardUrl) {
|
||||||
actions.push({
|
actions.push({
|
||||||
actionName: LL.woka.menu.businessCard(),
|
actionName: get(LL).woka.menu.businessCard(),
|
||||||
protected: true,
|
protected: true,
|
||||||
priority: 1,
|
priority: 1,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
@ -125,8 +125,8 @@ export class RemotePlayer extends Character implements ActivatableInterface {
|
|||||||
|
|
||||||
actions.push({
|
actions.push({
|
||||||
actionName: blackListManager.isBlackListed(this.userUuid)
|
actionName: blackListManager.isBlackListed(this.userUuid)
|
||||||
? LL.report.block.unblock()
|
? get(LL).report.block.unblock()
|
||||||
: LL.report.block.block(),
|
: get(LL).report.block.block(),
|
||||||
protected: true,
|
protected: true,
|
||||||
priority: -1,
|
priority: -1,
|
||||||
style: "is-error",
|
style: "is-error",
|
||||||
|
@ -76,6 +76,7 @@ export class ActivatablesManager {
|
|||||||
const currentPlayerPos = this.currentPlayer.getDirectionalActivationPosition(
|
const currentPlayerPos = this.currentPlayer.getDirectionalActivationPosition(
|
||||||
this.directionalActivationPositionShift
|
this.directionalActivationPositionShift
|
||||||
);
|
);
|
||||||
|
this.activatableObjectsDistances.clear();
|
||||||
for (const object of objects) {
|
for (const object of objects) {
|
||||||
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
|
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
|
||||||
this.activatableObjectsDistances.set(object, distance);
|
this.activatableObjectsDistances.set(object, distance);
|
||||||
|
@ -1135,6 +1135,13 @@ export class GameScene extends DirtyScene {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(
|
||||||
|
iframeListener.stopSoundStream.subscribe((stopSoundEvent) => {
|
||||||
|
const url = new URL(stopSoundEvent.url, this.MapUrlFile);
|
||||||
|
soundManager.stopSound(this.sound, url.toString());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
this.iframeSubscriptionList.push(
|
this.iframeSubscriptionList.push(
|
||||||
iframeListener.addActionsMenuKeyToRemotePlayerStream.subscribe((data) => {
|
iframeListener.addActionsMenuKeyToRemotePlayerStream.subscribe((data) => {
|
||||||
this.MapPlayersByKey.get(data.id)?.registerActionsMenuAction({
|
this.MapPlayersByKey.get(data.id)?.registerActionsMenuAction({
|
||||||
|
@ -94,6 +94,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public create(): void {
|
public create(): void {
|
||||||
|
this.tryLoadLastUsedWokaLayers();
|
||||||
waScaleManager.zoomModifier = 1;
|
waScaleManager.zoomModifier = 1;
|
||||||
this.createSlotBackgroundTextures();
|
this.createSlotBackgroundTextures();
|
||||||
this.initializeCustomWokaPreviewer();
|
this.initializeCustomWokaPreviewer();
|
||||||
@ -149,12 +150,35 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
this.scene.run(SelectCharacterSceneName);
|
this.scene.run(SelectCharacterSceneName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private tryLoadLastUsedWokaLayers(): void {
|
||||||
|
try {
|
||||||
|
const savedWokaLayers = gameManager.getCharacterLayers();
|
||||||
|
if (savedWokaLayers && savedWokaLayers.length !== 0) {
|
||||||
|
this.selectedLayers = [];
|
||||||
|
for (let i = 0; i < savedWokaLayers.length; i += 1) {
|
||||||
|
this.selectedLayers.push(
|
||||||
|
this.layers[i].findIndex((item) => item.id === gameManager.getCharacterLayers()[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.warn("Cannot load previous WOKA");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private createSlotBackgroundTextures(): void {
|
private createSlotBackgroundTextures(): void {
|
||||||
for (let i = 0; i < 4; i += 1) {
|
for (let i = 0; i < 4; i += 1) {
|
||||||
if (this.textures.getTextureKeys().includes(`floorTexture${i}`)) {
|
if (this.textures.getTextureKeys().includes(`floorTexture${i}`)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
TexturesHelper.createFloorRectangleTexture(this, `floorTexture${i}`, 50, 50, "floorTiles", i);
|
TexturesHelper.createFloorRectangleTexture(
|
||||||
|
this,
|
||||||
|
`floorTexture${i}`,
|
||||||
|
WokaBodyPartSlot.SIZE,
|
||||||
|
WokaBodyPartSlot.SIZE,
|
||||||
|
"floorTiles",
|
||||||
|
i
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,15 +237,16 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
),
|
),
|
||||||
[CustomWokaBodyPart.Body]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconBody")),
|
[CustomWokaBodyPart.Body]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconBody")),
|
||||||
[CustomWokaBodyPart.Clothes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconClothes")),
|
[CustomWokaBodyPart.Clothes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconClothes")),
|
||||||
[CustomWokaBodyPart.Eyes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconEyes")),
|
[CustomWokaBodyPart.Eyes]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconEyes", 0.7)),
|
||||||
[CustomWokaBodyPart.Hair]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHair")),
|
[CustomWokaBodyPart.Hair]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHair")),
|
||||||
[CustomWokaBodyPart.Hat]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHat")),
|
[CustomWokaBodyPart.Hat]: new IconButton(this, 0, 0, this.getDefaultIconButtonConfig("iconHat")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDefaultIconButtonConfig(iconTextureKey: string): IconButtonConfig {
|
private getDefaultIconButtonConfig(iconTextureKey: string, iconScale?: number): IconButtonConfig {
|
||||||
return {
|
return {
|
||||||
iconTextureKey,
|
iconTextureKey,
|
||||||
|
iconScale,
|
||||||
width: 25,
|
width: 25,
|
||||||
height: 25,
|
height: 25,
|
||||||
idle: {
|
idle: {
|
||||||
@ -327,13 +352,14 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleCustomWokaPreviewerOnResize(): void {
|
private handleCustomWokaPreviewerOnResize(): void {
|
||||||
|
const ratio = innerHeight / innerWidth;
|
||||||
this.customWokaPreviewer.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
this.customWokaPreviewer.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
|
||||||
this.customWokaPreviewer.y = this.customWokaPreviewer.displayHeight * 0.5 + 10;
|
this.customWokaPreviewer.y = this.customWokaPreviewer.displayHeight * 0.5 + (ratio > 1.6 ? 40 : 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBodyPartButtonsOnResize(): void {
|
private handleBodyPartButtonsOnResize(): void {
|
||||||
const ratio = innerHeight / innerWidth;
|
const ratio = innerHeight / innerWidth;
|
||||||
const slotDimension = 50;
|
const slotDimension = WokaBodyPartSlot.SIZE;
|
||||||
|
|
||||||
for (const part in this.bodyPartsButtons) {
|
for (const part in this.bodyPartsButtons) {
|
||||||
this.bodyPartsButtons[part as CustomWokaBodyPart].setDisplaySize(slotDimension, slotDimension);
|
this.bodyPartsButtons[part as CustomWokaBodyPart].setDisplaySize(slotDimension, slotDimension);
|
||||||
@ -420,7 +446,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
|
|
||||||
private handleRandomizeButtonOnResize(): void {
|
private handleRandomizeButtonOnResize(): void {
|
||||||
const x =
|
const x =
|
||||||
this.customWokaPreviewer.x +
|
this.customWokaPreviewer.x -
|
||||||
(this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5;
|
(this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5;
|
||||||
const y =
|
const y =
|
||||||
this.customWokaPreviewer.y +
|
this.customWokaPreviewer.y +
|
||||||
@ -431,7 +457,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
|
|
||||||
private handleFinishButtonOnResize(): void {
|
private handleFinishButtonOnResize(): void {
|
||||||
const x =
|
const x =
|
||||||
this.customWokaPreviewer.x -
|
this.customWokaPreviewer.x +
|
||||||
(this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5;
|
(this.customWokaPreviewer.displayWidth - this.randomizeButton.displayWidth) * 0.5;
|
||||||
const y =
|
const y =
|
||||||
this.customWokaPreviewer.y +
|
this.customWokaPreviewer.y +
|
||||||
|
@ -6,7 +6,6 @@ import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
|||||||
import { localeDetector } from "../../i18n/locales";
|
import { localeDetector } from "../../i18n/locales";
|
||||||
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
|
||||||
import { isErrorApiData } from "../../Messages/JsonMessages/ErrorApiData";
|
import { isErrorApiData } from "../../Messages/JsonMessages/ErrorApiData";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
|
||||||
|
|
||||||
export const EntrySceneName = "EntryScene";
|
export const EntrySceneName = "EntryScene";
|
||||||
|
|
||||||
@ -49,9 +48,7 @@ export class EntryScene extends Scene {
|
|||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
const errorType = isErrorApiData.safeParse(err?.response?.data);
|
||||||
if (errorType.success) {
|
if (errorType.success) {
|
||||||
if (errorType.data.type === "unauthorized") {
|
if (errorType.data.type === "redirect") {
|
||||||
void connectionManager.logout();
|
|
||||||
} else if (errorType.data.type === "redirect") {
|
|
||||||
window.location.assign(errorType.data.urlToRedirect);
|
window.location.assign(errorType.data.urlToRedirect);
|
||||||
} else errorScreenStore.setError(err?.response?.data);
|
} else errorScreenStore.setError(err?.response?.data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -18,6 +18,7 @@ import { DraggableGrid } from "@home-based-studio/phaser3-utils";
|
|||||||
import { WokaSlot } from "../Components/SelectWoka/WokaSlot";
|
import { WokaSlot } from "../Components/SelectWoka/WokaSlot";
|
||||||
import { DraggableGridEvent } from "@home-based-studio/phaser3-utils/lib/utils/gui/containers/grids/DraggableGrid";
|
import { DraggableGridEvent } from "@home-based-studio/phaser3-utils/lib/utils/gui/containers/grids/DraggableGrid";
|
||||||
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
|
||||||
|
import { myCameraVisibilityStore } from "../../Stores/MyCameraStoreVisibility";
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||||
@ -133,6 +134,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.selectedWoka = null;
|
this.selectedWoka = null;
|
||||||
|
myCameraVisibilityStore.set(false);
|
||||||
this.scene.sleep(SelectCharacterSceneName);
|
this.scene.sleep(SelectCharacterSceneName);
|
||||||
this.scene.run(CustomizeSceneName);
|
this.scene.run(CustomizeSceneName);
|
||||||
selectCharacterSceneVisibleStore.set(false);
|
selectCharacterSceneVisibleStore.set(false);
|
||||||
|
@ -87,7 +87,10 @@ function createChatMessagesStore() {
|
|||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addExternalMessage(authorId: number, text: string) {
|
/**
|
||||||
|
* @param origin The iframe that originated this message (if triggered from the Scripting API), or undefined otherwise.
|
||||||
|
*/
|
||||||
|
addExternalMessage(authorId: number, text: string, origin?: Window) {
|
||||||
update((list) => {
|
update((list) => {
|
||||||
const lastMessage = list[list.length - 1];
|
const lastMessage = list[list.length - 1];
|
||||||
if (
|
if (
|
||||||
@ -106,7 +109,7 @@ function createChatMessagesStore() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
iframeListener.sendUserInputChat(text);
|
iframeListener.sendUserInputChat(text, origin);
|
||||||
return list;
|
return list;
|
||||||
});
|
});
|
||||||
chatVisibilityStore.set(true);
|
chatVisibilityStore.set(true);
|
||||||
|
@ -2,7 +2,6 @@ import { get, writable } from "svelte/store";
|
|||||||
import Timeout = NodeJS.Timeout;
|
import Timeout = NodeJS.Timeout;
|
||||||
import { userIsAdminStore } from "./GameStore";
|
import { userIsAdminStore } from "./GameStore";
|
||||||
import { CONTACT_URL, IDENTITY_URL, PROFILE_URL } from "../Enum/EnvironmentVariable";
|
import { CONTACT_URL, IDENTITY_URL, PROFILE_URL } from "../Enum/EnvironmentVariable";
|
||||||
import { analyticsClient } from "../Administration/AnalyticsClient";
|
|
||||||
import type { Translation } from "../i18n/i18n-types";
|
import type { Translation } from "../i18n/i18n-types";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||||
@ -14,7 +13,6 @@ export const userIsConnected = writable(false);
|
|||||||
export const profileAvailable = writable(true);
|
export const profileAvailable = writable(true);
|
||||||
|
|
||||||
menuVisiblilityStore.subscribe((value) => {
|
menuVisiblilityStore.subscribe((value) => {
|
||||||
if (value) analyticsClient.openedMenu();
|
|
||||||
if (userIsConnected && value && IDENTITY_URL != null) {
|
if (userIsConnected && value && IDENTITY_URL != null) {
|
||||||
axios.get(getMeUrl()).catch((err) => {
|
axios.get(getMeUrl()).catch((err) => {
|
||||||
console.error("menuVisiblilityStore => err => ", err);
|
console.error("menuVisiblilityStore => err => ", err);
|
||||||
|
@ -8,6 +8,7 @@ import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils";
|
|||||||
import { LayoutMode } from "./LayoutManager";
|
import { LayoutMode } from "./LayoutManager";
|
||||||
import type { CoWebsite } from "./CoWebsite/CoWesbite";
|
import type { CoWebsite } from "./CoWebsite/CoWesbite";
|
||||||
import type CancelablePromise from "cancelable-promise";
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
import { analyticsClient } from "../Administration/AnalyticsClient";
|
||||||
|
|
||||||
export enum iframeStates {
|
export enum iframeStates {
|
||||||
closed = 1,
|
closed = 1,
|
||||||
@ -126,6 +127,7 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
const buttonCloseCoWebsite = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
const buttonCloseCoWebsite = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
|
||||||
buttonCloseCoWebsite.addEventListener("click", () => {
|
buttonCloseCoWebsite.addEventListener("click", () => {
|
||||||
|
analyticsClient.closeMultiIframe();
|
||||||
const coWebsite = this.getMainCoWebsite();
|
const coWebsite = this.getMainCoWebsite();
|
||||||
|
|
||||||
if (!coWebsite) {
|
if (!coWebsite) {
|
||||||
@ -143,6 +145,7 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
|
const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
|
||||||
buttonFullScreenFrame.addEventListener("click", () => {
|
buttonFullScreenFrame.addEventListener("click", () => {
|
||||||
|
analyticsClient.fullScreenMultiIframe();
|
||||||
buttonFullScreenFrame.blur();
|
buttonFullScreenFrame.blur();
|
||||||
this.fullscreen();
|
this.fullscreen();
|
||||||
});
|
});
|
||||||
@ -159,6 +162,7 @@ class CoWebsiteManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
buttonSwipe.addEventListener("click", () => {
|
buttonSwipe.addEventListener("click", () => {
|
||||||
|
analyticsClient.switchMultiIframe();
|
||||||
const mainCoWebsite = this.getMainCoWebsite();
|
const mainCoWebsite = this.getMainCoWebsite();
|
||||||
const highlightedEmbed = get(highlightedEmbedScreen);
|
const highlightedEmbed = get(highlightedEmbedScreen);
|
||||||
if (highlightedEmbed?.type === "cowebsite") {
|
if (highlightedEmbed?.type === "cowebsite") {
|
||||||
|
4
front/src/i18n/.gitignore
vendored
4
front/src/i18n/.gitignore
vendored
@ -1,3 +1 @@
|
|||||||
i18n-svelte.ts
|
i18n-*.ts
|
||||||
i18n-types.ts
|
|
||||||
i18n-util.ts
|
|
||||||
|
@ -16,8 +16,6 @@ import trigger from "./trigger";
|
|||||||
|
|
||||||
const de_DE: Translation = {
|
const de_DE: Translation = {
|
||||||
...(en_US as Translation),
|
...(en_US as Translation),
|
||||||
language: "Deutsch",
|
|
||||||
country: "Deutschland",
|
|
||||||
audio,
|
audio,
|
||||||
camera,
|
camera,
|
||||||
chat,
|
chat,
|
||||||
|
@ -77,7 +77,8 @@ const menu: NonNullable<Translation["menu"]> = {
|
|||||||
description: "Link zu diesem Raum teilen!",
|
description: "Link zu diesem Raum teilen!",
|
||||||
copy: "Kopieren",
|
copy: "Kopieren",
|
||||||
share: "Teilen",
|
share: "Teilen",
|
||||||
walk_automatically_to_position: "Automatisch zu meiner Position gehen",
|
walkAutomaticallyToPosition: "Automatisch zu meiner Position gehen",
|
||||||
|
selectEntryPoint: "Select an entry point",
|
||||||
},
|
},
|
||||||
globalMessage: {
|
globalMessage: {
|
||||||
text: "Text",
|
text: "Text",
|
||||||
|
@ -14,8 +14,6 @@ import emoji from "./emoji";
|
|||||||
import trigger from "./trigger";
|
import trigger from "./trigger";
|
||||||
|
|
||||||
const en_US: BaseTranslation = {
|
const en_US: BaseTranslation = {
|
||||||
language: "English",
|
|
||||||
country: "United States",
|
|
||||||
audio,
|
audio,
|
||||||
camera,
|
camera,
|
||||||
chat,
|
chat,
|
||||||
|
@ -77,7 +77,8 @@ const menu: BaseTranslation = {
|
|||||||
description: "Share the link of the room!",
|
description: "Share the link of the room!",
|
||||||
copy: "Copy",
|
copy: "Copy",
|
||||||
share: "Share",
|
share: "Share",
|
||||||
walk_automatically_to_position: "Walk automatically to my position",
|
walkAutomaticallyToPosition: "Walk automatically to my position",
|
||||||
|
selectEntryPoint: "Select an entry point",
|
||||||
},
|
},
|
||||||
globalMessage: {
|
globalMessage: {
|
||||||
text: "Text",
|
text: "Text",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type { AsyncFormattersInitializer } from "typesafe-i18n";
|
import type { FormattersInitializer } from "typesafe-i18n";
|
||||||
import type { Locales, Formatters } from "./i18n-types";
|
import type { Locales, Formatters } from "./i18n-types";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
export const initFormatters: AsyncFormattersInitializer<Locales, Formatters> = async () => {
|
export const initFormatters: FormattersInitializer<Locales, Formatters> = async () => {
|
||||||
const formatters: Formatters = {
|
const formatters: Formatters = {
|
||||||
// add your formatter functions here
|
// add your formatter functions here
|
||||||
};
|
};
|
||||||
|
@ -1,70 +1,44 @@
|
|||||||
import { detectLocale, navigatorDetector, initLocalStorageDetector } from "typesafe-i18n/detectors";
|
import { detectLocale, navigatorDetector, initLocalStorageDetector } from "typesafe-i18n/detectors";
|
||||||
import { FALLBACK_LOCALE } from "../Enum/EnvironmentVariable";
|
import { FALLBACK_LOCALE } from "../Enum/EnvironmentVariable";
|
||||||
import { initI18n, setLocale, locale } from "./i18n-svelte";
|
import { setLocale } from "./i18n-svelte";
|
||||||
import type { Locales } from "./i18n-types";
|
import type { Locales } from "./i18n-types";
|
||||||
import { baseLocale, getTranslationForLocale, locales } from "./i18n-util";
|
import { baseLocale, locales } from "./i18n-util";
|
||||||
import { get } from "svelte/store";
|
import { loadLocaleAsync } from "./i18n-util.async";
|
||||||
|
|
||||||
const fallbackLocale = FALLBACK_LOCALE || baseLocale;
|
const fallbackLocale = (FALLBACK_LOCALE || baseLocale) as Locales;
|
||||||
const localStorageProperty = "language";
|
const localStorageProperty = "language";
|
||||||
|
|
||||||
export const localeDetector = async () => {
|
export const localeDetector = async () => {
|
||||||
const exist = localStorage.getItem(localStorageProperty);
|
const exist = localStorage.getItem(localStorageProperty);
|
||||||
let detectedLocale: Locales = fallbackLocale as Locales;
|
let detectedLocale: Locales = fallbackLocale;
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
const localStorageDetector = initLocalStorageDetector(localStorageProperty);
|
const localStorageDetector = initLocalStorageDetector(localStorageProperty);
|
||||||
detectedLocale = detectLocale(fallbackLocale, locales, localStorageDetector) as Locales;
|
detectedLocale = detectLocale(fallbackLocale, locales, localStorageDetector);
|
||||||
} else {
|
} else {
|
||||||
detectedLocale = detectLocale(fallbackLocale, locales, navigatorDetector) as Locales;
|
detectedLocale = detectLocale(fallbackLocale, locales, navigatorDetector);
|
||||||
}
|
}
|
||||||
|
|
||||||
await initI18n(detectedLocale);
|
await setCurrentLocale(detectedLocale);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setCurrentLocale = (locale: Locales) => {
|
export const setCurrentLocale = async (locale: Locales) => {
|
||||||
localStorage.setItem(localStorageProperty, locale);
|
localStorage.setItem(localStorageProperty, locale);
|
||||||
setLocale(locale).catch(() => {
|
await loadLocaleAsync(locale);
|
||||||
console.log("Cannot reload the locale!");
|
setLocale(locale);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DisplayableLocale = { id: Locales; language: string; country: string };
|
export const displayableLocales: { id: Locales; language: string; region: string }[] = locales.map((locale) => {
|
||||||
|
const [language, region] = locale.split("-");
|
||||||
|
|
||||||
function getDisplayableLocales() {
|
// backwards compatibility
|
||||||
const localesObject: DisplayableLocale[] = [];
|
if (!Intl.DisplayNames) {
|
||||||
locales.forEach((locale) => {
|
return { id: locale, language, region };
|
||||||
getTranslationForLocale(locale)
|
}
|
||||||
.then((translations) => {
|
|
||||||
localesObject.push({
|
return {
|
||||||
id: locale,
|
id: locale,
|
||||||
language: translations.language,
|
language: new Intl.DisplayNames(locale, { type: "language" }).of(language),
|
||||||
country: translations.country,
|
region: new Intl.DisplayNames(locale, { type: "region" }).of(region),
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return localesObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const displayableLocales = getDisplayableLocales();
|
|
||||||
|
|
||||||
export const i18nJson = (text: string): string => {
|
|
||||||
if (text.trim().startsWith("{")) {
|
|
||||||
try {
|
|
||||||
const textObject = JSON.parse(text);
|
|
||||||
if (textObject[get(locale)]) {
|
|
||||||
return textObject[get(locale)];
|
|
||||||
} else if (Object.keys(textObject).length > 0) {
|
|
||||||
// fallback to first value
|
|
||||||
return textObject[Object.keys(textObject)[0]];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
|
|
||||||
&-buffer {
|
&-buffer {
|
||||||
iframe {
|
iframe {
|
||||||
z-index: 45 !important;
|
z-index: 201 !important;
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
//"module": "CommonJS",
|
//"module": "CommonJS",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES2017",
|
"target": "ES2020",
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
@ -7,6 +7,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@16bits/nes.css/-/nes.css-2.3.2.tgz#e69db834119b33ae8d3cb044f106a07a17cadd6f"
|
resolved "https://registry.yarnpkg.com/@16bits/nes.css/-/nes.css-2.3.2.tgz#e69db834119b33ae8d3cb044f106a07a17cadd6f"
|
||||||
integrity sha512-nEM5PIth+Bab5JSOa4uUR+PMNUsNTYxA55oVlG3gXI/4LoYtWS767Uv9Pu/KCbHXVvnIjt4ZXt13kZw3083qTw==
|
integrity sha512-nEM5PIth+Bab5JSOa4uUR+PMNUsNTYxA55oVlG3gXI/4LoYtWS767Uv9Pu/KCbHXVvnIjt4ZXt13kZw3083qTw==
|
||||||
|
|
||||||
|
"@anatine/zod-openapi@^1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@anatine/zod-openapi/-/zod-openapi-1.3.0.tgz#b5b38c3d821b79674226aa7b327c88c371860d0d"
|
||||||
|
integrity sha512-l54DypUdDsIq1Uwjv4ib9IBkTXMKZQLUj7qvdFL51EExC5LdSSqOlTOyaVVZZGYgWPKM7ZjGklhdoknLz4EC+w==
|
||||||
|
dependencies:
|
||||||
|
ts-deepmerge "^1.1.0"
|
||||||
|
validator "^13.7.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.14.0":
|
"@babel/runtime@^7.14.0":
|
||||||
version "7.14.0"
|
version "7.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
|
||||||
@ -77,10 +85,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@geprog/vite-plugin-env-config/-/vite-plugin-env-config-4.0.3.tgz#ca04bd9ad9f55fe568917db79266afe8e766e25e"
|
resolved "https://registry.yarnpkg.com/@geprog/vite-plugin-env-config/-/vite-plugin-env-config-4.0.3.tgz#ca04bd9ad9f55fe568917db79266afe8e766e25e"
|
||||||
integrity sha512-2HDCV+6XXJjSuBAhDWLRr111buMQ3bIZrKo3dymIhEJ4oJCC/3yDqg7HDQIn8Y8KKbsM0AtuHMZW4yz2tPBsYg==
|
integrity sha512-2HDCV+6XXJjSuBAhDWLRr111buMQ3bIZrKo3dymIhEJ4oJCC/3yDqg7HDQIn8Y8KKbsM0AtuHMZW4yz2tPBsYg==
|
||||||
|
|
||||||
"@home-based-studio/phaser3-utils@^0.4.2":
|
"@home-based-studio/phaser3-utils@^0.4.7":
|
||||||
version "0.4.2"
|
version "0.4.7"
|
||||||
resolved "https://registry.yarnpkg.com/@home-based-studio/phaser3-utils/-/phaser3-utils-0.4.2.tgz#b2c1815a6b51321ea8dab027b5badcf714d99fd6"
|
resolved "https://registry.yarnpkg.com/@home-based-studio/phaser3-utils/-/phaser3-utils-0.4.7.tgz#d0464c81cb27328657d3fd048396f6936e200c48"
|
||||||
integrity sha512-S0VkAq3z0Kf0vEUUyCDes911icvc+nkUq7lGp23zD/5lk7LTGM51NswSAfel7Rm/DLY8IBxvDTBJADTf/De82w==
|
integrity sha512-gYt1mkuad85uzYwHK0+wp+mrsGASV4sRZPaHZHnO8A2ofTAnX36S3PcI+BqKchdJ0I7jvBQcfh0yp1Ug0BHT+A==
|
||||||
dependencies:
|
dependencies:
|
||||||
phaser "3.55.1"
|
phaser "3.55.1"
|
||||||
|
|
||||||
@ -2060,6 +2068,13 @@ onetime@^5.1.0, onetime@^5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
openapi3-ts@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.2.tgz#a200dd838bf24c9086c8eedcfeb380b7eb31e82a"
|
||||||
|
integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==
|
||||||
|
dependencies:
|
||||||
|
yaml "^1.10.2"
|
||||||
|
|
||||||
optionator@^0.9.1:
|
optionator@^0.9.1:
|
||||||
version "0.9.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||||
@ -2880,6 +2895,11 @@ to-regex-range@^5.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
ts-deepmerge@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-1.1.0.tgz#4236ae102199affe2e77690dcf198a420160eef2"
|
||||||
|
integrity sha512-VvwaV/6RyYMwT9d8dClmfHIsG2PCdm6WY430QKOIbPRR50Y/1Q2ilp4i2XEZeHFcNqfaYnAQzpyUC6XA0AqqBg==
|
||||||
|
|
||||||
ts-deferred@^1.0.4:
|
ts-deferred@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/ts-deferred/-/ts-deferred-1.0.4.tgz#58145ebaeef5b8f2a290b8cec3d060839f9489c7"
|
resolved "https://registry.yarnpkg.com/ts-deferred/-/ts-deferred-1.0.4.tgz#58145ebaeef5b8f2a290b8cec3d060839f9489c7"
|
||||||
@ -2996,10 +3016,10 @@ type-fest@^0.21.3:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
|
||||||
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
||||||
|
|
||||||
typesafe-i18n@^2.59.0:
|
typesafe-i18n@^5.4.0:
|
||||||
version "2.59.0"
|
version "5.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/typesafe-i18n/-/typesafe-i18n-2.59.0.tgz#09a9a32e61711418d927a389fa52e1c06a5fa5c4"
|
resolved "https://registry.yarnpkg.com/typesafe-i18n/-/typesafe-i18n-5.4.0.tgz#cab696160bb144c387d7cbd13f7a728aa8371777"
|
||||||
integrity sha512-Qv3Mrwmb8b73VNzQDPHPECzwymdBRVyDiZ3w2qnp4c2iv/7TGuiJegNHT/l3MooEN7IPbSpc5tbXw2x3MbGtFg==
|
integrity sha512-htewpld3FzZQv3Y1G31w54bofaaKR11MCkDK0FIYuXCpX72y1G6fkXUDslqzZCyVkZWRnIhY8leviNDxLwEzRw==
|
||||||
|
|
||||||
typescript@*:
|
typescript@*:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
@ -3071,6 +3091,11 @@ validate-npm-package-license@^3.0.1:
|
|||||||
spdx-correct "^3.0.0"
|
spdx-correct "^3.0.0"
|
||||||
spdx-expression-parse "^3.0.0"
|
spdx-expression-parse "^3.0.0"
|
||||||
|
|
||||||
|
validator@^13.7.0:
|
||||||
|
version "13.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
||||||
|
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
||||||
|
|
||||||
vite-plugin-rewrite-all@^0.1.2:
|
vite-plugin-rewrite-all@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vite-plugin-rewrite-all/-/vite-plugin-rewrite-all-0.1.2.tgz#312bbcd76c700ceac5153bfc5ad7e3e3e4bc9606"
|
resolved "https://registry.yarnpkg.com/vite-plugin-rewrite-all/-/vite-plugin-rewrite-all-0.1.2.tgz#312bbcd76c700ceac5153bfc5ad7e3e3e4bc9606"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
let menuIframeApi = undefined;
|
let menuIframeApi = undefined;
|
||||||
|
|
||||||
WA.ui.registerMenuCommand('custom callback menu', () => {
|
WA.ui.registerMenuCommand('custom callback menu', () => {
|
||||||
WA.nav.openTab("https://workadventu.re/");
|
WA.chat.sendChatMessage('Custom menu clicked', 'Mr Robot');
|
||||||
})
|
})
|
||||||
|
|
||||||
WA.ui.registerMenuCommand('custom iframe menu', {iframe: 'customIframeMenu.html'});
|
WA.ui.registerMenuCommand('custom iframe menu', {iframe: 'customIframeMenu.html'});
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
{
|
{
|
||||||
"fontfamily":"Sans Serif",
|
"fontfamily":"Sans Serif",
|
||||||
"pixelsize":13,
|
"pixelsize":13,
|
||||||
"text":"Test:\nClick on tne WOKA name\n\nResult:\nThe tab with all emoji should be displayed\n\nTest:\nChoose on emoji and click on it\n\nResult:\nEmoji will be play at the top of your WOKA",
|
"text":"Test:\nClick on the WOKA name\n\nResult:\nThe tab with all emojis should be displayed\n\nTest:\nChoose on emoji and click on it\n\nResult:\nEmoji will be displayed above your WOKA",
|
||||||
"wrap":true
|
"wrap":true
|
||||||
},
|
},
|
||||||
"type":"",
|
"type":"",
|
||||||
|
@ -216,7 +216,7 @@
|
|||||||
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
|
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test a iframe opened by a script can use Iframe API</a>
|
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test a iframe opened by a script can use Iframe API <i>(already automated in E2E tests)</i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -224,7 +224,7 @@
|
|||||||
<input type="radio" name="test-custom-menu"> Success <input type="radio" name="test-custom-menu"> Failure <input type="radio" name="test-custom-menu" checked> Pending
|
<input type="radio" name="test-custom-menu"> Success <input type="radio" name="test-custom-menu"> Failure <input type="radio" name="test-custom-menu" checked> Pending
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="testLink" data-testmap="Metadata/customMenu.json" target="_blank">Testing add a custom menu by scripting API</a>
|
<a href="#" class="testLink" data-testmap="Metadata/customMenu.json" target="_blank">Testing add a custom menu by scripting API <i>(already automated in E2E tests)</i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { extendApi } from "@anatine/zod-openapi";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WARNING! The original file is in /messages/JsonMessages.
|
* WARNING! The original file is in /messages/JsonMessages.
|
||||||
@ -6,10 +7,17 @@ import { z } from "zod";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const isAdminApiData = z.object({
|
export const isAdminApiData = z.object({
|
||||||
userUuid: z.string(),
|
// @ts-ignore
|
||||||
email: z.nullable(z.string()),
|
userUuid: extendApi(z.string(), { example: "998ce839-3dea-4698-8b41-ebbdf7688ad9" }),
|
||||||
roomUrl: z.string(),
|
email: extendApi(z.nullable(z.string()), {
|
||||||
mapUrlStart: z.string(),
|
description: "The email of the current user.",
|
||||||
|
example: "example@workadventu.re",
|
||||||
|
}),
|
||||||
|
roomUrl: extendApi(z.string(), { example: "/@/teamSlug/worldSlug/roomSlug" }),
|
||||||
|
mapUrlStart: extendApi(z.string(), {
|
||||||
|
description: "The full URL to the JSON map file",
|
||||||
|
example: "https://myuser.github.io/myrepo/map.json",
|
||||||
|
}),
|
||||||
messages: z.optional(z.array(z.unknown())),
|
messages: z.optional(z.array(z.unknown())),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,40 +1,129 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { extendApi } from "@anatine/zod-openapi";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WARNING! The original file is in /messages/JsonMessages.
|
* WARNING! The original file is in /messages/JsonMessages.
|
||||||
* All other files are automatically copied from this file on container startup / build
|
* All other files are automatically copied from this file on container startup / build
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const isErrorApiErrorData = z.object({
|
export const isErrorApiErrorData = extendApi(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
z.object({
|
||||||
type: z.literal("error"),
|
type: z.literal("error"),
|
||||||
code: z.string(),
|
code: extendApi(z.string(), {
|
||||||
title: z.string(),
|
description: "The system code of an error, it must be in SCREAMING_SNAKE_CASE.",
|
||||||
subtitle: z.string(),
|
example: "ROOM_NOT_FOUND",
|
||||||
details: z.string(),
|
}),
|
||||||
image: z.string(),
|
title: extendApi(z.string(), { description: "Big title displayed on the error screen.", example: "ERROR" }),
|
||||||
});
|
subtitle: extendApi(z.string(), {
|
||||||
|
description: "Subtitle displayed to let the user know what is the main subject of the error.",
|
||||||
|
example: "The room was not found.",
|
||||||
|
}),
|
||||||
|
details: extendApi(z.string(), {
|
||||||
|
description: "Some others details on what the user can do if he don't understand the error.",
|
||||||
|
example:
|
||||||
|
"If you would like more information, you can contact the administrator or us at example@workadventu.re.",
|
||||||
|
}),
|
||||||
|
image: extendApi(z.string(), {
|
||||||
|
description: "The URL of the image displayed just under the logo in the error screen.",
|
||||||
|
example: "https://example.com/error.png",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'This is an error that can be returned by the API, its type must be equal to "error".\n If such an error is caught, an error screen will be displayed.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const isErrorApiRetryData = z.object({
|
export const isErrorApiRetryData = extendApi(
|
||||||
|
z.object({
|
||||||
type: z.literal("retry"),
|
type: z.literal("retry"),
|
||||||
code: z.string(),
|
code: extendApi(z.string(), {
|
||||||
title: z.string(),
|
description:
|
||||||
subtitle: z.string(),
|
"The system code of an error, it must be in SCREAMING_SNAKE_CASE. \n It will not be displayed to the user.",
|
||||||
details: z.string(),
|
example: "WORLD_FULL",
|
||||||
image: z.string(),
|
}),
|
||||||
buttonTitle: z.optional(z.nullable(z.string())),
|
title: extendApi(z.string(), { description: "Big title displayed on the error screen.", example: "ERROR" }),
|
||||||
timeToRetry: z.number(),
|
subtitle: extendApi(z.string(), {
|
||||||
canRetryManual: z.boolean(),
|
description: "Subtitle displayed to let the user know what is the main subject of the error.",
|
||||||
});
|
example: "Too successful, your WorkAdventure world is full!",
|
||||||
|
}),
|
||||||
|
details: extendApi(z.string(), {
|
||||||
|
description: "Some others details on what the user can do if he don't understand the error.",
|
||||||
|
example: "New automatic attempt in 30 seconds",
|
||||||
|
}),
|
||||||
|
image: extendApi(z.string(), {
|
||||||
|
description: "The URL of the image displayed just under the logo in the waiting screen.",
|
||||||
|
example: "https://example.com/wait.png",
|
||||||
|
}),
|
||||||
|
buttonTitle: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
|
description:
|
||||||
|
"If this is not defined the button and the parameter canRetryManual is set to true, the button will be not displayed at all.",
|
||||||
|
example: "Retry",
|
||||||
|
}),
|
||||||
|
timeToRetry: extendApi(z.number(), {
|
||||||
|
description: "This is the time (in millisecond) between the next auto refresh of the page.",
|
||||||
|
example: 30_000,
|
||||||
|
}),
|
||||||
|
canRetryManual: extendApi(z.boolean(), {
|
||||||
|
description: "This boolean show or hide the button to let the user refresh manually the current page.",
|
||||||
|
example: true,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'This is an error that can be returned by the API, its type must be equal to "retry".\n' +
|
||||||
|
"If such an error is caught, a waiting screen will be displayed.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const isErrorApiRedirectData = z.object({
|
export const isErrorApiRedirectData = extendApi(
|
||||||
|
z.object({
|
||||||
type: z.literal("redirect"),
|
type: z.literal("redirect"),
|
||||||
urlToRedirect: z.string(),
|
urlToRedirect: extendApi(z.string(), {
|
||||||
});
|
description: "A URL specified to redirect the user onto it directly",
|
||||||
|
example: "/contact-us",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'This is an error that can be returned by the API, its type must be equal to "redirect".\n' +
|
||||||
|
"If such an error is caught, the user will be automatically redirected to urlToRedirect.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const isErrorApiUnauthorizedData = z.object({
|
export const isErrorApiUnauthorizedData = extendApi(
|
||||||
|
z.object({
|
||||||
type: z.literal("unauthorized"),
|
type: z.literal("unauthorized"),
|
||||||
});
|
code: extendApi(z.string(), {
|
||||||
|
description: "This is the system code of an error, it must be in SCREAMING_SNAKE_CASE.",
|
||||||
|
example: "USER_ACCESS_FORBIDDEN",
|
||||||
|
}),
|
||||||
|
title: extendApi(z.string(), { description: "Big title displayed on the error screen.", example: "ERROR" }),
|
||||||
|
subtitle: extendApi(z.string(), {
|
||||||
|
description: "Subtitle displayed to let the user know what is the main subject of the error.",
|
||||||
|
example: "You can't access this place.",
|
||||||
|
}),
|
||||||
|
details: extendApi(z.string(), {
|
||||||
|
description: "Some others details on what the user can do if he don't understand the error.",
|
||||||
|
example:
|
||||||
|
"If you would like more information, you can contact the administrator or us at example@workadventu.re.",
|
||||||
|
}),
|
||||||
|
image: extendApi(z.string(), {
|
||||||
|
description: "The URL of the image displayed just under the logo in the error screen.",
|
||||||
|
example: "https://example.com/error.png",
|
||||||
|
}),
|
||||||
|
buttonTitle: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
|
description: "If this is not defined the button to logout will be not displayed.",
|
||||||
|
example: "Log out",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'This is an error that can be returned by the API, its type must be equal to "unauthorized".\n' +
|
||||||
|
"If such an error is caught, an error screen will be displayed with a button to let him logout and go to login page.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const isErrorApiData = z.discriminatedUnion("type", [
|
export const isErrorApiData = z.discriminatedUnion("type", [
|
||||||
isErrorApiErrorData,
|
isErrorApiErrorData,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { extendApi } from "@anatine/zod-openapi";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* WARNING! The original file is in /messages/JsonMessages.
|
* WARNING! The original file is in /messages/JsonMessages.
|
||||||
@ -6,20 +7,48 @@ import { z } from "zod";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const isMapDetailsData = z.object({
|
export const isMapDetailsData = z.object({
|
||||||
mapUrl: z.string(),
|
// @ts-ignore
|
||||||
authenticationMandatory: z.optional(z.nullable(z.boolean())),
|
mapUrl: extendApi(z.string(), {
|
||||||
group: z.nullable(z.string()),
|
description: "The full URL to the JSON map file",
|
||||||
|
example: "https://myuser.github.io/myrepo/map.json",
|
||||||
|
}),
|
||||||
|
authenticationMandatory: extendApi(z.optional(z.nullable(z.boolean())), {
|
||||||
|
description: "Whether the authentication is mandatory or not for this map",
|
||||||
|
example: true,
|
||||||
|
}),
|
||||||
|
group: extendApi(z.nullable(z.string()), {
|
||||||
|
description: 'The group this room is part of (maps the notion of "world" in WorkAdventure SAAS)',
|
||||||
|
example: "myorg/myworld",
|
||||||
|
}),
|
||||||
|
|
||||||
contactPage: z.optional(z.nullable(z.string())),
|
contactPage: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
iframeAuthentication: z.optional(z.nullable(z.string())),
|
description: "The URL to the contact page",
|
||||||
|
example: "https://mycompany.com/contact-us",
|
||||||
|
}),
|
||||||
|
iframeAuthentication: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
|
description: "The URL of the authentication Iframe",
|
||||||
|
example: "https://mycompany.com/authc",
|
||||||
|
}),
|
||||||
// The date (in ISO 8601 format) at which the room will expire
|
// The date (in ISO 8601 format) at which the room will expire
|
||||||
expireOn: z.optional(z.string()),
|
expireOn: extendApi(z.optional(z.string()), {
|
||||||
|
description: "The date (in ISO 8601 format) at which the room will expire",
|
||||||
|
example: "2022-11-05T08:15:30-05:00",
|
||||||
|
}),
|
||||||
// Whether the "report" feature is enabled or not on this room
|
// Whether the "report" feature is enabled or not on this room
|
||||||
canReport: z.optional(z.boolean()),
|
canReport: extendApi(z.optional(z.boolean()), {
|
||||||
|
description: 'Whether the "report" feature is enabled or not on this room',
|
||||||
|
example: true,
|
||||||
|
}),
|
||||||
// The URL of the logo image on the loading screen
|
// The URL of the logo image on the loading screen
|
||||||
loadingLogo: z.optional(z.nullable(z.string())),
|
loadingLogo: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
|
description: "The URL of the image to be used on the loading page",
|
||||||
|
example: "https://example.com/logo.png",
|
||||||
|
}),
|
||||||
// The URL of the logo image on "LoginScene"
|
// The URL of the logo image on "LoginScene"
|
||||||
loginSceneLogo: z.optional(z.nullable(z.string())),
|
loginSceneLogo: extendApi(z.optional(z.nullable(z.string())), {
|
||||||
|
description: "The URL of the image to be used on the LoginScene",
|
||||||
|
example: "https://example.com/logo_login.png",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type MapDetailsData = z.infer<typeof isMapDetailsData>;
|
export type MapDetailsData = z.infer<typeof isMapDetailsData>;
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
"pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'"
|
"pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@anatine/zod-openapi": "^1.3.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"grpc": "^1.24.4",
|
"grpc": "^1.24.4",
|
||||||
|
"openapi3-ts": "^2.0.2",
|
||||||
"ts-proto": "^1.96.0",
|
"ts-proto": "^1.96.0",
|
||||||
"zod": "^3.14.3"
|
"zod": "^3.14.3"
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@anatine/zod-openapi@^1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@anatine/zod-openapi/-/zod-openapi-1.3.0.tgz#b5b38c3d821b79674226aa7b327c88c371860d0d"
|
||||||
|
integrity sha512-l54DypUdDsIq1Uwjv4ib9IBkTXMKZQLUj7qvdFL51EExC5LdSSqOlTOyaVVZZGYgWPKM7ZjGklhdoknLz4EC+w==
|
||||||
|
dependencies:
|
||||||
|
ts-deepmerge "^1.1.0"
|
||||||
|
validator "^13.7.0"
|
||||||
|
|
||||||
"@babel/code-frame@^7.0.0":
|
"@babel/code-frame@^7.0.0":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
||||||
@ -3241,6 +3249,13 @@ onetime@^5.1.0, onetime@^5.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
|
openapi3-ts@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.2.tgz#a200dd838bf24c9086c8eedcfeb380b7eb31e82a"
|
||||||
|
integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==
|
||||||
|
dependencies:
|
||||||
|
yaml "^1.10.2"
|
||||||
|
|
||||||
optionator@^0.9.1:
|
optionator@^0.9.1:
|
||||||
version "0.9.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||||
@ -4284,6 +4299,11 @@ tough-cookie@~2.5.0:
|
|||||||
psl "^1.1.28"
|
psl "^1.1.28"
|
||||||
punycode "^2.1.1"
|
punycode "^2.1.1"
|
||||||
|
|
||||||
|
ts-deepmerge@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-1.1.0.tgz#4236ae102199affe2e77690dcf198a420160eef2"
|
||||||
|
integrity sha512-VvwaV/6RyYMwT9d8dClmfHIsG2PCdm6WY430QKOIbPRR50Y/1Q2ilp4i2XEZeHFcNqfaYnAQzpyUC6XA0AqqBg==
|
||||||
|
|
||||||
ts-poet@^4.5.0:
|
ts-poet@^4.5.0:
|
||||||
version "4.6.1"
|
version "4.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3"
|
resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3"
|
||||||
@ -4487,6 +4507,11 @@ validate-npm-package-name@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
builtins "^1.0.3"
|
builtins "^1.0.3"
|
||||||
|
|
||||||
|
validator@^13.7.0:
|
||||||
|
version "13.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
|
||||||
|
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
|
||||||
|
|
||||||
verror@1.10.0:
|
verror@1.10.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
@ -4593,7 +4618,7 @@ yallist@^3.0.0, yallist@^3.0.3:
|
|||||||
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==
|
||||||
|
|
||||||
yaml@^1.10.0:
|
yaml@^1.10.0, yaml@^1.10.2:
|
||||||
version "1.10.2"
|
version "1.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { BaseHttpController } from "./BaseHttpController";
|
import { BaseHttpController } from "./BaseHttpController";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||||
|
import SwaggerGenerator from "../Services/SwaggerGenerator";
|
||||||
|
|
||||||
export class SwaggerController extends BaseHttpController {
|
export class SwaggerController extends BaseHttpController {
|
||||||
routes() {
|
routes() {
|
||||||
this.app.get("/openapi", (req, res) => {
|
this.app.get("/openapi/pusher", (req, res) => {
|
||||||
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
|
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
|
||||||
const swaggerJsdoc = require("swagger-jsdoc");
|
const swaggerJsdoc = require("swagger-jsdoc");
|
||||||
const options = {
|
const options = {
|
||||||
@ -20,6 +22,43 @@ export class SwaggerController extends BaseHttpController {
|
|||||||
res.json(swaggerJsdoc(options));
|
res.json(swaggerJsdoc(options));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.app.get("/openapi/admin", (req, res) => {
|
||||||
|
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
|
||||||
|
const swaggerJsdoc = require("swagger-jsdoc");
|
||||||
|
const options = {
|
||||||
|
swaggerDefinition: {
|
||||||
|
swagger: "2.0",
|
||||||
|
//openapi: "3.0.0",
|
||||||
|
info: {
|
||||||
|
title: "WorkAdventure Pusher",
|
||||||
|
version: "1.0.0",
|
||||||
|
description:
|
||||||
|
"This is a documentation about the endpoints called by the pusher. \n You can find out more about WorkAdventure on [github](https://github.com/thecodingmachine/workadventure).",
|
||||||
|
contact: {
|
||||||
|
email: "hello@workadventu.re",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
host: "pusher." + ADMIN_URL.replace("//", ""),
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
name: "AdminAPI",
|
||||||
|
description: "Access to end points of the admin from the pusher",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
securityDefinitions: {
|
||||||
|
Bearer: {
|
||||||
|
type: "apiKey",
|
||||||
|
name: "Authorization",
|
||||||
|
in: "header",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...SwaggerGenerator.definitions(),
|
||||||
|
},
|
||||||
|
apis: ["./src/Services/*.ts"],
|
||||||
|
};
|
||||||
|
res.json(swaggerJsdoc(options));
|
||||||
|
});
|
||||||
|
|
||||||
// Create a LiveDirectory instance to virtualize directory with our assets
|
// Create a LiveDirectory instance to virtualize directory with our assets
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const LiveDirectory = require("live-directory");
|
const LiveDirectory = require("live-directory");
|
||||||
@ -39,8 +78,16 @@ export class SwaggerController extends BaseHttpController {
|
|||||||
if (err) {
|
if (err) {
|
||||||
return response.status(500).send(err.message);
|
return response.status(500).send(err.message);
|
||||||
}
|
}
|
||||||
const result = data.replace(/https:\/\/petstore\.swagger\.io\/v2\/swagger.json/g, "/openapi");
|
|
||||||
|
|
||||||
|
const urls = [
|
||||||
|
{ url: "/openapi/pusher", name: "Front -> Pusher" },
|
||||||
|
{ url: "/openapi/admin", name: "Pusher <- Admin" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = data.replace(
|
||||||
|
/url: "https:\/\/petstore\.swagger\.io\/v2\/swagger.json"/g,
|
||||||
|
`urls: ${JSON.stringify(urls)}, "urls.primaryName": "Pusher <- Admin"`
|
||||||
|
);
|
||||||
response.send(result);
|
response.send(result);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { extendApi } from "@anatine/zod-openapi";
|
||||||
|
|
||||||
export const isBanBannedAdminMessageInterface = z.object({
|
export const isBanBannedAdminMessageInterface = z.object({
|
||||||
type: z.enum(["ban", "banned"]),
|
type: z.enum(["ban", "banned"]),
|
||||||
@ -8,7 +9,7 @@ export const isBanBannedAdminMessageInterface = z.object({
|
|||||||
|
|
||||||
export const isUserMessageAdminMessageInterface = z.object({
|
export const isUserMessageAdminMessageInterface = z.object({
|
||||||
event: z.enum(["user-message"]),
|
event: z.enum(["user-message"]),
|
||||||
message: isBanBannedAdminMessageInterface,
|
message: extendApi(isBanBannedAdminMessageInterface, { $ref: "#/definitions/BanBannedAdminMessageInterface" }),
|
||||||
world: z.string(),
|
world: z.string(),
|
||||||
jwt: z.string(),
|
jwt: z.string(),
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import qs from "qs";
|
|||||||
import { AdminInterface } from "./AdminInterface";
|
import { AdminInterface } from "./AdminInterface";
|
||||||
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
|
||||||
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
import { InvalidTokenError } from "../Controller/InvalidTokenError";
|
||||||
|
import { extendApi } from "@anatine/zod-openapi";
|
||||||
|
|
||||||
export interface AdminBannedData {
|
export interface AdminBannedData {
|
||||||
is_banned: boolean;
|
is_banned: boolean;
|
||||||
@ -16,15 +17,31 @@ export interface AdminBannedData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isFetchMemberDataByUuidResponse = z.object({
|
export const isFetchMemberDataByUuidResponse = z.object({
|
||||||
email: z.string(),
|
// @ts-ignore
|
||||||
userUuid: z.string(),
|
email: extendApi(z.string(), {
|
||||||
tags: z.array(z.string()),
|
description: "The email of the fetched user, it can be an email, an uuid or undefined.",
|
||||||
visitCardUrl: z.nullable(z.string()),
|
example: "example@workadventu.re",
|
||||||
textures: z.array(isWokaDetail),
|
}),
|
||||||
messages: z.array(z.unknown()),
|
userUuid: extendApi(z.string(), {
|
||||||
|
description: "The uuid of the fetched user, it can be an email, an uuid or undefined.",
|
||||||
|
example: "998ce839-3dea-4698-8b41-ebbdf7688ad9",
|
||||||
|
}),
|
||||||
|
tags: extendApi(z.array(z.string()), {
|
||||||
|
description: "List of tags related to the user fetched.",
|
||||||
|
example: ["editor"],
|
||||||
|
}),
|
||||||
|
visitCardUrl: extendApi(z.nullable(z.string()), {
|
||||||
|
description: "URL of the visitCard of the user fetched.",
|
||||||
|
example: "https://mycompany.com/contact/me",
|
||||||
|
}),
|
||||||
|
textures: extendApi(z.array(isWokaDetail), { $ref: "#/definitions/WokaDetail" }),
|
||||||
|
messages: extendApi(z.array(z.unknown()), { description: "List of user's messages." }),
|
||||||
|
|
||||||
anonymous: z.optional(z.boolean()),
|
anonymous: extendApi(z.optional(z.boolean()), {
|
||||||
userRoomToken: z.optional(z.string()),
|
description: "Whether the user if logged as anonymous or not",
|
||||||
|
example: false,
|
||||||
|
}),
|
||||||
|
userRoomToken: extendApi(z.optional(z.string()), { description: "", example: "" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
|
||||||
@ -69,6 +86,47 @@ class AdminApi implements AdminInterface {
|
|||||||
userId,
|
userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/map:
|
||||||
|
* get:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Returns a map mapping map name to file name of the map
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "playUri"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The full URL of WorkAdventure"
|
||||||
|
* required: true
|
||||||
|
* type: "string"
|
||||||
|
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||||
|
* - name: "userId"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
|
||||||
|
* type: "string"
|
||||||
|
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The details of the member
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/definitions/MapDetailsData"
|
||||||
|
* 401:
|
||||||
|
* description: Error while retrieving the data because you are not authorized
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||||
|
* 403:
|
||||||
|
* description: Error while retrieving the data because you are not authorized
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiUnauthorizedData'
|
||||||
|
* 404:
|
||||||
|
* description: Error while retrieving the data
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiErrorData'
|
||||||
|
*
|
||||||
|
*/
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
params,
|
params,
|
||||||
@ -99,6 +157,58 @@ class AdminApi implements AdminInterface {
|
|||||||
characterLayers: string[],
|
characterLayers: string[],
|
||||||
locale?: string
|
locale?: string
|
||||||
): Promise<FetchMemberDataByUuidResponse> {
|
): Promise<FetchMemberDataByUuidResponse> {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/room/access:
|
||||||
|
* get:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Returns member's informations if he can access this room
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "userIdentifier"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
|
||||||
|
* type: "string"
|
||||||
|
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||||
|
* - name: "playUri"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The full URL of WorkAdventure"
|
||||||
|
* required: true
|
||||||
|
* type: "string"
|
||||||
|
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||||
|
* - name: "ipAddress"
|
||||||
|
* in: "query"
|
||||||
|
* required: true
|
||||||
|
* type: "string"
|
||||||
|
* example: "127.0.0.1"
|
||||||
|
* - name: "characterLayers"
|
||||||
|
* in: "query"
|
||||||
|
* type: "array"
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* example: ["male1"]
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The details of the member
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/definitions/FetchMemberDataByUuidResponse"
|
||||||
|
* 401:
|
||||||
|
* description: Error while retrieving the data because you are not authorized
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||||
|
* 403:
|
||||||
|
* description: Error while retrieving the data because you are not authorized
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiUnauthorizedData'
|
||||||
|
* 404:
|
||||||
|
* description: Error while retrieving the data
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiErrorData'
|
||||||
|
*
|
||||||
|
*/
|
||||||
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
|
||||||
params: {
|
params: {
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
@ -130,6 +240,42 @@ class AdminApi implements AdminInterface {
|
|||||||
playUri: string | null,
|
playUri: string | null,
|
||||||
locale?: string
|
locale?: string
|
||||||
): Promise<AdminApiData> {
|
): Promise<AdminApiData> {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/login-url/{organizationMemberToken}:
|
||||||
|
* get:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Returns a member from the token
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "organizationMemberToken"
|
||||||
|
* in: "path"
|
||||||
|
* description: "The token of member in the organization"
|
||||||
|
* type: "string"
|
||||||
|
* - name: "playUri"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The full URL of WorkAdventure"
|
||||||
|
* required: true
|
||||||
|
* type: "string"
|
||||||
|
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The details of the member
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/definitions/AdminApiData"
|
||||||
|
* 401:
|
||||||
|
* description: Error while retrieving the data because you are not authorized
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiRedirectData'
|
||||||
|
* 404:
|
||||||
|
* description: Error while retrieving the data
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiErrorData'
|
||||||
|
*
|
||||||
|
*/
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
|
||||||
params: { playUri },
|
params: { playUri },
|
||||||
@ -154,6 +300,41 @@ class AdminApi implements AdminInterface {
|
|||||||
reportWorldSlug: string,
|
reportWorldSlug: string,
|
||||||
locale?: string
|
locale?: string
|
||||||
) {
|
) {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/report:
|
||||||
|
* post:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Report one user with a comment
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "reportedUserUuid"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The identifier of the reported user \n It can be an uuid or an email"
|
||||||
|
* type: "string"
|
||||||
|
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
|
||||||
|
* - name: "reportedUserComment"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The comment of the report"
|
||||||
|
* required: true
|
||||||
|
* type: "string"
|
||||||
|
* - name: "reporterUserUuid"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The identifier of the reporter user \n It can be an uuid or an email"
|
||||||
|
* type: "string"
|
||||||
|
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
|
||||||
|
* - name: "reportWorldSlug"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The slug of the world where the report is made"
|
||||||
|
* type: "string"
|
||||||
|
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The report has been successfully saved
|
||||||
|
*/
|
||||||
return Axios.post(
|
return Axios.post(
|
||||||
`${ADMIN_API_URL}/api/report`,
|
`${ADMIN_API_URL}/api/report`,
|
||||||
{
|
{
|
||||||
@ -174,6 +355,53 @@ class AdminApi implements AdminInterface {
|
|||||||
roomUrl: string,
|
roomUrl: string,
|
||||||
locale?: string
|
locale?: string
|
||||||
): Promise<AdminBannedData> {
|
): Promise<AdminBannedData> {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/ban:
|
||||||
|
* get:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Check if user is banned or not
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "ipAddress"
|
||||||
|
* in: "query"
|
||||||
|
* type: "string"
|
||||||
|
* required: true
|
||||||
|
* example: "127.0.0.1"
|
||||||
|
* - name: "token"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The uuid of the user \n It can be an uuid or an email"
|
||||||
|
* type: "string"
|
||||||
|
* required: true
|
||||||
|
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
|
||||||
|
* - name: "roomUrl"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The slug of the world where to check if the user is banned"
|
||||||
|
* type: "string"
|
||||||
|
* required: true
|
||||||
|
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The user is banned or not
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* required:
|
||||||
|
* - is_banned
|
||||||
|
* properties:
|
||||||
|
* is_banned:
|
||||||
|
* type: boolean
|
||||||
|
* description: Whether the user is banned or not
|
||||||
|
* example: true
|
||||||
|
* 404:
|
||||||
|
* description: Error while retrieving the data
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiErrorData'
|
||||||
|
*/
|
||||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||||
return Axios.get(
|
return Axios.get(
|
||||||
ADMIN_API_URL +
|
ADMIN_API_URL +
|
||||||
@ -191,6 +419,37 @@ class AdminApi implements AdminInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
|
||||||
|
/**
|
||||||
|
* @openapi
|
||||||
|
* /api/room/sameWorld:
|
||||||
|
* get:
|
||||||
|
* tags: ["AdminAPI"]
|
||||||
|
* description: Get all URLs of the rooms from the world specified
|
||||||
|
* security:
|
||||||
|
* - Bearer: []
|
||||||
|
* produces:
|
||||||
|
* - "application/json"
|
||||||
|
* parameters:
|
||||||
|
* - name: "roomUrl"
|
||||||
|
* in: "query"
|
||||||
|
* description: "The slug of the room"
|
||||||
|
* type: "string"
|
||||||
|
* required: true
|
||||||
|
* example: "/@/teamSlug/worldSlug/roomSlug"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: The list of URL of the rooms from the same world
|
||||||
|
* schema:
|
||||||
|
* type: array
|
||||||
|
* items:
|
||||||
|
* type: string
|
||||||
|
* description: URL of a room
|
||||||
|
* example: "http://example.com/@/teamSlug/worldSlug/room2Slug"
|
||||||
|
* 404:
|
||||||
|
* description: Error while retrieving the data
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/definitions/ErrorApiErrorData'
|
||||||
|
*/
|
||||||
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
|
||||||
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
|
||||||
}).then((data) => {
|
}).then((data) => {
|
||||||
@ -204,10 +463,6 @@ class AdminApi implements AdminInterface {
|
|||||||
}
|
}
|
||||||
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logoutOauth(token: string): Promise<void> {
|
|
||||||
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adminApi = new AdminApi();
|
export const adminApi = new AdminApi();
|
||||||
|
@ -74,9 +74,4 @@ export interface AdminInterface {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
getProfileUrl(accessToken: string): string;
|
getProfileUrl(accessToken: string): string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param token
|
|
||||||
*/
|
|
||||||
logoutOauth(token: string): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
@ -87,10 +87,6 @@ class LocalAdmin implements AdminInterface {
|
|||||||
new Error("No admin backoffice set!");
|
new Error("No admin backoffice set!");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
async logoutOauth(token: string): Promise<void> {
|
|
||||||
return Promise.reject(new Error("No admin backoffice set!"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localAdmin = new LocalAdmin();
|
export const localAdmin = new LocalAdmin();
|
||||||
|
@ -658,7 +658,7 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
|
||||||
const errorMessage = new ErrorScreenMessage();
|
const errorMessage = new ErrorScreenMessage();
|
||||||
errorMessage.setType(errorApi.type);
|
errorMessage.setType(errorApi.type);
|
||||||
if (errorApi.type == "retry" || errorApi.type == "error") {
|
if (errorApi.type == "retry" || errorApi.type == "error" || errorApi.type == "unauthorized") {
|
||||||
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
errorMessage.setCode(new StringValue().setValue(errorApi.code));
|
||||||
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
|
||||||
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
|
||||||
|
35
pusher/src/Services/SwaggerGenerator.ts
Normal file
35
pusher/src/Services/SwaggerGenerator.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { generateSchema } from "@anatine/zod-openapi";
|
||||||
|
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||||
|
import {
|
||||||
|
isErrorApiErrorData,
|
||||||
|
isErrorApiRedirectData,
|
||||||
|
isErrorApiRetryData,
|
||||||
|
isErrorApiUnauthorizedData,
|
||||||
|
} from "../Messages/JsonMessages/ErrorApiData";
|
||||||
|
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||||
|
import { isFetchMemberDataByUuidResponse } from "./AdminApi";
|
||||||
|
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
|
||||||
|
|
||||||
|
class SwaggerGenerator {
|
||||||
|
definitions() {
|
||||||
|
return {
|
||||||
|
definitions: {
|
||||||
|
AdminApiData: generateSchema(isAdminApiData),
|
||||||
|
//BanBannedAdminMessageInterface: generateSchema(isBanBannedAdminMessageInterface),
|
||||||
|
ErrorApiErrorData: generateSchema(isErrorApiErrorData),
|
||||||
|
ErrorApiRedirectData: generateSchema(isErrorApiRedirectData),
|
||||||
|
ErrorApiRetryData: generateSchema(isErrorApiRetryData),
|
||||||
|
ErrorApiUnauthorizedData: generateSchema(isErrorApiUnauthorizedData),
|
||||||
|
FetchMemberDataByUuidResponse: generateSchema(isFetchMemberDataByUuidResponse),
|
||||||
|
//ListenRoomsMessageInterface: generateSchema(isListenRoomsMessageInterface),
|
||||||
|
MapDetailsData: generateSchema(isMapDetailsData),
|
||||||
|
//RegisterData: generateSchema(isRegisterData),
|
||||||
|
//RoomRedirect: generateSchema(isRoomRedirect),
|
||||||
|
//UserMessageAdminMessageInterface: generateSchema(isUserMessageAdminMessageInterface),
|
||||||
|
WokaDetail: generateSchema(isWokaDetail),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SwaggerGenerator();
|
38
tests/tests/iframe_script.spec.ts
Normal file
38
tests/tests/iframe_script.spec.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
import { login } from './utils/roles';
|
||||||
|
|
||||||
|
test.describe('Iframe API', () => {
|
||||||
|
test('can be called from an iframe loading a script', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto(
|
||||||
|
'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Metadata/cowebsiteAllowApi.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
await login(page);
|
||||||
|
|
||||||
|
await expect(page.locator('p.other-text')).toHaveText('The iframe opened by a script works !', {useInnerText: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can add a custom menu by scripting API', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.goto(
|
||||||
|
'http://play.workadventure.localhost/_/global/maps.workadventure.localhost/tests/Metadata/customMenu.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
await login(page);
|
||||||
|
|
||||||
|
await page.click('.menuIcon img:first-child');
|
||||||
|
|
||||||
|
await page.click('button:has-text("custom iframe menu")');
|
||||||
|
|
||||||
|
const iframeParagraph = page
|
||||||
|
.frameLocator('.menu-submenu-container iframe')
|
||||||
|
.locator('p');
|
||||||
|
await expect(iframeParagraph).toHaveText('This is an iframe in a custom menu.');
|
||||||
|
|
||||||
|
await page.click('button:has-text("custom callback menu")');
|
||||||
|
await expect(page.locator('p.other-text')).toHaveText('Custom menu clicked', {useInnerText: true});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user