Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop

This commit is contained in:
_Bastler 2022-05-09 07:58:46 +02:00
commit 1f11ab57f3
43 changed files with 1754 additions and 90 deletions

View File

@ -570,6 +570,7 @@ export class GameRoom {
mapUrl,
authenticationMandatory: null,
group: null,
showPoweredBy: true,
};
}

View File

@ -14,7 +14,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
Example:
```javascript
```ts
WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerControls();
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
@ -25,5 +25,28 @@ WA.room.onEnterLayer('myZone').subscribe(() => {
popup.close();
}
}]);
})
});
```
### Disabling / restoring proximity meeting
```
WA.controls.disablePlayerProximityMeeting(): void
WA.controls.restorePlayerProximityMeeting(): void
```
These 2 methods can be used to completely disable player proximity meeting and to enable them again.
When proximity meeting are disabled, the user cannot speak with anyone.
Example:
```ts
WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerProximityMeeting();
});
WA.room.onLeaveLayer('myZone').subscribe(() => {
WA.controls.restorePlayerProximityMeeting();
});
```

View File

@ -34,7 +34,7 @@ Please note that `openPopup` returns an object of the `Popup` class. Also, the c
The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called.
```javascript
```ts
class Popup {
/**
* Closes the popup
@ -45,7 +45,7 @@ class Popup {
Example:
```javascript
```ts
let helloWorldPopup;
// Open the popup when we enter a given zone
@ -90,8 +90,8 @@ Custom menu exist only until the map is unloaded, or you leave the iframe zone o
</div>
</div>
Example:
```javascript
Example:
```ts
const menu = WA.ui.registerMenuCommand('menu test',
{
callback: () => {
@ -107,7 +107,7 @@ Please note that `registerMenuCommand` returns an object of the `Menu` class.
The `Menu` class contains a single method: `remove(): void`. This will obviously remove the menu when called.
```javascript
```ts
class Menu {
/**
* Remove the menu
@ -120,7 +120,7 @@ class Menu {
### Awaiting User Confirmation (with space bar)
```
```ts
WA.ui.displayActionMessage({
message: string,
callback: () => void,
@ -136,7 +136,7 @@ Displays a message at the bottom of the screen (that will disappear when space b
Example:
```javascript
```ts
const triggerMessage = WA.ui.displayActionMessage({
message: "press 'space' to confirm",
callback: () => {
@ -154,7 +154,7 @@ Please note that `displayActionMessage` returns an object of the `ActionMessage`
The `ActionMessage` class contains a single method: `remove(): Promise<void>`. This will obviously remove the message when called.
```javascript
```ts
class ActionMessage {
/**
* Hides the message
@ -173,7 +173,7 @@ When clicking on other player's WOKA, the contextual menu (we call it ActionsMen
To do that, we need to listen for the `onRemotePlayerClicked` stream and make use of the `remotePlayer` object that is passed by as a payload.
```javascript
```ts
WA.ui.onRemotePlayerClicked.subscribe((remotePlayer) => {
remotePlayer.addAction('Ask to tell a joke', () => {
console.log('I am NOT telling you a joke!');
@ -182,7 +182,7 @@ WA.ui.onRemotePlayerClicked.subscribe((remotePlayer) => {
```
`remotePlayer.addAction(actionName, callback)` returns an Action object, which can remove itself from ActionsMenu:
```javascript
```ts
const action = remotePlayer.addAction('This will disappear!', () => {
console.log('You managed to click me!');
});
@ -193,3 +193,95 @@ setTimeout(
1000,
);
```
# Open fixed iframes
You can use the scripting API to display an iframe (so any HTML element) above the game. The iframe is positionned relative to the browser window (so unlike [embedded websites](website-in-map.md), the position of the iframe does not move when someone walks on the map).
<div class="col">
<img src="images/ui-website.png" class="figure-img img-fluid rounded" alt="" />
</div>
This functonnality creates an iframe positionned on the viewport.
## Display a UI website
```ts
WA.ui.website.open(website: CreateUIWebsiteEvent): Promise<UIWebsite>
interface CreateUIWebsiteEvent {
url: string, // Website URL
visible?: boolean, // The website is visible or not
allowApi?: boolean, // Allow scripting API on the website
allowPolicy?: string, // The list of feature policies allowed
position: {
vertical: "top"|"middle"|"bottom",,
horizontal: "left","middle","right",
},
size: { // Size on the UI (available units: px|em|%|cm|in|pc|pt|mm|ex|vw|vh|rem and others values auto|inherit)
height: string,
width: string,
},
margin?: { // Website margin (available units: px|em|%|cm|in|pc|pt|mm|ex|vw|vh|rem and others values auto|inherit)
top?: string,
bottom?: string,
left?: string,
right?: string,
},
}
interface UIWebsite {
readonly id: string, // Unique ID
url: string, // Website URL
visible: boolean, // The website is visible or not
readonly allowApi: boolean, // Allow scripting API on the website
readonly allowPolicy: string, // The list of feature policies allowed
position: {
vertical: string, // Vertical position (top, middle, bottom)
horizontal: string, // Horizontal position (left, middle, right)
},
size: { // Size on the UI (available units: px|em|%|cm|in|pc|pt|mm|ex|vw|vh|rem and others values auto|inherit)
height: string,
width: string,
},
margin?: { // Website margin (available units: px|em|%|cm|in|pc|pt|mm|ex|vw|vh|rem and others values auto|inherit)
top?: string,
bottom?: string,
left?: string,
right?: string,
},
close(): Promise<void>, // Close the current website instance
}
```
You can open a website with the `WA.ui.website.open()` method. It returns an `Promise<UIWebsite>` instance.
```ts
const myWebsite = await WA.ui.website.open({
url: "https://wikipedia.org",
position: {
vertical: "middle",
horizontal: "middle",
},
size: {
height: "50vh",
width: "50vw",
},
});
myWebsite.position.vertical = "top";
```
## Close a UI website
You can close a website with the close function on the `UIWebsite` object
```ts
myWebsite.close();
```
## Get all UI websites
You can get all websites with the `WA.ui.website.getAll()` method. It returns an `Promise<UIWebsite[]>` instance.
```ts
WA.ui.website.getAll();
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

1
front/.gitignore vendored
View File

@ -5,3 +5,4 @@ dist/
*.sh
!templater.sh
/public/iframe_api.js
/public/es.js

View File

