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, mapUrl,
authenticationMandatory: null, authenticationMandatory: null,
group: 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: Example:
```javascript ```ts
WA.room.onEnterLayer('myZone').subscribe(() => { WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerControls(); WA.controls.disablePlayerControls();
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{ WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
@ -25,5 +25,28 @@ WA.room.onEnterLayer('myZone').subscribe(() => {
popup.close(); 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. 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 { class Popup {
/** /**
* Closes the popup * Closes the popup
@ -45,7 +45,7 @@ class Popup {
Example: Example:
```javascript ```ts
let helloWorldPopup; let helloWorldPopup;
// Open the popup when we enter a given zone // Open the popup when we enter a given zone
@ -91,7 +91,7 @@ Custom menu exist only until the map is unloaded, or you leave the iframe zone o
</div> </div>
Example: Example:
```javascript ```ts
const menu = WA.ui.registerMenuCommand('menu test', const menu = WA.ui.registerMenuCommand('menu test',
{ {
callback: () => { 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. The `Menu` class contains a single method: `remove(): void`. This will obviously remove the menu when called.
```javascript ```ts
class Menu { class Menu {
/** /**
* Remove the menu * Remove the menu
@ -120,7 +120,7 @@ class Menu {
### Awaiting User Confirmation (with space bar) ### Awaiting User Confirmation (with space bar)
``` ```ts
WA.ui.displayActionMessage({ WA.ui.displayActionMessage({
message: string, message: string,
callback: () => void, callback: () => void,
@ -136,7 +136,7 @@ Displays a message at the bottom of the screen (that will disappear when space b
Example: Example:
```javascript ```ts
const triggerMessage = WA.ui.displayActionMessage({ const triggerMessage = WA.ui.displayActionMessage({
message: "press 'space' to confirm", message: "press 'space' to confirm",
callback: () => { 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. The `ActionMessage` class contains a single method: `remove(): Promise<void>`. This will obviously remove the message when called.
```javascript ```ts
class ActionMessage { class ActionMessage {
/** /**
* Hides the message * 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. 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) => { WA.ui.onRemotePlayerClicked.subscribe((remotePlayer) => {
remotePlayer.addAction('Ask to tell a joke', () => { remotePlayer.addAction('Ask to tell a joke', () => {
console.log('I am NOT telling you 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: `remotePlayer.addAction(actionName, callback)` returns an Action object, which can remove itself from ActionsMenu:
```javascript ```ts
const action = remotePlayer.addAction('This will disappear!', () => { const action = remotePlayer.addAction('This will disappear!', () => {
console.log('You managed to click me!'); console.log('You managed to click me!');
}); });
@ -193,3 +193,95 @@ setTimeout(
1000, 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 *.sh
!templater.sh !templater.sh
/public/iframe_api.js /public/iframe_api.js
/public/es.js

View File

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

View File

@ -40,6 +40,7 @@ import { isAddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRem
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent"; import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
import { isRemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent"; import { isRemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
import { isGetPropertyEvent } from "./GetPropertyEvent"; import { isGetPropertyEvent } from "./GetPropertyEvent";
import { isCreateUIWebsiteEvent, isModifyUIWebsiteEvent, isUIWebsite } from "./ui/UIWebsite";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -97,6 +98,14 @@ export const isIframeEventWrapper = z.union([
type: z.literal("restorePlayerControls"), type: z.literal("restorePlayerControls"),
data: z.undefined(), data: z.undefined(),
}), }),
z.object({
type: z.literal("disablePlayerProximityMeeting"),
data: z.undefined(),
}),
z.object({
type: z.literal("restorePlayerProximityMeeting"),
data: z.undefined(),
}),
z.object({ z.object({
type: z.literal("displayBubble"), type: z.literal("displayBubble"),
data: z.undefined(), data: z.undefined(),
@ -153,6 +162,10 @@ export const isIframeEventWrapper = z.union([
type: z.literal("modifyEmbeddedWebsite"), type: z.literal("modifyEmbeddedWebsite"),
data: isEmbeddedWebsiteEvent, data: isEmbeddedWebsiteEvent,
}), }),
z.object({
type: z.literal("modifyUIWebsite"),
data: isModifyUIWebsiteEvent,
}),
]); ]);
export type IframeEvent = z.infer<typeof isIframeEventWrapper>; export type IframeEvent = z.infer<typeof isIframeEventWrapper>;
@ -266,6 +279,18 @@ export const iframeQueryMapTypeGuards = {
query: isMovePlayerToEventConfig, query: isMovePlayerToEventConfig,
answer: isMovePlayerToEventAnswer, 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; 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 { AddActionsMenuKeyToRemotePlayerEvent } from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent"; import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent";
import { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent"; import { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
import { ModifyUIWebsiteEvent } from "./Events/ui/UIWebsite";
type AnswererCallback<T extends keyof IframeQueryMap> = ( type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"], query: IframeQueryMap[T]["query"],
@ -58,6 +59,15 @@ class IframeListener {
private readonly _disablePlayerControlStream: Subject<void> = new Subject(); private readonly _disablePlayerControlStream: Subject<void> = new Subject();
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable(); 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(); private readonly _cameraSetStream: Subject<CameraSetEvent> = new Subject();
public readonly cameraSetStream = this._cameraSetStream.asObservable(); public readonly cameraSetStream = this._cameraSetStream.asObservable();
@ -73,9 +83,6 @@ class IframeListener {
public readonly removeActionsMenuKeyFromRemotePlayerEvent = public readonly removeActionsMenuKeyFromRemotePlayerEvent =
this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable(); this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable();
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject(); private readonly _closePopupStream: Subject<ClosePopupEvent> = new Subject();
public readonly closePopupStream = this._closePopupStream.asObservable(); public readonly closePopupStream = this._closePopupStream.asObservable();
@ -115,6 +122,9 @@ class IframeListener {
private readonly _modifyEmbeddedWebsiteStream: Subject<ModifyEmbeddedWebsiteEvent> = new Subject(); private readonly _modifyEmbeddedWebsiteStream: Subject<ModifyEmbeddedWebsiteEvent> = new Subject();
public readonly modifyEmbeddedWebsiteStream = this._modifyEmbeddedWebsiteStream.asObservable(); 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 iframes = new Set<HTMLIFrameElement>();
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>(); private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
private readonly scripts = new Map<string, HTMLIFrameElement>(); private readonly scripts = new Map<string, HTMLIFrameElement>();
@ -263,6 +273,10 @@ class IframeListener {
this._disablePlayerControlStream.next(); this._disablePlayerControlStream.next();
} else if (iframeEvent.type === "restorePlayerControls") { } else if (iframeEvent.type === "restorePlayerControls") {
this._enablePlayerControlStream.next(); this._enablePlayerControlStream.next();
} else if (iframeEvent.type === "disablePlayerProximityMeeting") {
this._disablePlayerProximityMeetingStream.next();
} else if (iframeEvent.type === "restorePlayerProximityMeeting") {
this._enablePlayerProximityMeetingStream.next();
} else if (iframeEvent.type === "displayBubble") { } else if (iframeEvent.type === "displayBubble") {
this._displayBubbleStream.next(); this._displayBubbleStream.next();
} else if (iframeEvent.type === "removeBubble") { } else if (iframeEvent.type === "removeBubble") {
@ -279,6 +293,8 @@ class IframeListener {
this._setTilesStream.next(iframeEvent.data); this._setTilesStream.next(iframeEvent.data);
} else if (iframeEvent.type == "modifyEmbeddedWebsite") { } else if (iframeEvent.type == "modifyEmbeddedWebsite") {
this._modifyEmbeddedWebsiteStream.next(iframeEvent.data); this._modifyEmbeddedWebsiteStream.next(iframeEvent.data);
} else if (iframeEvent.type == "modifyUIWebsite") {
this._modifyUIWebsiteStream.next(iframeEvent.data);
} else if (iframeEvent.type == "registerMenu") { } else if (iframeEvent.type == "registerMenu") {
const dataName = iframeEvent.data.name; const dataName = iframeEvent.data.name;
this.iframeCloseCallbacks.get(iframe)?.push(() => { this.iframeCloseCallbacks.get(iframe)?.push(() => {

View File

@ -1,6 +1,6 @@
import { requestedCameraState, requestedMicrophoneState, silentStore } from "../../Stores/MediaStore"; import { requestedCameraState, requestedMicrophoneState, silentStore } from "../../Stores/MediaStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { WorkAdventureDesktopApi } from "@wa-preload-app"; import { WorkAdventureDesktopApi } from "../../Interfaces/DesktopAppInterfaces";
declare global { declare global {
interface Window { 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 { restorePlayerControls(): void {
sendToWorkadventure({ type: "restorePlayerControls", data: undefined }); sendToWorkadventure({ type: "restorePlayerControls", data: undefined });
} }
disablePlayerProximityMeeting(): void {
sendToWorkadventure({ type: "disablePlayerProximityMeeting", data: undefined });
}
restorePlayerProximityMeeting(): void {
sendToWorkadventure({ type: "restorePlayerProximityMeeting", data: undefined });
}
} }
export default new WorkadventureControlsCommands(); export default new WorkadventureControlsCommands();

View File

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

View File

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

View File

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

View File

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

View File

@ -296,7 +296,32 @@ class ConnectionManager {
}); });
connection.connectionErrorStream.subscribe((event: CloseEvent) => { 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( reject(
new Error( new Error(
"An error occurred while connecting to socket server. Retrying. Code: " + "An error occurred while connecting to socket server. Retrying. Code: " +

View File

@ -25,6 +25,7 @@ export class Room {
private _canReport: boolean = false; private _canReport: boolean = false;
private _loadingLogo: string | undefined; private _loadingLogo: string | undefined;
private _loginSceneLogo: string | undefined; private _loginSceneLogo: string | undefined;
private _showPoweredBy: boolean | undefined = true;
private constructor(private roomUrl: URL) { private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname; this.id = roomUrl.pathname;
@ -120,6 +121,7 @@ export class Room {
this._canReport = data.canReport ?? false; this._canReport = data.canReport ?? false;
this._loadingLogo = data.loadingLogo ?? undefined; this._loadingLogo = data.loadingLogo ?? undefined;
this._loginSceneLogo = data.loginSceneLogo ?? undefined; this._loginSceneLogo = data.loginSceneLogo ?? undefined;
this._showPoweredBy = data.showPoweredBy ?? true;
return new MapDetail(data.mapUrl); return new MapDetail(data.mapUrl);
} else { } else {
console.log(data); console.log(data);
@ -213,4 +215,8 @@ export class Room {
get loginSceneLogo(): string | undefined { get loginSceneLogo(): string | undefined {
return this._loginSceneLogo; 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)); .catch((e) => console.warn("Could not load logo: ", logoResource, e));
let poweredByLogoPromise: CancelablePromise<Texture> | undefined; let poweredByLogoPromise: CancelablePromise<Texture> | undefined;
if (gameManager.currentStartedRoom.loadingLogo) { if (gameManager.currentStartedRoom.loadingLogo && gameManager.currentStartedRoom.showPoweredBy !== false) {
poweredByLogoPromise = this.superLoad.image( poweredByLogoPromise = this.superLoad.image(
"poweredByLogo", "poweredByLogo",
"static/images/Powered_By_WorkAdventure_Small.png" "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. //Note: Player characters use their y coordinate as their depth to simulate a perspective.
//See the Character class. //See the Character class.
export const DEPTH_OVERLAY_INDEX = 10000; export const DEPTH_OVERLAY_INDEX = 10000;
export const DEPTH_BUBBLE_CHAT_SPRITE = 99999;
export const DEPTH_INGAME_TEXT_INDEX = 100000; export const DEPTH_INGAME_TEXT_INDEX = 100000;
export const DEPTH_UI_INDEX = 1000000; 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 type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore } from "../../Stores/AudioManagerStore"; import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import { currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore"; import { currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore";
import { errorScreenStore } from "../../Stores/ErrorScreenStore";
import EVENT_TYPE = Phaser.Scenes.Events; import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture; 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 { MapStore } from "../../Stores/Utils/MapStore";
import { followUsersColorStore } from "../../Stores/FollowStore"; import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte";
import { i18nJson } from "../../i18n/locales"; import { i18nJson } from "../../i18n/locales";
import LL, { locale } from "../../i18n/i18n-svelte";
import { availabilityStatusStore, localVolumeStore } from "../../Stores/MediaStore"; import { availabilityStatusStore, localVolumeStore } from "../../Stores/MediaStore";
import { StringUtils } from "../../Utils/StringUtils"; import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
@ -101,7 +102,9 @@ import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import CancelablePromise from "cancelable-promise"; import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred"; import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin"; 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 { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
reconnecting: boolean; reconnecting: boolean;
@ -613,14 +616,30 @@ export class GameScene extends DirtyScene {
if (this.isReconnecting) { if (this.isReconnecting) {
setTimeout(() => { setTimeout(() => {
this.scene.sleep(); 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); }, 0);
} else if (this.connection === undefined) { } else if (this.connection === undefined) {
// Let's wait 1 second before printing the "connecting" screen to avoid blinking // Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout(() => { setTimeout(() => {
if (this.connection === undefined) { if (this.connection === undefined) {
this.scene.sleep(); 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); }, 1000);
} }
@ -904,7 +923,9 @@ export class GameScene extends DirtyScene {
// Analyze tags to find if we are admin. If yes, show console. // Analyze tags to find if we are admin. If yes, show console.
if (this.scene.isSleeping()) { 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 //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( this.iframeSubscriptionList.push(
iframeListener.cameraSetStream.subscribe((cameraSetEvent) => { iframeListener.cameraSetStream.subscribe((cameraSetEvent) => {
const duration = cameraSetEvent.smooth ? 1000 : 0; 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( this.iframeSubscriptionList.push(
iframeListener.loadPageStream.subscribe((url: string) => { iframeListener.loadPageStream.subscribe((url: string) => {
this.loadNextGameFromExitUrl(url) this.loadNextGameFromExitUrl(url)
@ -1225,7 +1259,7 @@ export class GameScene extends DirtyScene {
this.CurrentPlayer.y, this.CurrentPlayer.y,
"circleSprite-white" "circleSprite-white"
); );
scriptedBubbleSprite.setDisplayOrigin(48, 48); scriptedBubbleSprite.setDisplayOrigin(48, 48).setDepth(DEPTH_BUBBLE_CHAT_SPRITE);
this.add.existing(scriptedBubbleSprite); this.add.existing(scriptedBubbleSprite);
}) })
); );
@ -1313,6 +1347,17 @@ export class GameScene extends DirtyScene {
data.propertyValue = this.gameMap.getLayerProperty(data.layerName, data.propertyName); data.propertyValue = this.gameMap.getLayerProperty(data.layerName, data.propertyName);
return data; 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", () => { iframeListener.registerAnswerer("getMapData", () => {
return { return {
@ -1610,6 +1655,9 @@ export class GameScene extends DirtyScene {
iframeListener.unregisterAnswerer("getCoWebsites"); iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline"); iframeListener.unregisterAnswerer("setPlayerOutline");
iframeListener.unregisterAnswerer("setVariable"); iframeListener.unregisterAnswerer("setVariable");
iframeListener.unregisterAnswerer("openUIWebsite");
iframeListener.unregisterAnswerer("getUIWebsites");
iframeListener.unregisterAnswerer("closeUIWebsite");
this.sharedVariablesManager?.close(); this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close(); this.embeddedWebsiteManager?.close();
@ -2079,7 +2127,7 @@ export class GameScene extends DirtyScene {
? "circleSprite-red" ? "circleSprite-red"
: "circleSprite-white" : "circleSprite-white"
); );
sprite.setDisplayOrigin(48, 48); sprite.setDisplayOrigin(48, 48).setDepth(DEPTH_BUBBLE_CHAT_SPRITE);
this.add.existing(sprite); this.add.existing(sprite);
this.groups.set(groupPositionMessage.groupId, sprite); this.groups.set(groupPositionMessage.groupId, sprite);
if (this.currentPlayerGroupId === groupPositionMessage.groupId) { 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 { public create(): void {
this.selectedLayers = [0, 0, 0, 0, 0, 0];
this.tryLoadLastUsedWokaLayers(); this.tryLoadLastUsedWokaLayers();
waScaleManager.zoomModifier = 1; waScaleManager.zoomModifier = 1;
this.createSlotBackgroundTextures(); this.createSlotBackgroundTextures();
@ -154,11 +155,10 @@ export class CustomizeScene extends AbstractCharacterScene {
try { try {
const savedWokaLayers = gameManager.getCharacterLayers(); const savedWokaLayers = gameManager.getCharacterLayers();
if (savedWokaLayers && savedWokaLayers.length !== 0) { if (savedWokaLayers && savedWokaLayers.length !== 0) {
this.selectedLayers = [];
for (let i = 0; i < savedWokaLayers.length; i += 1) { for (let i = 0; i < savedWokaLayers.length; i += 1) {
this.selectedLayers.push( const index = this.layers[i].findIndex((item) => item.id === gameManager.getCharacterLayers()[i]);
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 { } 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. * A store that contains one error of type WAError to be displayed.
*/ */
function createErrorScreenStore() { function createErrorScreenStore() {
const { subscribe, set } = writable<ErrorScreenMessage>(undefined); const { subscribe, set } = writable<ErrorScreenMessage | undefined>(undefined);
return { return {
subscribe, subscribe,
setError: (e: ErrorScreenMessage): void => { setError: (e: ErrorScreenMessage): void => {
set(e); set(e);
}, },
delete: () => {
set(undefined);
},
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
{ {
// "include": ["src/**/*"], // "include": ["src/**/*"],
"extends": "@tsconfig/svelte/tsconfig.json", "extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist/", "outDir": "./dist/",
@ -14,23 +13,17 @@
"jsx": "react", "jsx": "react",
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"importsNotUsedAsValues": "remove", "importsNotUsedAsValues": "remove",
"strict": true, /* Enable all strict type-checking options. */
"strict": true, /* Enable all strict type-checking options. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "strictNullChecks": true, /* Enable strict null checks. */
"strictNullChecks": true, /* Enable strict null checks. */ "strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */ "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"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. */
"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. */
"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"],
}
}, },
"exclude": [ "exclude": [
"node_modules", "node_modules",

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" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier-plugin-svelte@^2.5.0: prettier-plugin-svelte@^2.7.0:
version "2.5.0" version "2.7.0"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1" resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.7.0.tgz#ecfa4fe824238a4466a3497df1a96d15cf43cabb"
integrity sha512-+iHY2uGChOngrgKielJUnqo74gIL/EO5oeWm8MftFWjEi213lq9QYTOwm1pv4lI1nA61tdgf80CF2i5zMcu1kw== integrity sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==
prettier@^2.0.2: prettier@^2.0.2:
version "2.5.1" 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> <a href="#" class="testLink" data-testmap="Modules/without_modules.json" target="_blank">Testing scripts with modules mode disabled</a>
</td> </td>
</tr> </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> </table>
<h2>CoWebsite</h2> <h2>CoWebsite</h2>
<table class="table"> <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", description: "The URL of the image to be used on the LoginScene",
example: "https://example.com/logo_login.png", 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>; export type MapDetailsData = z.infer<typeof isMapDetailsData>;

View File

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