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

This commit is contained in:
_Bastler
2022-05-09 07:58:46 +02:00
43 changed files with 1754 additions and 90 deletions
+1
View File
@@ -5,3 +5,4 @@ dist/
*.sh
!templater.sh
/public/iframe_api.js
/public/es.js
+2 -2
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"
]
}
}
}
+25
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;
+75
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>;
+19 -3
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(() => {
+1 -1
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 {
+338
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();
+8
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();
+6
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();
+1 -1
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>
+6
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">
+30 -22
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 {
@@ -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>
@@ -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

@@ -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>;
+26 -1
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: " +
+6
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;
}
}
@@ -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[]>;
};
+1 -1
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"
+1
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;
+60 -12
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) {
@@ -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();
+4 -4
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 {
+4 -1
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);
},
};
}
+1 -1
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
+20
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();
+22 -5
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())
+2
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;
+2
View File
@@ -13,6 +13,8 @@ const warning: BaseTranslation = {
},
importantMessage: "Important message",
connectionLost: "Connection lost. Reconnecting...",
connectionLostTitle: "Connection lost",
connectionLostSubtitle: "Reconnecting",
};
export default warning;
+1
View File
@@ -71,6 +71,7 @@
&-loader {
width: 20%;
display: none;
#smoke {
@for $i from 1 through 3 {
+12 -19
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"
]
}
}
+4 -4
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"