@ -21,7 +21,7 @@
"lint-staged": "^12.3.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.1",
"prettier-plugin-svelte": "^2.5.0",
"prettier-plugin-svelte": "^2.7.0",
"sass": "^1.49.7",
"svelte": "^3.46.3",
"svelte-check": "^2.1.0",
@ -96,4 +96,4 @@
"yarn run pretty"
]
}
}
}

View File

@ -40,6 +40,7 @@ import { isAddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRem
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
import { isRemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
import { isGetPropertyEvent } from "./GetPropertyEvent";
import { isCreateUIWebsiteEvent, isModifyUIWebsiteEvent, isUIWebsite } from "./ui/UIWebsite";
export interface TypedMessageEvent<T> extends MessageEvent {
data: T;
@ -97,6 +98,14 @@ export const isIframeEventWrapper = z.union([
type: z.literal("restorePlayerControls"),
data: z.undefined(),
}),
z.object({
type: z.literal("disablePlayerProximityMeeting"),
data: z.undefined(),
}),
z.object({
type: z.literal("restorePlayerProximityMeeting"),
data: z.undefined(),
}),
z.object({
type: z.literal("displayBubble"),
data: z.undefined(),
@ -153,6 +162,10 @@ export const isIframeEventWrapper = z.union([
type: z.literal("modifyEmbeddedWebsite"),
data: isEmbeddedWebsiteEvent,
}),
z.object({
type: z.literal("modifyUIWebsite"),
data: isModifyUIWebsiteEvent,
}),
]);
export type IframeEvent = z.infer<typeof isIframeEventWrapper>;
@ -266,6 +279,18 @@ export const iframeQueryMapTypeGuards = {
query: isMovePlayerToEventConfig,
answer: isMovePlayerToEventAnswer,
},
openUIWebsite: {
query: isCreateUIWebsiteEvent,
answer: isUIWebsite,
},
closeUIWebsite: {
query: z.string(),
answer: z.undefined(),
},
getUIWebsites: {
query: z.undefined(),
answer: z.array(isUIWebsite),
},
};
type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards;

View File

@ -0,0 +1,75 @@
import { z } from "zod";
const regexUnit = /-*\d+(px|em|%|cm|in|pc|pt|mm|ex|vw|vh|rem)|auto|inherit/;
// Parse the string to check if is a valid CSS unit (px,%,vw,vh...)
export const isUIWebsiteCSSValue = z.string().regex(regexUnit);
export type UIWebsiteCSSValue = z.infer<typeof isUIWebsiteCSSValue>;
export const isUIWebsiteMargin = z.object({
top: isUIWebsiteCSSValue.optional(),
bottom: isUIWebsiteCSSValue.optional(),
left: isUIWebsiteCSSValue.optional(),
right: isUIWebsiteCSSValue.optional(),
});
export type UIWebsiteMargin = z.infer<typeof isUIWebsiteMargin>;
export const isViewportPositionVertical = z.enum(["top", "middle", "bottom"]);
export type ViewportPositionVertical = z.infer<typeof isViewportPositionVertical>;
export const isViewportPositionHorizontal = z.enum(["left", "middle", "right"]);
export type ViewportPositionHorizontal = z.infer<typeof isViewportPositionHorizontal>;
export const isUIWebsitePosition = z.object({
vertical: isViewportPositionVertical,
horizontal: isViewportPositionHorizontal,
});
export type UIWebsitePosition = z.infer<typeof isUIWebsitePosition>;
export const isUIWebsiteSize = z.object({
height: isUIWebsiteCSSValue,
width: isUIWebsiteCSSValue,
});
export type UIWebsiteSize = z.infer<typeof isUIWebsiteSize>;
export const isCreateUIWebsiteEvent = z.object({
url: z.string(),
visible: z.optional(z.boolean()),
allowApi: z.optional(z.boolean()),
allowPolicy: z.optional(z.string()),
position: isUIWebsitePosition,
size: isUIWebsiteSize,
margin: isUIWebsiteMargin.optional(),
});
export type CreateUIWebsiteEvent = z.infer<typeof isCreateUIWebsiteEvent>;
export const isModifyUIWebsiteEvent = z.object({
id: z.string(),
url: z.string().optional(),
visible: z.boolean().optional(),
position: isUIWebsitePosition.optional(),
size: isUIWebsiteSize.optional(),
margin: isUIWebsiteMargin.optional(),
});
export type ModifyUIWebsiteEvent = z.infer<typeof isModifyUIWebsiteEvent>;
export const isUIWebsite = z.object({
id: z.string(),
url: z.string(),
visible: z.boolean(),
allowApi: z.boolean(),
allowPolicy: z.string(),
position: isUIWebsitePosition,
size: isUIWebsiteSize,
margin: isUIWebsiteMargin.optional(),
});
export type UIWebsite = z.infer<typeof isUIWebsite>;

View File

@ -35,6 +35,7 @@ import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent
import { AddActionsMenuKeyToRemotePlayerEvent } from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent";
import { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
import { ModifyUIWebsiteEvent } from "./Events/ui/UIWebsite";
type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"],
@ -58,6 +59,15 @@ class IframeListener {
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
private readonly _disablePlayerProximityMeetingStream: Subject<void> = new Subject();
public readonly disablePlayerProximityMeetingStream = this._disablePlayerProximityMeetingStream.asObservable();
private readonly _enablePlayerProximityMeetingStream: Subject<void> = new Subject();
public readonly enablePlayerProximityMeetingStream = this._enablePlayerProximityMeetingStream.asObservable();
private readonly _cameraSetStream: Subject<CameraSetEvent> = new Subject();
public readonly cameraSetStream = this._cameraSetStream.asObservable();
@ -73,9 +83,6 @@ class IframeListener {
public readonly removeActionsMenuKeyFromRemotePlayerEvent =
this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable();
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject();
public readonly closePopupStream = this._closePopupStream.asObservable();
@ -115,6 +122,9 @@ class IframeListener {
private readonly _modifyEmbeddedWebsiteStream: Subject<ModifyEmbeddedWebsiteEvent> = new Subject();
public readonly modifyEmbeddedWebsiteStream = this._modifyEmbeddedWebsiteStream.asObservable();
private readonly _modifyUIWebsiteStream: Subject<ModifyUIWebsiteEvent> = new Subject();
public readonly modifyUIWebsiteStream = this._modifyUIWebsiteStream.asObservable();
private readonly iframes = new Set<HTMLIFrameElement>();
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
private readonly scripts = new Map<string, HTMLIFrameElement>();
@ -263,6 +273,10 @@ class IframeListener {
this._disablePlayerControlStream.next();
} else if (iframeEvent.type === "restorePlayerControls") {
this._enablePlayerControlStream.next();
} else if (iframeEvent.type === "disablePlayerProximityMeeting") {
this._disablePlayerProximityMeetingStream.next();
} else if (iframeEvent.type === "restorePlayerProximityMeeting") {
this._enablePlayerProximityMeetingStream.next();
} else if (iframeEvent.type === "displayBubble") {
this._displayBubbleStream.next();
} else if (iframeEvent.type === "removeBubble") {
@ -279,6 +293,8 @@ class IframeListener {
this._setTilesStream.next(iframeEvent.data);
} else if (iframeEvent.type == "modifyEmbeddedWebsite") {
this._modifyEmbeddedWebsiteStream.next(iframeEvent.data);
} else if (iframeEvent.type == "modifyUIWebsite") {
this._modifyUIWebsiteStream.next(iframeEvent.data);
} else if (iframeEvent.type == "registerMenu") {
const dataName = iframeEvent.data.name;
this.iframeCloseCallbacks.get(iframe)?.push(() => {

View File

@ -1,6 +1,6 @@
import { requestedCameraState, requestedMicrophoneState, silentStore } from "../../Stores/MediaStore";
import { get } from "svelte/store";
import { WorkAdventureDesktopApi } from "@wa-preload-app";
import { WorkAdventureDesktopApi } from "../../Interfaces/DesktopAppInterfaces";
declare global {
interface Window {

View File

@ -0,0 +1,338 @@
import {
CreateUIWebsiteEvent,
UIWebsiteCSSValue,
UIWebsiteMargin,
UIWebsitePosition,
UIWebsiteSize,
ViewportPositionHorizontal,
ViewportPositionVertical,
UIWebsite as UIWebsiteCore,
} from "../../Events/ui/UIWebsite";
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "../IframeApiContribution";
class UIWebsitePositionInternal {
private readonly website: UIWebsite;
private _vertical: ViewportPositionVertical;
private _horizontal: ViewportPositionHorizontal;
constructor(uiWebsite: UIWebsite, position: UIWebsitePosition) {
this.website = uiWebsite;
this._vertical = position.vertical;
this._horizontal = position.horizontal;
}
public get vertical() {
return this._vertical;
}
public set vertical(vertical: ViewportPositionVertical) {
this._vertical = vertical;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
position: {
vertical: this._vertical,
horizontal: this._horizontal,
},
},
});
}
public get horizontal() {
return this._horizontal;
}
public set horizontal(horizontal: ViewportPositionHorizontal) {
this._horizontal = horizontal;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
position: {
vertical: this._vertical,
horizontal: this._horizontal,
},
},
});
}
}
class UIWebsiteSizeInternal {
private readonly website: UIWebsite;
private _height: UIWebsiteCSSValue;
private _width: UIWebsiteCSSValue;
constructor(uiWebsite: UIWebsite, size: UIWebsiteSize) {
this.website = uiWebsite;
this._height = size.height;
this._width = size.width;
}
public get height() {
return this._height;
}
public set height(height: UIWebsiteCSSValue) {
this._height = height;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
size: {
height: this._height,
width: this._width,
},
},
});
}
public get width() {
return this._height;
}
public set width(width: UIWebsiteCSSValue) {
this._width = width;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
size: {
height: this._height,
width: this._width,
},
},
});
}
}
class UIWebsiteMarginInternal {
private readonly website: UIWebsite;
private _top?: UIWebsiteCSSValue;
private _bottom?: UIWebsiteCSSValue;
private _left?: UIWebsiteCSSValue;
private _right?: UIWebsiteCSSValue;
constructor(uiWebsite: UIWebsite, margin: UIWebsiteMargin) {
this.website = uiWebsite;
this._top = margin.top;
this._bottom = margin.bottom;
this._left = margin.left;
this._right = margin.right;
}
public get top() {
return this._top;
}
public set top(top: UIWebsiteCSSValue | undefined) {
this._top = top;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
margin: {
top: this._top,
},
},
});
}
public get bottom() {
return this._bottom;
}
public set bottom(bottom: UIWebsiteCSSValue | undefined) {
this._bottom = bottom;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
margin: {
bottom: this._bottom,
},
},
});
}
public get left() {
return this._left;
}
public set left(left: UIWebsiteCSSValue | undefined) {
this._left = left;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
margin: {
left: this._left,
},
},
});
}
public get right() {
return this._right;
}
public set right(right: UIWebsiteCSSValue | undefined) {
this._right = right;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.website.id,
margin: {
right: this._right,
},
},
});
}
}
export class UIWebsite {
public readonly id: string;
private _url: string;
private _visible: boolean;
private readonly _allowPolicy: string;
private readonly _allowApi: boolean;
private _position: UIWebsitePositionInternal;
private _size: UIWebsiteSizeInternal;
private _margin: UIWebsiteMarginInternal;
constructor(config: UIWebsiteCore) {
this.id = config.id;
this._url = config.url;
this._visible = config.visible ?? true;
this._allowPolicy = config.allowPolicy ?? "";
this._allowApi = config.allowApi ?? false;
this._position = new UIWebsitePositionInternal(this, config.position);
this._size = new UIWebsiteSizeInternal(this, config.size);
this._margin = new UIWebsiteMarginInternal(this, config.margin ?? {});
}
public get url() {
return this._url;
}
public set url(url: string) {
this._url = url;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.id,
url: this._url,
},
});
}
public get visible() {
return this._visible;
}
public set visible(visible: boolean) {
this._visible = visible;
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.id,
visible: this._visible,
},
});
}
public get allowPolicy() {
return this._allowPolicy;
}
public get allowApi() {
return this._allowApi;
}
public get position() {
return this._position;
}
public set position(position: UIWebsitePosition) {
this._position = new UIWebsitePositionInternal(this, position);
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.id,
position: {
vertical: this._position.vertical,
horizontal: this._position.horizontal,
},
},
});
}
public get size() {
return this._size;
}
public set size(size: UIWebsiteSize) {
this._size = new UIWebsiteSizeInternal(this, size);
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.id,
size: {
height: this._size.height,
width: this._size.width,
},
},
});
}
public get margin() {
return this._margin;
}
public set margin(margin: UIWebsiteMargin) {
this._margin = new UIWebsiteMarginInternal(this, margin);
sendToWorkadventure({
type: "modifyUIWebsite",
data: {
id: this.id,
margin: {
top: this._margin.top,
bottom: this._margin.bottom,
left: this._margin.left,
right: this._margin.right,
},
},
});
}
close() {
return queryWorkadventure({
type: "closeUIWebsite",
data: this.id,
});
}
}
export class UIWebsiteCommands extends IframeApiContribution<UIWebsiteCommands> {
callbacks = [];
async open(createUIWebsite: CreateUIWebsiteEvent): Promise<UIWebsite> {
const result = await queryWorkadventure({
type: "openUIWebsite",
data: createUIWebsite,
});
return new UIWebsite(result);
}
async getAll(): Promise<UIWebsite[]> {
const result = await queryWorkadventure({
type: "getUIWebsites",
data: undefined,
});
return result.map((current) => new UIWebsite(current));
}
}
export default new UIWebsiteCommands();

View File

@ -10,6 +10,14 @@ export class WorkadventureControlsCommands extends IframeApiContribution<Workadv
restorePlayerControls(): void {
sendToWorkadventure({ type: "restorePlayerControls", data: undefined });
}
disablePlayerProximityMeeting(): void {
sendToWorkadventure({ type: "disablePlayerProximityMeeting", data: undefined });
}
restorePlayerProximityMeeting(): void {
sendToWorkadventure({ type: "restorePlayerProximityMeeting", data: undefined });
}
}
export default new WorkadventureControlsCommands();

View File

@ -14,6 +14,8 @@ import {
isActionsMenuActionClickedEvent,
} from "../Events/ActionsMenuActionClickedEvent";
import { Observable, Subject } from "rxjs";
import type { UIWebsiteCommands } from "./Ui/UIWebsite";
import website from "./Ui/UIWebsite";
let popupId = 0;
const popups: Map<number, Popup> = new Map<number, Popup>();
@ -290,6 +292,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
actionMessages.set(actionMessage.uuid, actionMessage);
return actionMessage;
}
get website(): UIWebsiteCommands {
return website;
}
}
export default new WorkAdventureUiCommands();

View File

@ -63,7 +63,7 @@
<section class="action">
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">{$LL.login.continue()}</button>
</section>
{#if logo !== logoImg}
{#if logo !== logoImg && gameManager.currentStartedRoom.showPoweredBy !== false}
<section class="text-center powered-by">
<img src={poweredByWorkAdventureImg} alt="Powered by WorkAdventure" />
</section>

View File

@ -39,6 +39,8 @@
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
import Lazy from "./Lazy.svelte";
import { showDesktopCapturerSourcePicker } from "../Stores/ScreenSharingStore";
import UiWebsiteContainer from "./UI/Website/UIWebsiteContainer.svelte";
import { uiWebsitesStore } from "../Stores/UIWebsiteStore";
let mainLayout: HTMLDivElement;
@ -128,6 +130,10 @@
{#if hasEmbedScreen}
<EmbedScreensContainer />
{/if}
{#if $uiWebsitesStore}
<UiWebsiteContainer />
{/if}
</section>
<section id="main-layout-baseline">

View File

@ -11,14 +11,17 @@
import reload from "../images/reload.png";
let errorScreen = get(errorScreenStore);
import error from "./images/error.png";
let errorLogo = errorScreen?.image ?? error;
function click() {
if (errorScreen.type === "unauthorized") void connectionManager.logout();
if (errorScreen?.type === "unauthorized") void connectionManager.logout();
else window.location.reload();
}
let details = errorScreen.details;
let timeVar = errorScreen.timeToRetry ?? 0;
let details = errorScreen?.details ?? "";
let timeVar = errorScreen?.timeToRetry ?? 0;
if (errorScreen.type === "retry") {
if (errorScreen?.type === "retry") {
let interval = setInterval(() => {
if (timeVar <= 1000) click();
timeVar -= 1000;
@ -29,24 +32,29 @@
$: detailsStylized = (details ?? "").replace("{time}", `${timeVar / 1000}`);
</script>
<main class="errorScreen" transition:fly={{ y: -200, duration: 500 }}>
<div style="width: 90%;">
<img src={logo} alt="WorkAdventure" class="logo" />
<div><img src={$errorScreenStore.image} alt="" class="icon" /></div>
{#if $errorScreenStore.type !== "retry"}<h2>{$errorScreenStore.title}</h2>{/if}
<p>{$errorScreenStore.subtitle}</p>
{#if $errorScreenStore.type !== "retry"}<p class="code">Code : {$errorScreenStore.code}</p>{/if}
<p class="details">
{detailsStylized}{#if $errorScreenStore.type === "retry"}<div class="loading" />{/if}
</p>
{#if ($errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual) || $errorScreenStore.type === "unauthorized"}
<button type="button" class="nes-btn is-primary button" on:click={click}>
{#if $errorScreenStore.type === "retry"}<img src={reload} alt="" class="reload" />{/if}
{$errorScreenStore.buttonTitle}
</button>
{/if}
</div>
</main>
{#if $errorScreenStore}
<main class="errorScreen" transition:fly={{ y: -200, duration: 500 }}>
<div style="width: 90%;">
<img src={logo} alt="WorkAdventure" class="logo" />
<div><img src={errorLogo} alt="Error logo" class="icon" /></div>
{#if $errorScreenStore.type !== "retry"}<h2>{$errorScreenStore.title}</h2>{/if}
{#if $errorScreenStore.subtitle}<p>{$errorScreenStore.subtitle}</p>{/if}
{#if $errorScreenStore.type !== "retry"}<p class="code">Code : {$errorScreenStore.code}</p>{/if}
<p class="details">
{detailsStylized}
{#if $errorScreenStore.type === "retry" || $errorScreenStore.type === "reconnecting"}
<div class="loading" />
{/if}
</p>
{#if ($errorScreenStore.type === "retry" && $errorScreenStore.canRetryManual) || $errorScreenStore.type === "unauthorized"}
<button type="button" class="nes-btn is-primary button" on:click={click}>
{#if $errorScreenStore.type === "retry"}<img src={reload} alt="" class="reload" />{/if}
{$errorScreenStore.buttonTitle}
</button>
{/if}
</div>
</main>
{/if}
<style lang="scss">
main.errorScreen {

View File

@ -0,0 +1,21 @@
<script lang="ts">
import { uiWebsitesStore } from "../../../Stores/UIWebsiteStore";
import UiWebsiteLayer from "./UIWebsiteLayer.svelte";
</script>
<div id="ui-website-container">
{#each $uiWebsitesStore.reverse() as uiWebsite (uiWebsite.id)}
<UiWebsiteLayer {uiWebsite} />
{/each}
</div>
<style lang="scss">
#ui-website-container {
position: absolute;
z-index: 180;
height: 100%;
width: 100%;
top: 0;
left: 0;
}
</style>

View File

@ -0,0 +1,66 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { UIWebsite } from "../../../Api/Events/ui/UIWebsite";
import { iframeListener } from "../../../Api/IframeListener";
export let uiWebsite: UIWebsite;
let main: HTMLDivElement;
const iframe = document.createElement("iframe");
$: {
iframe.id = `ui-website-${uiWebsite.id}`;
iframe.src = uiWebsite.url;
iframe.title = uiWebsite.url;
iframe.style.border = "0";
iframe.allow = uiWebsite.allowPolicy ?? "";
iframe.style.height = uiWebsite.size.height;
iframe.style.width = uiWebsite.size.width;
iframe.style.visibility = uiWebsite.visible ? "visible" : "hidden";
iframe.style.margin = uiWebsite.margin
? `${uiWebsite.margin.top ? uiWebsite.margin.top : "O"} ${
uiWebsite.margin.right ? uiWebsite.margin.right : "O"
} ${uiWebsite.margin.bottom ? uiWebsite.margin.bottom : "O"} ${
uiWebsite.margin.left ? uiWebsite.margin.left : "O"
}`
: "0";
}
onMount(() => {
main.appendChild(iframe);
if (uiWebsite.allowApi) {
iframeListener.registerIframe(iframe);
}
});
onDestroy(() => {
if (uiWebsite.allowApi) {
iframeListener.unregisterIframe(iframe);
}
});
</script>
<div
bind:this={main}
class="layer"
style:justify-content={uiWebsite.position.horizontal === "middle"
? "center"
: uiWebsite.position.horizontal === "right"
? "end"
: "start"}
style:align-items={uiWebsite.position.vertical === "middle"
? "center"
: uiWebsite.position.vertical === "bottom"
? "end"
: "top"}
/>
<style lang="scss">
.layer {
height: 100%;
width: 100%;
display: flex;
position: absolute;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -5,7 +5,7 @@
showDesktopCapturerSourcePicker,
} from "../../Stores/ScreenSharingStore";
import { onDestroy, onMount } from "svelte";
import type { DesktopCapturerSource } from "@wa-preload-app";
import type { DesktopCapturerSource } from "../../Interfaces/DesktopAppInterfaces";
let desktopCapturerSources: DesktopCapturerSource[] = [];
let interval: ReturnType<typeof setInterval>;

View File

@ -296,7 +296,32 @@ class ConnectionManager {
});
connection.connectionErrorStream.subscribe((event: CloseEvent) => {
console.log("connectionErrorStream => An error occurred while connecting to socket server. Retrying");
console.info(
"An error occurred while connecting to socket server. Retrying => Event: ",
event.reason,
event.code,
event
);
//However, Chrome will rarely report any close code 1006 reasons to the Javascript side.
//This is likely due to client security rules in the WebSocket spec to prevent abusing WebSocket.
//(such as using it to scan for open ports on a destination server, or for generating lots of connections for a denial-of-service attack).
// more detail here: https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
if (event.code === 1006) {
//check cookies
const cookies = document.cookie.split(";");
for (const cookie of cookies) {
//check id cookie posthog exist
const numberIndexPh = cookie.indexOf("_posthog=");
if (numberIndexPh !== -1) {
//if exist, remove posthog cookie
document.cookie =
cookie.slice(0, numberIndexPh + 9) +
"; domain=.workadventu.re; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/";
}
}
}
reject(
new Error(
"An error occurred while connecting to socket server. Retrying. Code: " +

View File

@ -25,6 +25,7 @@ export class Room {
private _canReport: boolean = false;
private _loadingLogo: string | undefined;
private _loginSceneLogo: string | undefined;
private _showPoweredBy: boolean | undefined = true;
private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname;
@ -120,6 +121,7 @@ export class Room {
this._canReport = data.canReport ?? false;
this._loadingLogo = data.loadingLogo ?? undefined;
this._loginSceneLogo = data.loginSceneLogo ?? undefined;
this._showPoweredBy = data.showPoweredBy ?? true;
return new MapDetail(data.mapUrl);
} else {
console.log(data);
@ -213,4 +215,8 @@ export class Room {
get loginSceneLogo(): string | undefined {
return this._loginSceneLogo;
}
get showPoweredBy(): boolean | undefined {
return this._showPoweredBy;
}
}

View File

@ -0,0 +1,21 @@
// copy of Electron.SourcesOptions to avoid Electron dependency in front
export interface SourcesOptions {
types: string[];
thumbnailSize?: { height: number; width: number };
}
export interface DesktopCapturerSource {
id: string;
name: string;
thumbnailURL: string;
}
export type WorkAdventureDesktopApi = {
desktop: boolean;
isDevelopment: () => Promise<boolean>;
getVersion: () => Promise<string>;
notify: (txt: string) => void;
onMuteToggle: (callback: () => void) => void;
onCameraToggle: (callback: () => void) => void;
getDesktopCapturerSources: (options: SourcesOptions) => Promise<DesktopCapturerSource[]>;
};

View File

@ -54,7 +54,7 @@ export class Loader {
.catch((e) => console.warn("Could not load logo: ", logoResource, e));
let poweredByLogoPromise: CancelablePromise<Texture> | undefined;
if (gameManager.currentStartedRoom.loadingLogo) {
if (gameManager.currentStartedRoom.loadingLogo && gameManager.currentStartedRoom.showPoweredBy !== false) {
poweredByLogoPromise = this.superLoad.image(
"poweredByLogo",
"static/images/Powered_By_WorkAdventure_Small.png"

View File

@ -4,5 +4,6 @@ export const DEPTH_TILE_INDEX = 0;
//Note: Player characters use their y coordinate as their depth to simulate a perspective.
//See the Character class.
export const DEPTH_OVERLAY_INDEX = 10000;
export const DEPTH_BUBBLE_CHAT_SPRITE = 99999;
export const DEPTH_INGAME_TEXT_INDEX = 100000;
export const DEPTH_UI_INDEX = 1000000;

View File

@ -76,6 +76,7 @@ import { contactPageStore } from "../../Stores/MenuStore";
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import { currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore";
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture;
@ -90,8 +91,8 @@ import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import { MapStore } from "../../Stores/Utils/MapStore";
import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte";
import { i18nJson } from "../../i18n/locales";
import LL, { locale } from "../../i18n/i18n-svelte";
import { availabilityStatusStore, localVolumeStore } from "../../Stores/MediaStore";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
@ -101,7 +102,9 @@ import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import { PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages";
import { DEPTH_BUBBLE_CHAT_SPRITE } from "./DepthIndexes";
import { ErrorScreenMessage, PlayerDetailsUpdatedMessage } from "../../Messages/ts-proto-generated/protos/messages";
import { uiWebsiteManager } from "./UI/UIWebsiteManager";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@ -613,14 +616,30 @@ export class GameScene extends DirtyScene {
if (this.isReconnecting) {
setTimeout(() => {
this.scene.sleep();
this.scene.launch(ReconnectingSceneName);
errorScreenStore.setError(
ErrorScreenMessage.fromPartial({
type: "reconnecting",
code: "CONNECTION_LOST",
title: get(LL).warning.connectionLostTitle(),
details: get(LL).warning.connectionLostSubtitle(),
})
);
//this.scene.launch(ReconnectingSceneName);
}, 0);
} else if (this.connection === undefined) {
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout(() => {
if (this.connection === undefined) {
this.scene.sleep();
this.scene.launch(ReconnectingSceneName);
errorScreenStore.setError(
ErrorScreenMessage.fromPartial({
type: "reconnecting",
code: "CONNECTION_LOST",
title: get(LL).warning.connectionLostTitle(),
details: get(LL).warning.connectionLostSubtitle(),
})
);
//this.scene.launch(ReconnectingSceneName);
}
}, 1000);
}
@ -904,7 +923,9 @@ export class GameScene extends DirtyScene {
// Analyze tags to find if we are admin. If yes, show console.
if (this.scene.isSleeping()) {
this.scene.stop(ReconnectingSceneName);
const error = get(errorScreenStore);
if (error && error?.type === "reconnecting") errorScreenStore.delete();
//this.scene.stop(ReconnectingSceneName);
}
//init user position and play trigger to check layers properties
@ -1111,6 +1132,24 @@ export class GameScene extends DirtyScene {
})
);
this.iframeSubscriptionList.push(
iframeListener.enablePlayerControlStream.subscribe(() => {
this.userInputManager.restoreControls();
})
);
this.iframeSubscriptionList.push(
iframeListener.disablePlayerProximityMeetingStream.subscribe(() => {
this.disableMediaBehaviors();
})
);
this.iframeSubscriptionList.push(
iframeListener.enablePlayerProximityMeetingStream.subscribe(() => {
this.enableMediaBehaviors();
})
);
this.iframeSubscriptionList.push(
iframeListener.cameraSetStream.subscribe((cameraSetEvent) => {
const duration = cameraSetEvent.smooth ? 1000 : 0;
@ -1198,11 +1237,6 @@ export class GameScene extends DirtyScene {
})
);
this.iframeSubscriptionList.push(
iframeListener.enablePlayerControlStream.subscribe(() => {
this.userInputManager.restoreControls();
})
);
this.iframeSubscriptionList.push(
iframeListener.loadPageStream.subscribe((url: string) => {
this.loadNextGameFromExitUrl(url)
@ -1225,7 +1259,7 @@ export class GameScene extends DirtyScene {
this.CurrentPlayer.y,
"circleSprite-white"
);
scriptedBubbleSprite.setDisplayOrigin(48, 48);
scriptedBubbleSprite.setDisplayOrigin(48, 48).setDepth(DEPTH_BUBBLE_CHAT_SPRITE);
this.add.existing(scriptedBubbleSprite);
})
);
@ -1313,6 +1347,17 @@ export class GameScene extends DirtyScene {
data.propertyValue = this.gameMap.getLayerProperty(data.layerName, data.propertyName);
return data;
});
iframeListener.registerAnswerer("openUIWebsite", (websiteConfig) => {
return uiWebsiteManager.open(websiteConfig);
});
iframeListener.registerAnswerer("getUIWebsites", () => {
return uiWebsiteManager.getAll();
});
iframeListener.registerAnswerer("closeUIWebsite", (websiteId) => {
return uiWebsiteManager.close(websiteId);
});
iframeListener.registerAnswerer("getMapData", () => {
return {
@ -1610,6 +1655,9 @@ export class GameScene extends DirtyScene {
iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline");
iframeListener.unregisterAnswerer("setVariable");
iframeListener.unregisterAnswerer("openUIWebsite");
iframeListener.unregisterAnswerer("getUIWebsites");
iframeListener.unregisterAnswerer("closeUIWebsite");
this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close();
@ -2079,7 +2127,7 @@ export class GameScene extends DirtyScene {
? "circleSprite-red"
: "circleSprite-white"
);
sprite.setDisplayOrigin(48, 48);
sprite.setDisplayOrigin(48, 48).setDepth(DEPTH_BUBBLE_CHAT_SPRITE);
this.add.existing(sprite);
this.groups.set(groupPositionMessage.groupId, sprite);
if (this.currentPlayerGroupId === groupPositionMessage.groupId) {

View File

@ -0,0 +1,94 @@
import { get } from "svelte/store";
import { CreateUIWebsiteEvent, ModifyUIWebsiteEvent, UIWebsite } from "../../../Api/Events/ui/UIWebsite";
import { iframeListener } from "../../../Api/IframeListener";
import { v4 as uuidv4 } from "uuid";
import { uiWebsitesStore } from "../../../Stores/UIWebsiteStore";
class UIWebsiteManager {
constructor() {
iframeListener.modifyUIWebsiteStream.subscribe((websiteEvent: ModifyUIWebsiteEvent) => {
const website = get(uiWebsitesStore).find((currentWebsite) => currentWebsite.id === websiteEvent.id);
if (!website) {
throw new Error(`Could not find ui website with the id "${websiteEvent.id}" in your map`);
}
if (websiteEvent.url) {
website.url = websiteEvent.url;
}
if (websiteEvent.visible !== undefined) {
website.visible = websiteEvent.visible;
}
if (websiteEvent.position) {
if (websiteEvent.position.horizontal) {
website.position.horizontal = websiteEvent.position.horizontal;
}
if (websiteEvent.position.vertical) {
website.position.vertical = websiteEvent.position.vertical;
}
}
if (websiteEvent.size) {
if (websiteEvent.size.height) {
website.size.height = websiteEvent.size.height;
}
if (websiteEvent.size.width) {
website.size.width = websiteEvent.size.width;
}
}
if (websiteEvent.margin) {
website.margin = {};
if (websiteEvent.margin.top !== undefined) {
website.margin.top = websiteEvent.margin.top;
}
if (websiteEvent.margin.bottom !== undefined) {
website.margin.bottom = websiteEvent.margin.bottom;
}
if (websiteEvent.margin.left !== undefined) {
website.margin.left = websiteEvent.margin.left;
}
if (websiteEvent.margin.right !== undefined) {
website.margin.right = websiteEvent.margin.right;
}
}
});
}
public open(websiteConfig: CreateUIWebsiteEvent): UIWebsite {
const newWebsite: UIWebsite = {
...websiteConfig,
id: uuidv4(),
visible: websiteConfig.visible ?? true,
allowPolicy: websiteConfig.allowPolicy ?? "",
allowApi: websiteConfig.allowApi ?? false,
};
uiWebsitesStore.add(newWebsite);
return newWebsite;
}
public getAll(): UIWebsite[] {
return get(uiWebsitesStore);
}
public close(websiteId: string) {
const uiWebsite = get(uiWebsitesStore).find((currentWebsite) => currentWebsite.id === websiteId);
if (!uiWebsite) {
return;
}
uiWebsitesStore.remove(uiWebsite);
}
}
export const uiWebsiteManager = new UIWebsiteManager();

View File

@ -94,6 +94,7 @@ export class CustomizeScene extends AbstractCharacterScene {
}
public create(): void {
this.selectedLayers = [0, 0, 0, 0, 0, 0];
this.tryLoadLastUsedWokaLayers();
waScaleManager.zoomModifier = 1;
this.createSlotBackgroundTextures();
@ -154,11 +155,10 @@ export class CustomizeScene extends AbstractCharacterScene {
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])
);
const index = this.layers[i].findIndex((item) => item.id === gameManager.getCharacterLayers()[i]);
// set first item as default if not found
this.selectedLayers[i] = index !== -1 ? index : 0;
}
}
} catch {

View File

@ -5,13 +5,16 @@ import { ErrorScreenMessage } from "../Messages/ts-proto-generated/protos/messag
* A store that contains one error of type WAError to be displayed.
*/
function createErrorScreenStore() {
const { subscribe, set } = writable<ErrorScreenMessage>(undefined);
const { subscribe, set } = writable<ErrorScreenMessage | undefined>(undefined);
return {
subscribe,
setError: (e: ErrorScreenMessage): void => {
set(e);
},
delete: () => {
set(undefined);
},
};
}

View File

@ -2,7 +2,7 @@ import { derived, Readable, readable, writable } from "svelte/store";
import { peerStore } from "./PeerStore";
import type { LocalStreamStoreValue } from "./MediaStore";
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
import type { DesktopCapturerSource } from "@wa-preload-app";
import type { DesktopCapturerSource } from "../Interfaces/DesktopAppInterfaces";
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any

View File

@ -0,0 +1,20 @@
import { writable } from "svelte/store";
import { UIWebsite } from "../Api/Events/ui/UIWebsite";
function createUIWebsiteStore() {
const { subscribe, update, set } = writable(Array<UIWebsite>());
set(Array<UIWebsite>());
return {
subscribe,
add: (uiWebsite: UIWebsite) => {
update((currentArray) => [...currentArray, uiWebsite]);
},
remove: (uiWebsite: UIWebsite) => {
update((currentArray) => currentArray.filter((currentWebsite) => currentWebsite.id !== uiWebsite.id));
},
};
}
export const uiWebsitesStore = createUIWebsiteStore();

View File

@ -238,6 +238,7 @@ class CoWebsiteManager {
const iframe = coWebsite.getIframe();
if (iframe) {
this.activateMainLoaderAnimation();
iframe.style.display = "none";
}
this.resizing = true;
@ -258,6 +259,7 @@ class CoWebsiteManager {
const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "flex";
this.desactivateMainLoaderAnimation();
}
this.resizing = false;
});
@ -273,6 +275,7 @@ class CoWebsiteManager {
const iframe = coWebsite.getIframe();
if (iframe) {
this.activateMainLoaderAnimation();
iframe.style.display = "none";
}
this.resizing = true;
@ -296,6 +299,7 @@ class CoWebsiteManager {
const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "flex";
this.desactivateMainLoaderAnimation();
}
this.resizing = false;
});
@ -309,9 +313,7 @@ class CoWebsiteManager {
if (this.cowebsiteDom.classList.contains("closing")) {
this.cowebsiteDom.classList.remove("closing");
if (this.loaderAnimationInterval.interval) {
clearInterval(this.loaderAnimationInterval.interval);
}
this.desactivateMainLoaderAnimation();
this.loaderAnimationInterval.trails = undefined;
}
});
@ -353,7 +355,10 @@ class CoWebsiteManager {
this.fire();
}
private loadMain(openingWidth?: number): void {
private activateMainLoaderAnimation() {
this.desactivateMainLoaderAnimation();
this.cowebsiteLoaderDom.style.display = "block";
this.loaderAnimationInterval.interval = setInterval(() => {
if (!this.loaderAnimationInterval.trails) {
this.loaderAnimationInterval.trails = [0, 1, 2];
@ -361,7 +366,6 @@ class CoWebsiteManager {
for (let trail = 1; trail < this.loaderAnimationInterval.trails.length + 1; trail++) {
for (let state = 0; state < 4; state++) {
// const newState = this.loaderAnimationInterval.frames + trail -1;
const stateDom = this.cowebsiteLoaderDom.querySelector(
`#trail-${trail}-state-${state}`
) as SVGPolygonElement;
@ -382,6 +386,17 @@ class CoWebsiteManager {
trail === 3 ? 0 : trail + 1
);
}, 200);
}
private desactivateMainLoaderAnimation() {
if (this.loaderAnimationInterval.interval) {
this.cowebsiteLoaderDom.style.display = "none";
clearInterval(this.loaderAnimationInterval.interval);
}
}
private loadMain(openingWidth?: number): void {
this.activateMainLoaderAnimation();
if (!this.verticalMode && openingWidth) {
let newWidth = 50;
@ -623,6 +638,8 @@ class CoWebsiteManager {
setTimeout(() => {
this.fire();
}, animationTime);
this.desactivateMainLoaderAnimation();
} else if (
!highlightedEmbed &&
this.getCoWebsites().find((searchCoWebsite) => searchCoWebsite.getId() === coWebsite.getId())

View File

@ -14,6 +14,8 @@ const warning: NonNullable<Translation["warning"]> = {
},
importantMessage: "Wichtige Nachricht",
connectionLost: "Verbindungen unterbrochen. Wiederverbinden...",
connectionLostTitle: "Verbindungen unterbrochen",
connectionLostSubtitle: "Wiederverbinden",
};
export default warning;

View File

@ -13,6 +13,8 @@ const warning: BaseTranslation = {
},
importantMessage: "Important message",
connectionLost: "Connection lost. Reconnecting...",
connectionLostTitle: "Connection lost",
connectionLostSubtitle: "Reconnecting",
};
export default warning;

View File

@ -71,6 +71,7 @@
&-loader {
width: 20%;
display: none;
#smoke {
@for $i from 1 through 3 {

View File

@ -1,6 +1,5 @@
{
// "include": ["src/**/*"],
// "include": ["src/**/*"],
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
@ -14,23 +13,17 @@
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"importsNotUsedAsValues": "remove",
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
"paths": {
"@wa-preload-app": ["../desktop/electron/src/preload-app/types.ts"],
}
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
},
"exclude": [
"node_modules",
@ -38,4 +31,4 @@
"public/iframe_api.js",
"packages/iframe-api-typings"
]
}
}

View File

@ -2244,10 +2244,10 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-plugin-svelte@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1"
integrity sha512-+iHY2uGChOngrgKielJUnqo74gIL/EO5oeWm8MftFWjEi213lq9QYTOwm1pv4lI1nA61tdgf80CF2i5zMcu1kw==
prettier-plugin-svelte@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.7.0.tgz#ecfa4fe824238a4466a3497df1a96d15cf43cabb"
integrity sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==
prettier@^2.0.2:
version "2.5.1"

View File

@ -0,0 +1,5 @@
<html>
<body style="background-color: white;">
This is test page
</body>
</html>

View File

@ -0,0 +1,48 @@
WA.onInit().then(() => {
initListeners();
});
function initListeners() {
let first_website = undefined;
let second_website = undefined;
WA.room.onEnterLayer('first_website').subscribe(async () => {
first_website = await WA.ui.website.open({
url: "http://maps.workadventure.localhost/tests/UIWebsite/index.html",
position: {
vertical: "middle",
horizontal: "middle",
},
size: {
height: "50vh",
width: "50vw",
},
});
});
WA.room.onLeaveLayer('first_website').subscribe(() => {
if (first_website) {
first_website.close();
}
});
WA.room.onEnterLayer('second_website').subscribe(async () => {
second_website = await WA.ui.website.open({
url: "https://www.wikipedia.org/",
position: {
vertical: "top",
horizontal: "right",
},
size: {
height: "20vh",
width: "50vw",
},
});
});
WA.room.onLeaveLayer('second_website').subscribe(() => {
if (second_website) {
second_website.close();
}
});
}

View File

@ -0,0 +1,679 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":10,
"id":1,
"name":"floor",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23,
0, 0, 0, 0, 0, 23, 23, 23, 23, 23],
"height":10,
"id":5,
"name":"first_website",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 12, 12, 12, 12, 12,
0, 0, 0, 0, 0, 12, 12, 12, 12, 12,
0, 0, 0, 0, 0, 12, 12, 12, 12, 12,
0, 0, 0, 0, 0, 12, 12, 12, 12, 12,
0, 0, 0, 0, 0, 12, 12, 12, 12, 12],
"height":10,
"id":7,
"name":"second_website",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":116.924156284309,
"id":1,
"name":"Tests",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":8,
"text":"Test 1:\nMove on the white carpet to display a UIWebsite.\n\nTest 2:\nMove on the blue carpet to display an other UIWebsite above the first.",
"wrap":true
},
"type":"",
"visible":true,
"width":158.381128664136,
"x":1.64026713939023,
"y":201.037039933902
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 82, 0,
0, 0, 0, 0, 0, 0, 0, 8, 0, 0,
0, 0, 0, 0, 0, 0, 0, 19, 27, 0,
0, 0, 0, 0, 0, 0, 0, 30, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":8,
"name":"objects",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
}],
"nextlayerid":9,
"nextobjectid":3,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"script.js"
}],
"renderorder":"right-down",
"tiledversion":"1.8.4",
"tileheight":32,
"tilesets":[
{
"columns":11,
"firstgid":1,
"image":"..\/tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"spacing":0,
"tilecount":121,
"tileheight":32,
"tiles":[
{
"id":1,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":2,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":3,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":4,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":5,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":6,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":7,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":8,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":9,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":10,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":12,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":18,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":19,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":20,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":21,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":23,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":24,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":25,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":26,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":27,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":28,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":29,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":30,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":31,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":32,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":34,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":35,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":42,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":43,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":45,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":46,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":59,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":60,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":70,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":71,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":80,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":81,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":89,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":91,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":93,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":94,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":95,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":96,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":97,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":100,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":102,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":103,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":104,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":105,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":106,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":107,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":108,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":114,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":115,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":"1.8",
"width":10
}

View File

@ -363,6 +363,14 @@
<a href="#" class="testLink" data-testmap="Modules/without_modules.json" target="_blank">Testing scripts with modules mode disabled</a>
</td>
</tr>
<tr>
<td>
<input type="radio" name="test-uiwebsite"> Success <input type="radio" name="test-uiwebsite"> Failure <input type="radio" name="test-uiwebsite" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="UIWebsite/uiwebsite.json" target="_blank">Testing UIWebsites</a>
</td>
</tr>
</table>
<h2>CoWebsite</h2>
<table class="table">

View File

@ -49,6 +49,10 @@ export const isMapDetailsData = z.object({
description: "The URL of the image to be used on the LoginScene",
example: "https://example.com/logo_login.png",
}),
showPoweredBy: extendApi(z.boolean(), {
description: "The URL of the image to be used on the name scene",
example: "https://example.com/logo_login.png",
}),
});
export type MapDetailsData = z.infer<typeof isMapDetailsData>;

View File

@ -49,6 +49,7 @@ class LocalAdmin implements AdminInterface {
iframeAuthentication: null,
loadingLogo: null,
loginSceneLogo: null,
showPoweredBy: true,
});
}