Merge branch 'develop' of ssh://git.bstly.de:222/_Bastler/partey_workadventure
This commit is contained in:
@@ -5,14 +5,16 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface()
|
||||
url: tg.isString,
|
||||
allowApi: tg.isOptional(tg.isBoolean),
|
||||
allowPolicy: tg.isOptional(tg.isString),
|
||||
widthPercent: tg.isOptional(tg.isNumber),
|
||||
position: tg.isOptional(tg.isNumber),
|
||||
closable: tg.isOptional(tg.isBoolean),
|
||||
lazy: tg.isOptional(tg.isBoolean),
|
||||
})
|
||||
.get();
|
||||
|
||||
export const isCoWebsite = new tg.IsInterface()
|
||||
.withProperties({
|
||||
id: tg.isString,
|
||||
position: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
|
||||
|
||||
export class CoWebsite {
|
||||
constructor(private readonly id: string, public readonly position: number) {}
|
||||
constructor(private readonly id: string) {}
|
||||
|
||||
close() {
|
||||
return queryWorkadventure({
|
||||
@@ -41,17 +41,28 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
||||
});
|
||||
}
|
||||
|
||||
async openCoWebSite(url: string, allowApi?: boolean, allowPolicy?: string, position?: number): Promise<CoWebsite> {
|
||||
async openCoWebSite(
|
||||
url: string,
|
||||
allowApi?: boolean,
|
||||
allowPolicy?: string,
|
||||
widthPercent?: number,
|
||||
position?: number,
|
||||
closable?: boolean,
|
||||
lazy?: boolean
|
||||
): Promise<CoWebsite> {
|
||||
const result = await queryWorkadventure({
|
||||
type: "openCoWebsite",
|
||||
data: {
|
||||
url,
|
||||
allowApi,
|
||||
allowPolicy,
|
||||
widthPercent,
|
||||
position,
|
||||
closable,
|
||||
lazy,
|
||||
},
|
||||
});
|
||||
return new CoWebsite(result.id, result.position);
|
||||
return new CoWebsite(result.id);
|
||||
}
|
||||
|
||||
async getCoWebSites(): Promise<CoWebsite[]> {
|
||||
@@ -59,7 +70,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
||||
type: "getCoWebsites",
|
||||
data: undefined,
|
||||
});
|
||||
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position));
|
||||
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="typescript">
|
||||
import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
|
||||
|
||||
let actionsMenuData: ActionsMenuData | undefined;
|
||||
|
||||
let actionsMenuStoreUnsubscriber: Unsubscriber | null;
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") {
|
||||
closeActionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function closeActionsMenu() {
|
||||
actionsMenuStore.clear();
|
||||
}
|
||||
|
||||
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
|
||||
actionsMenuData = value;
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (actionsMenuStoreUnsubscriber) {
|
||||
actionsMenuStoreUnsubscriber();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if actionsMenuData}
|
||||
<div class="actions-menu nes-container is-rounded">
|
||||
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>×</button>
|
||||
<h2>{actionsMenuData.playerName}</h2>
|
||||
<div class="actions">
|
||||
{#each [...actionsMenuData.actions] as { actionName, callback }}
|
||||
<button
|
||||
type="button"
|
||||
class="nes-btn"
|
||||
on:click|preventDefault={() => {
|
||||
callback();
|
||||
}}
|
||||
>
|
||||
{actionName}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.actions-menu {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
width: 260px !important;
|
||||
height: max-content !important;
|
||||
max-height: 40vh;
|
||||
margin-top: 200px;
|
||||
|
||||
pointer-events: auto;
|
||||
font-family: "Press Start 2P";
|
||||
background-color: #333333;
|
||||
color: whitesmoke;
|
||||
|
||||
.actions {
|
||||
max-height: calc(100% - 50px);
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
button {
|
||||
width: calc(100% - 10px);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-family: "Press Start 2P";
|
||||
}
|
||||
|
||||
.nes-btn.is-error.close {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: -20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+41
-155
@@ -1,166 +1,52 @@
|
||||
<script lang="typescript">
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore } from "../Stores/MenuStore";
|
||||
import { emoteMenuStore } from "../Stores/EmoteStore";
|
||||
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import MyCamera from "./MyCamera.svelte";
|
||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||
import { selectCompanionSceneVisibleStore } from "../Stores/SelectCompanionStore";
|
||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
||||
import { errorStore } from "../Stores/ErrorStore";
|
||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||
import LoginScene from "./Login/LoginScene.svelte";
|
||||
import Chat from "./Chat/Chat.svelte";
|
||||
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||
import { requestVisitCardsStore } from "../Stores/GameStore";
|
||||
|
||||
import type { Game } from "../Phaser/Game/Game";
|
||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
||||
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
||||
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
||||
import { errorStore } from "../Stores/ErrorStore";
|
||||
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||
import { selectCompanionSceneVisibleStore } from "../Stores/SelectCompanionStore";
|
||||
import Chat from "./Chat/Chat.svelte";
|
||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||
import LoginScene from "./Login/LoginScene.svelte";
|
||||
import MainLayout from "./MainLayout.svelte";
|
||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||
import Menu from "./Menu/Menu.svelte";
|
||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
||||
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||
import { warningContainerStore } from "../Stores/MenuStore";
|
||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||
import LayoutManager from "./LayoutManager/LayoutManager.svelte";
|
||||
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||
import { followStateStore } from "../Stores/FollowStore";
|
||||
import { peerStore } from "../Stores/PeerStore";
|
||||
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||
|
||||
export let game: Game;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if $loginSceneVisibleStore}
|
||||
<div class="scrollable">
|
||||
<LoginScene {game} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCharacterScene {game} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $customCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<CustomCharacterScene {game} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCompanionSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCompanionScene {game} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $enableCameraSceneVisibilityStore}
|
||||
<div class="scrollable">
|
||||
<EnableCameraScene {game} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $banMessageStore.length > 0}
|
||||
<div>
|
||||
<BanMessageContainer />
|
||||
</div>
|
||||
{:else if $textMessageStore.length > 0}
|
||||
<div>
|
||||
<TextMessageContainer />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $soundPlayingStore}
|
||||
<div>
|
||||
<AudioPlaying url={$soundPlayingStore} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $audioManagerVisibilityStore}
|
||||
<div>
|
||||
<AudioManager />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $layoutManagerVisibilityStore}
|
||||
<div>
|
||||
<LayoutManager />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $showReportScreenStore !== userReportEmpty}
|
||||
<div>
|
||||
<ReportMenu />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||
<div>
|
||||
<FollowMenu />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $menuIconVisiblilityStore}
|
||||
<div>
|
||||
<MenuIcon />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $menuVisiblilityStore}
|
||||
<div>
|
||||
<Menu />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $emoteMenuStore}
|
||||
<div>
|
||||
<EmoteMenu />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $gameOverlayVisibilityStore}
|
||||
<div>
|
||||
<VideoOverlay />
|
||||
<MyCamera />
|
||||
<CameraControls />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $helpCameraSettingsVisibleStore}
|
||||
<div>
|
||||
<HelpCameraSettingsPopup />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $showLimitRoomModalStore}
|
||||
<div>
|
||||
<LimitRoomModal />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $showShareLinkMapModalStore}
|
||||
<div>
|
||||
<ShareLinkMapModal />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||
{/if}
|
||||
{#if $errorStore.length > 0}
|
||||
<div>
|
||||
<ErrorDialog />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $errorStore.length > 0}
|
||||
<div>
|
||||
<ErrorDialog />
|
||||
</div>
|
||||
{:else if $loginSceneVisibleStore}
|
||||
<div class="scrollable">
|
||||
<LoginScene {game} />
|
||||
</div>
|
||||
{:else if $selectCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCharacterScene {game} />
|
||||
</div>
|
||||
{:else if $customCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<CustomCharacterScene {game} />
|
||||
</div>
|
||||
{:else if $selectCompanionSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCompanionScene {game} />
|
||||
</div>
|
||||
{:else if $enableCameraSceneVisibilityStore}
|
||||
<div class="scrollable">
|
||||
<EnableCameraScene {game} />
|
||||
</div>
|
||||
{:else}
|
||||
<MainLayout />
|
||||
|
||||
{#if $chatVisibilityStore}
|
||||
<Chat />
|
||||
{/if}
|
||||
{#if $warningContainerStore}
|
||||
<WarningContainer />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -157,13 +157,16 @@
|
||||
|
||||
<style lang="scss">
|
||||
div.main-audio-manager.nes-container.is-rounded {
|
||||
position: relative;
|
||||
top: 0.5rem;
|
||||
position: absolute;
|
||||
top: 1%;
|
||||
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
||||
width: clamp(200px, 15vw, 15vw);
|
||||
padding: 3px 3px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 550;
|
||||
|
||||
background-color: rgb(0, 0, 0, 0.5);
|
||||
display: grid;
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||
import layoutChatImg from "./images/layout-chat.svg";
|
||||
import { layoutModeStore } from "../Stores/StreamableCollectionStore";
|
||||
import followImg from "./images/follow.svg";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { peerStore } from "../Stores/PeerStore";
|
||||
import { onDestroy } from "svelte";
|
||||
import { embedScreenLayout } from "../Stores/EmbedScreensStore";
|
||||
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
|
||||
import { gameManager } from "../Phaser/Game/GameManager";
|
||||
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if (isSilent) return;
|
||||
@@ -42,10 +47,26 @@
|
||||
}
|
||||
|
||||
function switchLayoutMode() {
|
||||
if ($layoutModeStore === LayoutMode.Presentation) {
|
||||
$layoutModeStore = LayoutMode.VideoChat;
|
||||
if ($embedScreenLayout === LayoutMode.Presentation) {
|
||||
$embedScreenLayout = LayoutMode.VideoChat;
|
||||
} else {
|
||||
$layoutModeStore = LayoutMode.Presentation;
|
||||
$embedScreenLayout = LayoutMode.Presentation;
|
||||
}
|
||||
}
|
||||
|
||||
function followClick() {
|
||||
switch ($followStateStore) {
|
||||
case "off":
|
||||
gameScene.connection?.emitFollowRequest();
|
||||
followRoleStore.set("leader");
|
||||
followStateStore.set("active");
|
||||
break;
|
||||
case "requesting":
|
||||
case "active":
|
||||
case "ending":
|
||||
gameScene.connection?.emitFollowAbort();
|
||||
followUsersStore.stopFollowing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,41 +77,156 @@
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
||||
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
||||
{:else}
|
||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="btn-monitor nes-btn is-dark"
|
||||
on:click={screenSharingClick}
|
||||
class:hide={!$screenSharingAvailableStore || isSilent}
|
||||
class:enabled={$requestedScreenSharingState}
|
||||
>
|
||||
{#if $requestedScreenSharingState && !isSilent}
|
||||
<img src={monitorImg} alt="Start screen sharing" />
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState ||
|
||||
isSilent}>
|
||||
{#if $requestedCameraState && !isSilent}
|
||||
<img src={cinemaImg} alt="Turn on webcam" />
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
||||
{#if $requestedMicrophoneState && !isSilent}
|
||||
<img src={microphoneImg} alt="Turn on microphone" />
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||
<img class="noselect" src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
||||
{:else}
|
||||
<img class="noselect" src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="btn-follow nes-btn is-dark"
|
||||
class:hide={($peerStore.size === 0 && $followStateStore === "off") || isSilent}
|
||||
class:disabled={$followStateStore !== "off"}
|
||||
on:click={followClick}
|
||||
>
|
||||
<img class="noselect" src={followImg} alt="" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="btn-monitor nes-btn is-dark"
|
||||
on:click={screenSharingClick}
|
||||
class:hide={!$screenSharingAvailableStore || isSilent}
|
||||
class:enabled={$requestedScreenSharingState}
|
||||
>
|
||||
{#if $requestedScreenSharingState && !isSilent}
|
||||
<img class="noselect" src={monitorImg} alt="Start screen sharing" />
|
||||
{:else}
|
||||
<img class="noselect" src={monitorCloseImg} alt="Stop screen sharing" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
|
||||
{#if $requestedCameraState && !isSilent}
|
||||
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
|
||||
{:else}
|
||||
<img class="noselect" src={cinemaCloseImg} alt="Turn off webcam" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
||||
{#if $requestedMicrophoneState && !isSilent}
|
||||
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
|
||||
{:else}
|
||||
<img class="noselect" src={microphoneCloseImg} alt="Turn off microphone" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../style/breakpoints.scss";
|
||||
|
||||
.btn-cam-action {
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
display: inline-flex;
|
||||
bottom: 10px;
|
||||
right: 15px;
|
||||
width: 360px;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
justify-content: flex-end;
|
||||
z-index: 251;
|
||||
|
||||
&:hover {
|
||||
div.hide {
|
||||
transform: translateY(60px);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*btn animation*/
|
||||
.btn-cam-action div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
transform: translateY(15px);
|
||||
transition-timing-function: ease-in-out;
|
||||
transition: all 0.3s;
|
||||
margin: 0 4%;
|
||||
|
||||
&.hide {
|
||||
transform: translateY(60px);
|
||||
}
|
||||
}
|
||||
.btn-cam-action div.disabled {
|
||||
background: #d75555;
|
||||
}
|
||||
.btn-cam-action div.enabled {
|
||||
background: #73c973;
|
||||
}
|
||||
.btn-cam-action:hover div {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.btn-cam-action div:hover {
|
||||
background: #407cf7;
|
||||
box-shadow: 4px 4px 48px #666;
|
||||
transition: 120ms;
|
||||
}
|
||||
.btn-micro {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.btn-video {
|
||||
pointer-events: auto;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.btn-monitor {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.btn-layout {
|
||||
pointer-events: auto;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.btn-cam-action div img {
|
||||
height: 22px;
|
||||
width: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-follow {
|
||||
pointer-events: auto;
|
||||
|
||||
img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
/**
|
||||
* If we cannot hover over elements, let's display camera button in full.
|
||||
*/
|
||||
.btn-cam-action {
|
||||
div {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.btn-cam-action {
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
max-height: 40px;
|
||||
|
||||
div {
|
||||
width: 20%;
|
||||
max-height: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} on:click={onClick} />
|
||||
|
||||
<aside class="chatWindow nes-container is-rounded is-dark" transition:fly={{ x: -1000, duration: 500 }}
|
||||
bind:this={chatWindowElement}>
|
||||
<p class="close-icon" on:click={closeChat}>×</p>
|
||||
<aside class="chatWindow nes-container is-rounded is-dark" transition:fly={{ x: -1000, duration: 500 }} bind:this={chatWindowElement}>
|
||||
<p class="close-icon noselect" on:click={closeChat}>×</p>
|
||||
<section class="messagesList" bind:this={listDom}>
|
||||
<ul>
|
||||
<li><p class="system-text">{$LL.chat.intro()}</p></li>
|
||||
@@ -78,7 +77,7 @@
|
||||
}
|
||||
|
||||
aside.chatWindow {
|
||||
z-index: 100;
|
||||
z-index: 1000;
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
form.customCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
@@ -129,7 +131,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
form.customCharacterScene button.customCharacterSceneButtonLeft {
|
||||
left: 5vw;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="typescript">
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
||||
import MediaBox from "../Video/MediaBox.svelte";
|
||||
|
||||
export let highlightedEmbedScreen: EmbedScreen | null;
|
||||
export let full = false;
|
||||
$: clickable = !full;
|
||||
</script>
|
||||
|
||||
<aside class="cameras-container" class:full>
|
||||
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||
{#if !highlightedEmbedScreen || highlightedEmbedScreen.type !== "streamable" || (highlightedEmbedScreen.type === "streamable" && highlightedEmbedScreen.embed !== peer)}
|
||||
<MediaBox streamable={peer} isClickable={clickable} />
|
||||
{/if}
|
||||
{/each}
|
||||
</aside>
|
||||
|
||||
<style lang="scss">
|
||||
.cameras-container {
|
||||
flex: 0 0 25%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
&:first-child {
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
&.full {
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,324 @@
|
||||
<script lang="typescript">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { ICON_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { mainCoWebsite } from "../../Stores/CoWebsiteStore";
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
|
||||
export let index: number;
|
||||
export let coWebsite: CoWebsite;
|
||||
export let vertical: boolean;
|
||||
|
||||
let icon: HTMLImageElement;
|
||||
let iconLoaded = false;
|
||||
let state = coWebsite.getStateSubscriber();
|
||||
let isJitsi: boolean = coWebsite instanceof JitsiCoWebsite;
|
||||
const mainState = coWebsiteManager.getMainStateSubscriber();
|
||||
|
||||
onMount(() => {
|
||||
icon.src = isJitsi
|
||||
? "/resources/logos/meet.svg"
|
||||
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||
icon.alt = coWebsite.getUrl().hostname;
|
||||
icon.onload = () => {
|
||||
iconLoaded = true;
|
||||
};
|
||||
});
|
||||
|
||||
async function onClick() {
|
||||
if (vertical) {
|
||||
coWebsiteManager.goToMain(coWebsite);
|
||||
} else if ($mainCoWebsite) {
|
||||
if ($mainCoWebsite.getId() === coWebsite.getId()) {
|
||||
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||
coWebsiteManager.displayMain();
|
||||
} else if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||
coWebsiteManager.goToMain($highlightedEmbedScreen.embed);
|
||||
} else {
|
||||
coWebsiteManager.hideMain();
|
||||
}
|
||||
} else {
|
||||
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||
coWebsiteManager.goToMain(coWebsite);
|
||||
coWebsiteManager.displayMain();
|
||||
} else {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: coWebsite,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($state === "asleep") {
|
||||
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
coWebsiteManager.resizeAllIframes();
|
||||
}
|
||||
|
||||
function noDrag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let isHighlight: boolean = false;
|
||||
let isMain: boolean = false;
|
||||
$: {
|
||||
isMain =
|
||||
$mainState === iframeStates.opened &&
|
||||
$mainCoWebsite !== undefined &&
|
||||
$mainCoWebsite.getId() === coWebsite.getId();
|
||||
isHighlight =
|
||||
$highlightedEmbedScreen !== null &&
|
||||
$highlightedEmbedScreen.type === "cowebsite" &&
|
||||
$highlightedEmbedScreen.embed.getId() === coWebsite.getId();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
id={"cowebsite-thumbnail-" + index}
|
||||
class="cowebsite-thumbnail nes-pointer"
|
||||
class:asleep={$state === "asleep"}
|
||||
class:loading={$state === "loading"}
|
||||
class:ready={$state === "ready"}
|
||||
class:displayed={isMain || isHighlight}
|
||||
class:vertical
|
||||
on:click={onClick}
|
||||
>
|
||||
<img
|
||||
class="cowebsite-icon noselect nes-pointer"
|
||||
class:hide={!iconLoaded}
|
||||
class:jitsi={isJitsi}
|
||||
bind:this={icon}
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
alt=""
|
||||
/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="cowebsite-icon"
|
||||
class:hide={iconLoaded}
|
||||
style="margin: auto; background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%; shape-rendering: auto;"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<rect x="19" y="19" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="40" y="19" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.125s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="61" y="19" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.25s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="19" y="40" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.875s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="61" y="40" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.375s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="19" y="61" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.75s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="40" y="61" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.625s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect><rect x="61" y="61" width="20" height="20" fill="#14304c">
|
||||
<animate
|
||||
attributeName="fill"
|
||||
values="#365dff;#14304c;#14304c"
|
||||
keyTimes="0;0.125;1"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
begin="0.5s"
|
||||
calcMode="discrete"
|
||||
/>
|
||||
</rect>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.cowebsite-thumbnail {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
background-color: rgba(#000000, 0.6);
|
||||
margin: 12px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
|
||||
margin: 4px;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 4px;
|
||||
border-image-slice: 3;
|
||||
border-image-width: 3;
|
||||
border-image-repeat: stretch;
|
||||
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(33,37,41)" /></svg>');
|
||||
border-image-outset: 1;
|
||||
}
|
||||
|
||||
&:not(.vertical) {
|
||||
transition: all 300ms;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
margin: 7px;
|
||||
|
||||
&::before {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.cowebsite-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
animation: shake 0.35s ease-in-out;
|
||||
}
|
||||
|
||||
&.displayed {
|
||||
&:not(.vertical) {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
&.asleep {
|
||||
filter: grayscale(100%);
|
||||
--webkit-filter: grayscale(100%);
|
||||
}
|
||||
|
||||
&.loading {
|
||||
animation: 2500ms ease-in-out 0s infinite alternate backgroundLoading;
|
||||
}
|
||||
|
||||
&.ready {
|
||||
&::before {
|
||||
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(38, 74, 110)" /></svg>');
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes backgroundLoading {
|
||||
0% {
|
||||
background-color: rgba(#000000, 0.6);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: #25598e;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.cowebsite-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: cover;
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.jitsi {
|
||||
filter: invert(100%);
|
||||
-webkit-filter: invert(100%);
|
||||
padding: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script lang="typescript">
|
||||
import { coWebsites } from "../../Stores/CoWebsiteStore";
|
||||
import CoWebsiteThumbnail from "./CoWebsiteThumbnailSlot.svelte";
|
||||
|
||||
export let vertical = false;
|
||||
</script>
|
||||
|
||||
{#if $coWebsites.length > 0}
|
||||
<div id="cowebsite-thumbnail-container" class:vertical>
|
||||
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())}
|
||||
<CoWebsiteThumbnail {index} {coWebsite} {vertical} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
#cowebsite-thumbnail-container {
|
||||
pointer-events: all;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 2%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
&.vertical {
|
||||
height: auto !important;
|
||||
width: auto !important;
|
||||
bottom: auto !important;
|
||||
left: auto !important;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="typescript">
|
||||
import PresentationLayout from "./Layouts/PresentationLayout.svelte";
|
||||
import MozaicLayout from "./Layouts/MozaicLayout.svelte";
|
||||
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
||||
import { embedScreenLayout } from "../../Stores/EmbedScreensStore";
|
||||
</script>
|
||||
|
||||
<div id="embedScreensContainer">
|
||||
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||
<PresentationLayout />
|
||||
{:else}
|
||||
<MozaicLayout />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#embedScreensContainer {
|
||||
display: flex;
|
||||
padding-top: 2%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||
import { streamableCollectionStore } from "../../../Stores/StreamableCollectionStore";
|
||||
import MediaBox from "../../Video/MediaBox.svelte";
|
||||
|
||||
let layoutDom: HTMLDivElement;
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {});
|
||||
|
||||
onMount(() => {
|
||||
resizeObserver.observe(layoutDom);
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="mozaic-layout" bind:this={layoutDom}>
|
||||
<div
|
||||
class="media-container"
|
||||
class:full-width={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||
class:quarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size === 4}
|
||||
>
|
||||
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||
<MediaBox
|
||||
streamable={peer}
|
||||
mozaicSolo={$streamableCollectionStore.size === 1}
|
||||
mozaicFullWidth={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||
mozaicQuarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size >= 4}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#mozaic-layout {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
.media-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 33.3% 33.3% 33.3%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.full-width {
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: 50% 50%;
|
||||
}
|
||||
|
||||
&.quarter {
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: 50% 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||
import CamerasContainer from "../CamerasContainer.svelte";
|
||||
import MediaBox from "../../Video/MediaBox.svelte";
|
||||
import { coWebsiteManager } from "../../../WebRtc/CoWebsiteManager";
|
||||
import { afterUpdate, onMount } from "svelte";
|
||||
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../../../Utils/BreakpointsUtils";
|
||||
import { peerStore } from "../../../Stores/PeerStore";
|
||||
|
||||
function closeCoWebsite() {
|
||||
if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||
if ($highlightedEmbedScreen.embed.isClosable()) {
|
||||
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed);
|
||||
} else {
|
||||
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => {
|
||||
console.error("Cannot unload co-website", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
if ($highlightedEmbedScreen) {
|
||||
coWebsiteManager.resizeAllIframes();
|
||||
}
|
||||
});
|
||||
|
||||
let layoutDom: HTMLDivElement;
|
||||
|
||||
let displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||
let displayFullMedias = isMediaBreakpointUp("sm");
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||
displayFullMedias = isMediaBreakpointUp("sm");
|
||||
|
||||
if (!displayCoWebsiteContainer && $highlightedEmbedScreen && $highlightedEmbedScreen.type === "cowebsite") {
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
}
|
||||
|
||||
if (displayFullMedias) {
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
resizeObserver.observe(layoutDom);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="presentation-layout" bind:this={layoutDom} class:full-medias={displayFullMedias}>
|
||||
{#if displayFullMedias}
|
||||
<div id="full-medias">
|
||||
<CamerasContainer full={true} highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||
</div>
|
||||
{:else}
|
||||
<div id="embed-left-block" class:full={$peerStore.size === 0}>
|
||||
<div id="main-embed-screen">
|
||||
{#if $highlightedEmbedScreen}
|
||||
{#if $highlightedEmbedScreen.type === "streamable"}
|
||||
{#key $highlightedEmbedScreen.embed.uniqueId}
|
||||
<MediaBox
|
||||
isHightlighted={true}
|
||||
isClickable={true}
|
||||
streamable={$highlightedEmbedScreen.embed}
|
||||
/>
|
||||
{/key}
|
||||
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
||||
{#key $highlightedEmbedScreen.embed.getId()}
|
||||
<div
|
||||
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
|
||||
class="highlighted-cowebsite nes-container is-rounded"
|
||||
>
|
||||
<div class="actions">
|
||||
<button type="button" class="nes-btn is-error close" on:click={closeCoWebsite}
|
||||
>×</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $peerStore.size > 0}
|
||||
<CamerasContainer highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#presentation-layout {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
&.full-medias {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
#embed-left-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 0 0 75%;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
|
||||
&.full {
|
||||
flex: 0 0 98% !important;
|
||||
width: 98% !important;
|
||||
}
|
||||
}
|
||||
|
||||
#main-embed-screen {
|
||||
height: 100%;
|
||||
margin-bottom: 3%;
|
||||
|
||||
.highlighted-cowebsite {
|
||||
height: 100% !important;
|
||||
width: 96%;
|
||||
background-color: rgba(#000000, 0.6);
|
||||
margin: 0 !important;
|
||||
|
||||
.actions {
|
||||
z-index: 200;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: end;
|
||||
gap: 2%;
|
||||
|
||||
button {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,8 +3,8 @@
|
||||
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { EmojiButton } from "@joeattardi/emoji-button";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
let emojiContainer: HTMLElement;
|
||||
let picker: EmojiButton;
|
||||
@@ -20,7 +20,7 @@
|
||||
"--secondary-text-color": "whitesmoke",
|
||||
"--category-button-color": "whitesmoke",
|
||||
},
|
||||
emojisPerRow: isMobile() ? 6 : 8,
|
||||
emojisPerRow: isMediaBreakpointUp("md") ? 6 : 8,
|
||||
autoFocusSearch: false,
|
||||
style: "native",
|
||||
showPreview: false,
|
||||
@@ -86,6 +86,8 @@
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.emote-menu {
|
||||
|
||||
@@ -127,6 +127,8 @@
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
.enableCameraScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
@@ -214,7 +216,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
.enableCameraScene h2 {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="typescript">
|
||||
import followImg from "../images/follow.svg";
|
||||
|
||||
export let hidden: Boolean;
|
||||
|
||||
let cancelButton = false;
|
||||
</script>
|
||||
|
||||
<div class="btn-follow" class:hide={hidden} class:cancel={cancelButton}>
|
||||
<img src={followImg} alt="" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.btn-follow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: solid 0px black;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: #666;
|
||||
box-shadow: 2px 2px 24px #444;
|
||||
border-radius: 48px;
|
||||
transform: translateY(15px);
|
||||
transition-timing-function: ease-in-out;
|
||||
margin: 0 4%;
|
||||
|
||||
img {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,5 @@
|
||||
<!--
|
||||
vim: ft=typescript
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import followImg from "../images/follow.svg";
|
||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
@@ -11,11 +7,7 @@ vim: ft=typescript
|
||||
|
||||
function name(userId: number): string {
|
||||
const user = gameScene.MapPlayersByKey.get(userId);
|
||||
return user ? user.PlayerValue : "";
|
||||
}
|
||||
|
||||
function sendFollowRequest() {
|
||||
gameScene.CurrentPlayer.sendFollowRequest();
|
||||
return user ? user.playerName : "";
|
||||
}
|
||||
|
||||
function acceptFollowRequest() {
|
||||
@@ -83,7 +75,7 @@ vim: ft=typescript
|
||||
|
||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||
<div class="interact-status nes-container is-rounded">
|
||||
<section class="interact-status">
|
||||
<section>
|
||||
{#if $followRoleStore === "follower"}
|
||||
<p>{$LL.follow.interactStatus.following({ leader: name($followUsersStore[0]) })}</p>
|
||||
{:else if $followUsersStore.length === 0}
|
||||
@@ -109,48 +101,27 @@ vim: ft=typescript
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $followStateStore === "off"}
|
||||
<button
|
||||
type="button"
|
||||
class="nes-btn is-primary follow-menu-button"
|
||||
on:click|preventDefault={sendFollowRequest}
|
||||
title="Ask others to follow"><img class="background-img" src={followImg} alt="" /></button
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||
{#if $followRoleStore === "follower"}
|
||||
<button
|
||||
type="button"
|
||||
class="nes-btn is-error follow-menu-button"
|
||||
on:click|preventDefault={reset}
|
||||
title="Stop following"><img class="background-img" src={followImg} alt="" /></button
|
||||
>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="nes-btn is-error follow-menu-button"
|
||||
on:click|preventDefault={reset}
|
||||
title="Stop leading the way"><img class="background-img" src={followImg} alt="" /></button
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
.nes-container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.interact-status {
|
||||
.interact-status {
|
||||
background-color: #333333;
|
||||
color: whitesmoke;
|
||||
|
||||
position: relative;
|
||||
height: 2.7em;
|
||||
position: absolute;
|
||||
max-height: 2.7em;
|
||||
width: 40vw;
|
||||
top: 87vh;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
z-index: 400;
|
||||
}
|
||||
|
||||
div.interact-menu {
|
||||
@@ -158,10 +129,14 @@ vim: ft=typescript
|
||||
background-color: #333333;
|
||||
color: whitesmoke;
|
||||
|
||||
position: relative;
|
||||
position: absolute;
|
||||
width: 60vw;
|
||||
top: 60vh;
|
||||
margin: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
z-index: 150;
|
||||
|
||||
section.interact-menu-title {
|
||||
margin-bottom: 20px;
|
||||
@@ -189,23 +164,16 @@ vim: ft=typescript
|
||||
}
|
||||
}
|
||||
|
||||
.follow-menu-button {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
div.interact-status {
|
||||
width: 100vw;
|
||||
@include media-breakpoint-up(md) {
|
||||
.interact-status {
|
||||
width: 90vw;
|
||||
top: 78vh;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
div.interact-menu {
|
||||
height: 21vh;
|
||||
width: 100vw;
|
||||
max-height: 21vh;
|
||||
width: 90vw;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<form
|
||||
class="helpCameraSettings nes-container"
|
||||
on:submit|preventDefault={close}
|
||||
transition:fly={{ y: -900, duration: 500 }}
|
||||
transition:fly={{ y: -50, duration: 500 }}
|
||||
>
|
||||
<section>
|
||||
<h2>{$LL.camera.help.title()}</h2>
|
||||
@@ -55,9 +55,12 @@
|
||||
background: #eceeee;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10vh;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 4%;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
z-index: 600;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
|
||||
|
||||
+7
-1
@@ -35,9 +35,11 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 40px;
|
||||
margin: 0 auto;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
width: clamp(200px, 20vw, 20vw);
|
||||
z-index: 155;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -45,6 +47,10 @@
|
||||
animation: moveMessage 0.5s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: ease-in-out;
|
||||
|
||||
div {
|
||||
margin-bottom: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
div.nes-container {
|
||||
@@ -53,7 +53,7 @@
|
||||
<section class="terms-and-conditions">
|
||||
<a style="display: none;" href="traduction">Need for traduction</a>
|
||||
<p>
|
||||
{$LL.login.terms()}
|
||||
{@html $LL.login.terms()}
|
||||
</p>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<script lang="typescript">
|
||||
import { onMount } from "svelte";
|
||||
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
||||
import { embedScreenLayout, hasEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||
import { emoteMenuStore } from "../Stores/EmoteStore";
|
||||
import { myCameraVisibilityStore } from "../Stores/MyCameraStoreVisibility";
|
||||
import { requestVisitCardsStore } from "../Stores/GameStore";
|
||||
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
||||
import { layoutManagerActionVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
|
||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import EmbedScreensContainer from "./EmbedScreens/EmbedScreensContainer.svelte";
|
||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import LayoutActionManager from "./LayoutActionManager/LayoutActionManager.svelte";
|
||||
import Menu from "./Menu/Menu.svelte";
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import MyCamera from "./MyCamera.svelte";
|
||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
|
||||
import CoWebsitesContainer from "./EmbedScreens/CoWebsitesContainer.svelte";
|
||||
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||
import { followStateStore } from "../Stores/FollowStore";
|
||||
import { peerStore } from "../Stores/PeerStore";
|
||||
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
||||
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
||||
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||
|
||||
let mainLayout: HTMLDivElement;
|
||||
|
||||
let displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||
let displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||
displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
resizeObserver.observe(mainLayout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="main-layout" bind:this={mainLayout}>
|
||||
<aside id="main-layout-left-aside">
|
||||
{#if $menuIconVisiblilityStore}
|
||||
<MenuIcon />
|
||||
{/if}
|
||||
|
||||
{#if $embedScreenLayout === LayoutMode.VideoChat || displayCoWebsiteContainerMd}
|
||||
<CoWebsitesContainer vertical={true} />
|
||||
{/if}
|
||||
</aside>
|
||||
|
||||
<section id="main-layout-main">
|
||||
{#if $menuVisiblilityStore}
|
||||
<Menu />
|
||||
{/if}
|
||||
|
||||
{#if $banMessageStore.length > 0}
|
||||
<BanMessageContainer />
|
||||
{:else if $textMessageStore.length > 0}
|
||||
<TextMessageContainer />
|
||||
{/if}
|
||||
|
||||
{#if $soundPlayingStore}
|
||||
<AudioPlaying url={$soundPlayingStore} />
|
||||
{/if}
|
||||
|
||||
{#if $warningContainerStore}
|
||||
<WarningContainer />
|
||||
{/if}
|
||||
|
||||
{#if $showReportScreenStore !== userReportEmpty}
|
||||
<ReportMenu />
|
||||
{/if}
|
||||
|
||||
{#if $helpCameraSettingsVisibleStore}
|
||||
<HelpCameraSettingsPopup />
|
||||
{/if}
|
||||
|
||||
{#if $audioManagerVisibilityStore}
|
||||
<AudioManager />
|
||||
{/if}
|
||||
|
||||
{#if $showLimitRoomModalStore}
|
||||
<LimitRoomModal />
|
||||
{/if}
|
||||
|
||||
{#if $showShareLinkMapModalStore}
|
||||
<ShareLinkMapModal />
|
||||
{/if}
|
||||
|
||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||
<FollowMenu />
|
||||
{/if}
|
||||
|
||||
{#if $actionsMenuStore}
|
||||
<ActionsMenu />
|
||||
{/if}
|
||||
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||
{/if}
|
||||
|
||||
{#if $emoteMenuStore}
|
||||
<EmoteMenu />
|
||||
{/if}
|
||||
|
||||
{#if hasEmbedScreen}
|
||||
<EmbedScreensContainer />
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section id="main-layout-baseline">
|
||||
{#if displayCoWebsiteContainerLg}
|
||||
<CoWebsitesContainer />
|
||||
{/if}
|
||||
|
||||
{#if $layoutManagerActionVisibilityStore}
|
||||
<LayoutActionManager />
|
||||
{/if}
|
||||
|
||||
{#if $myCameraVisibilityStore}
|
||||
<MyCamera />
|
||||
<CameraControls />
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../style/breakpoints.scss";
|
||||
|
||||
#main-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 120px calc(100% - 120px);
|
||||
grid-template-rows: 80% 20%;
|
||||
|
||||
&-left-aside {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
&-baseline {
|
||||
grid-column: 1/3;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
#main-layout {
|
||||
grid-template-columns: 15% 85%;
|
||||
|
||||
&-left-aside {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
#main-layout {
|
||||
grid-template-columns: 20% 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -100,6 +100,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
.string-HTML {
|
||||
white-space: pre-line;
|
||||
}
|
||||
@@ -126,7 +128,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
div.about-room-main {
|
||||
section.container-overflow {
|
||||
height: calc(100% - 120px);
|
||||
|
||||
@@ -67,6 +67,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
div.global-message-main {
|
||||
height: calc(100% - 50px);
|
||||
display: grid;
|
||||
@@ -109,7 +111,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
.global-message-content {
|
||||
height: calc(100% - 5px);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script lang="ts">
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||
|
||||
let entryPoint: string = $startLayerNamesStore[0];
|
||||
let walkAutomatically: boolean = false;
|
||||
const currentPlayer = gameManager.getCurrentGameScene().CurrentPlayer;
|
||||
const playerPos = { x: Math.floor(currentPlayer.x), y: Math.floor(currentPlayer.y) };
|
||||
|
||||
function copyLink() {
|
||||
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
|
||||
@@ -8,8 +15,23 @@
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function getLink() {
|
||||
return `${location.origin}${location.pathname}#${entryPoint}${
|
||||
walkAutomatically ? `&moveTo=${playerPos.x},${playerPos.y}` : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
function updateInputFieldValue() {
|
||||
const input = document.getElementById("input-share-link");
|
||||
if (input) {
|
||||
(input as HTMLInputElement).value = getLink();
|
||||
}
|
||||
}
|
||||
|
||||
let canShare = navigator.share !== undefined;
|
||||
|
||||
async function shareLink() {
|
||||
const shareData = { url: location.toString() };
|
||||
const shareData = { url: getLink() };
|
||||
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
@@ -22,29 +44,71 @@
|
||||
|
||||
<div class="guest-main">
|
||||
<section class="container-overflow">
|
||||
<section class="share-url not-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
||||
</section>
|
||||
<section class="is-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
||||
{#if !canShare}
|
||||
<section class="share-url not-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="text" readonly id="input-share-link" class="link-url" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="is-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
||||
</section>
|
||||
{/if}
|
||||
<h3>Select an entry point</h3>
|
||||
<section class="nes-select is-dark starting-points">
|
||||
<select
|
||||
bind:value={entryPoint}
|
||||
on:blur={() => {
|
||||
updateInputFieldValue();
|
||||
}}
|
||||
>
|
||||
{#each $startLayerNamesStore as entryPointName}
|
||||
<option value={entryPointName}>{entryPointName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</section>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="nes-checkbox is-dark"
|
||||
bind:checked={walkAutomatically}
|
||||
on:change={() => {
|
||||
updateInputFieldValue();
|
||||
}}
|
||||
/>
|
||||
<span>{$LL.menu.invite.walk_automatically_to_position()}</span>
|
||||
</label>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
div.guest-main {
|
||||
width: 50%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: calc(100% - 56px);
|
||||
|
||||
text-align: center;
|
||||
input.link-url {
|
||||
width: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.starting-points {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
section.nes-select select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
@@ -53,25 +117,23 @@
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
div.guest-main {
|
||||
section.share-url.not-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
div.guest-main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
} else {
|
||||
const customMenu = customMenuIframe.get(menu.label);
|
||||
if (customMenu !== undefined) {
|
||||
activeSubMenu = menu;
|
||||
props = { url: customMenu.url, allowApi: customMenu.allowApi };
|
||||
activeComponent = CustomSubMenu;
|
||||
} else {
|
||||
@@ -129,6 +130,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
.nes-container {
|
||||
padding: 5px;
|
||||
}
|
||||
@@ -140,11 +143,15 @@
|
||||
pointer-events: auto;
|
||||
height: 80%;
|
||||
width: 75%;
|
||||
top: 10%;
|
||||
top: 4%;
|
||||
|
||||
position: relative;
|
||||
z-index: 80;
|
||||
margin: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
position: absolute;
|
||||
z-index: 900;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
||||
@@ -177,12 +184,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
div.menu-container-main {
|
||||
--size-first-columns-grid: 120px;
|
||||
height: 70%;
|
||||
top: 55px;
|
||||
width: 100%;
|
||||
width: 95%;
|
||||
font-size: 0.5em;
|
||||
|
||||
div.menu-nav-sidebar {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
function showMenu() {
|
||||
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
||||
}
|
||||
|
||||
function showChat() {
|
||||
chatVisibilityStore.set(true);
|
||||
}
|
||||
@@ -21,68 +22,104 @@
|
||||
function register() {
|
||||
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
||||
}
|
||||
|
||||
function showInvite() {
|
||||
showShareLinkMapModalStore.set(true);
|
||||
}
|
||||
|
||||
function noDrag() {
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window />
|
||||
|
||||
<main class="menuIcon">
|
||||
<main class="menuIcon noselect">
|
||||
{#if $limitMapStore}
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoInvite}
|
||||
alt={$LL.menu.icon.open.invite()}
|
||||
class="nes-pointer"
|
||||
on:click|preventDefault={showInvite}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoRegister}
|
||||
alt={$LL.menu.icon.open.register()}
|
||||
class="nes-pointer"
|
||||
on:click|preventDefault={register}
|
||||
/>
|
||||
</span>
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoInvite}
|
||||
alt={$LL.menu.icon.open.invite()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showInvite}
|
||||
/>
|
||||
</span>
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoRegister}
|
||||
alt={$LL.menu.icon.open.register()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={register}
|
||||
/>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="nes-btn is-dark">
|
||||
<img src={logoWA} alt={$LL.menu.icon.open.menu()} class="nes-pointer" on:click|preventDefault={showMenu} />
|
||||
</span>
|
||||
<span class="nes-btn is-dark">
|
||||
<img src={logoTalk} alt={$LL.menu.icon.open.chat()} class="nes-pointer" on:click|preventDefault={showChat} />
|
||||
</span>
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoWA}
|
||||
alt={$LL.menu.icon.open.menu()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showMenu}
|
||||
/>
|
||||
</span>
|
||||
<span class="nes-btn is-dark">
|
||||
<img
|
||||
src={logoTalk}
|
||||
alt={$LL.menu.icon.open.chat()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showChat}
|
||||
/>
|
||||
</span>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
.menuIcon {
|
||||
display: inline-grid;
|
||||
z-index: 90;
|
||||
position: relative;
|
||||
margin: 25px;
|
||||
img {
|
||||
pointer-events: auto;
|
||||
width: 24px;
|
||||
padding-top: 0;
|
||||
margin: 3px
|
||||
}
|
||||
}
|
||||
|
||||
.menuIcon img:hover{
|
||||
transform: scale(1.2);
|
||||
}
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 800px) {
|
||||
.menuIcon {
|
||||
margin: 6px;
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
}
|
||||
display: inline-grid;
|
||||
z-index: 90;
|
||||
position: relative;
|
||||
margin: 25px;
|
||||
img {
|
||||
pointer-events: auto;
|
||||
width: 24px;
|
||||
padding-top: 0;
|
||||
margin: 3px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menuIcon img:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.menuIcon {
|
||||
margin-top: 10%;
|
||||
img {
|
||||
pointer-events: auto;
|
||||
width: 60px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
.menuIcon img:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.menuIcon {
|
||||
img {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
|
||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
|
||||
import btnProfileSubMenuIdentity from "../images/btn-menu-profile-identity.svg";
|
||||
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||
import Woka from "../Woka/Woka.svelte";
|
||||
import Companion from "../Companion/Companion.svelte";
|
||||
@@ -30,12 +29,6 @@
|
||||
gameManager.leaveGame(SelectCompanionSceneName, new SelectCompanionScene());
|
||||
}
|
||||
|
||||
function openEditNameScene() {
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
gameManager.leaveGame(LoginSceneName, new LoginScene());
|
||||
}
|
||||
|
||||
function openEditSkinScene() {
|
||||
disableMenuStores();
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
@@ -104,6 +97,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
div.customize-main {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
@@ -163,7 +158,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
div.customize-main.content section button {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
||||
import LL, { locale } from "../../i18n/i18n-svelte";
|
||||
import type { Locales } from "../../i18n/i18n-types";
|
||||
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||
@@ -85,6 +85,8 @@
|
||||
function closeMenu() {
|
||||
menuVisiblilityStore.set(false);
|
||||
}
|
||||
|
||||
const isMobile = isMediaBreakpointUp("md");
|
||||
</script>
|
||||
|
||||
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
||||
@@ -93,22 +95,22 @@
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={valueGame}>
|
||||
<option value={120}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.gameQuality.short.high()
|
||||
: $LL.menu.settings.gameQuality.long.high()}</option
|
||||
>
|
||||
<option value={60}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.gameQuality.short.medium()
|
||||
: $LL.menu.settings.gameQuality.long.medium()}</option
|
||||
>
|
||||
<option value={40}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.gameQuality.short.small()
|
||||
: $LL.menu.settings.gameQuality.long.small()}</option
|
||||
>
|
||||
<option value={20}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.gameQuality.short.minimum()
|
||||
: $LL.menu.settings.gameQuality.long.minimum()}</option
|
||||
>
|
||||
@@ -120,22 +122,22 @@
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={valueVideo}>
|
||||
<option value={30}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.videoQuality.short.high()
|
||||
: $LL.menu.settings.videoQuality.long.high()}</option
|
||||
>
|
||||
<option value={20}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.videoQuality.short.medium()
|
||||
: $LL.menu.settings.videoQuality.long.medium()}</option
|
||||
>
|
||||
<option value={10}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.videoQuality.short.small()
|
||||
: $LL.menu.settings.videoQuality.long.small()}</option
|
||||
>
|
||||
<option value={5}
|
||||
>{isMobile()
|
||||
>{isMobile
|
||||
? $LL.menu.settings.videoQuality.short.minimum()
|
||||
: $LL.menu.settings.videoQuality.long.minimum()}</option
|
||||
>
|
||||
@@ -199,6 +201,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
div.settings-main {
|
||||
height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
@@ -236,7 +240,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
div.settings-main {
|
||||
section {
|
||||
padding: 0;
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import type { World } from "../../Connexion/World";
|
||||
|
||||
|
||||
let worlds = connectionManager.getWorlds();
|
||||
|
||||
let worlds : Promise<World[]> = connectionManager.getWorlds();
|
||||
|
||||
function worldRoomId(world: World) {
|
||||
return world.roomId;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
max-width: 80vw;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
z-index: 500;
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
max-width: 80vw;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
z-index: 450;
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { srcObject } from "./Video/utils";
|
||||
import LL from "../i18n/i18n-svelte";
|
||||
|
||||
@@ -23,16 +23,75 @@
|
||||
isSilent = value;
|
||||
});
|
||||
|
||||
let cameraContainer: HTMLDivElement;
|
||||
|
||||
onMount(() => {
|
||||
cameraContainer.addEventListener("transitionend", () => {
|
||||
if (cameraContainer.classList.contains("hide")) {
|
||||
cameraContainer.style.visibility = "hidden";
|
||||
}
|
||||
});
|
||||
|
||||
cameraContainer.addEventListener("transitionstart", () => {
|
||||
if (!cameraContainer.classList.contains("hide")) {
|
||||
cameraContainer.style.visibility = "visible";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(unsubscribeIsSilent);
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="video-container nes-container is-rounded is-dark div-myCamVideo"
|
||||
class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline />
|
||||
<SoundMeterWidget {stream} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="nes-container is-dark is-silent" class:hide={isSilent}>{$LL.camera.my.silentZone()}</div>
|
||||
<div
|
||||
class="nes-container is-rounded my-cam-video-container"
|
||||
class:hide={($localStreamStore.type !== "success" || !$obtainedMediaConstraintStore.video) && !isSilent}
|
||||
bind:this={cameraContainer}
|
||||
>
|
||||
{#if isSilent}
|
||||
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
|
||||
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
|
||||
<SoundMeterWidget {stream} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../style/breakpoints.scss";
|
||||
|
||||
.my-cam-video-container {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
bottom: 30px;
|
||||
max-height: 20%;
|
||||
transition: transform 1000ms;
|
||||
padding: 0;
|
||||
background-color: rgba(#000000, 0.6);
|
||||
background-clip: content-box;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
z-index: 250;
|
||||
|
||||
&.nes-container.is-rounded {
|
||||
border-image-outset: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.my-cam-video-container.hide {
|
||||
transform: translateX(200%);
|
||||
}
|
||||
|
||||
.my-cam-video {
|
||||
background-color: #00000099;
|
||||
max-height: 20vh;
|
||||
max-width: max(25vw, 150px);
|
||||
width: 100%;
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.is-silent {
|
||||
font-size: 2em;
|
||||
color: white;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -108,12 +108,16 @@
|
||||
pointer-events: auto;
|
||||
background-color: #333333;
|
||||
color: whitesmoke;
|
||||
|
||||
position: relative;
|
||||
z-index: 650;
|
||||
position: absolute;
|
||||
height: 70vh;
|
||||
width: 50vw;
|
||||
top: 10vh;
|
||||
margin: auto;
|
||||
top: 4%;
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
section.report-menu-title {
|
||||
display: grid;
|
||||
@@ -137,13 +141,4 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
div.report-menu-main {
|
||||
top: 21vh;
|
||||
height: 60vh;
|
||||
width: 100vw;
|
||||
font-size: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
form.selectCompanionScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
@@ -85,7 +87,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
form.selectCompanionScene button.selectCharacterButtonLeft {
|
||||
left: 5vw;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { fly, fade } from "svelte/transition";
|
||||
import { onMount } from "svelte";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
@@ -13,6 +14,8 @@
|
||||
|
||||
onMount(() => {
|
||||
timeToRead();
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
gameScene.playSound("audio-report-message");
|
||||
});
|
||||
|
||||
function timeToRead() {
|
||||
@@ -53,18 +56,19 @@
|
||||
on:click|preventDefault={closeBanMessage}>{nameButton}</button
|
||||
>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<audio id="report-message" autoplay>
|
||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3" />
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.main-ban-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
top: 15vh;
|
||||
position: absolute;
|
||||
top: 4%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
z-index: 850;
|
||||
|
||||
height: 70vh;
|
||||
width: 60vw;
|
||||
|
||||
@@ -11,3 +11,9 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.main-ban-message-container {
|
||||
z-index: 800;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,14 +42,17 @@
|
||||
div.main-text-message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
|
||||
max-height: 25vh;
|
||||
width: 80vw;
|
||||
max-height: 25%;
|
||||
width: 60%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 0;
|
||||
top: 6%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-bottom: 0;
|
||||
z-index: 240;
|
||||
|
||||
pointer-events: auto;
|
||||
background-color: #333333;
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.main-text-message-container {
|
||||
.main-text-message-container {
|
||||
padding-top: 16px;
|
||||
z-index: 800;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
background-color: black;
|
||||
border-radius: 30px 0 0 30px;
|
||||
display: inline-flex;
|
||||
z-index: 750;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -25,11 +25,17 @@
|
||||
<style lang="scss">
|
||||
div.error-div {
|
||||
pointer-events: auto;
|
||||
margin-top: 10vh;
|
||||
margin-top: 4%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
width: max-content;
|
||||
max-width: 80vw;
|
||||
z-index: 230;
|
||||
height: auto !important;
|
||||
background-clip: padding-box;
|
||||
|
||||
.button-bar {
|
||||
text-align: center;
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
<script lang="typescript">
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
import { srcObject } from "./utils";
|
||||
|
||||
export let clickable = false;
|
||||
|
||||
export let peer: ScreenSharingLocalMedia;
|
||||
let stream = peer.stream;
|
||||
export let cssClass: string | undefined;
|
||||
let embedScreen: EmbedScreen;
|
||||
|
||||
if (stream) {
|
||||
embedScreen = {
|
||||
type: "streamable",
|
||||
embed: peer as unknown as Streamable,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="video-container {cssClass ? cssClass : ''}" class:hide={!stream}>
|
||||
{#if stream}
|
||||
<video use:srcObject={stream} autoplay muted playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
||||
<video
|
||||
use:srcObject={stream}
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -7,14 +7,115 @@
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
|
||||
export let streamable: Streamable;
|
||||
export let isHightlighted = false;
|
||||
export let isClickable = false;
|
||||
export let mozaicSolo = false;
|
||||
export let mozaicFullWidth = false;
|
||||
export let mozaicQuarter = false;
|
||||
</script>
|
||||
|
||||
<div class="media-container">
|
||||
{#if streamable instanceof VideoPeer}
|
||||
<VideoMediaBox peer={streamable} />
|
||||
{:else if streamable instanceof ScreenSharingPeer}
|
||||
<ScreenSharingMediaBox peer={streamable} />
|
||||
{:else}
|
||||
<LocalStreamMediaBox peer={streamable} cssClass="" />
|
||||
{/if}
|
||||
<div
|
||||
class="media-container nes-container is-rounded {isHightlighted ? 'hightlighted' : ''}"
|
||||
class:clickable={isClickable}
|
||||
class:mozaic-solo={mozaicSolo}
|
||||
class:mozaic-full-width={mozaicFullWidth}
|
||||
class:mozaic-quarter={mozaicQuarter}
|
||||
>
|
||||
<div>
|
||||
{#if streamable instanceof VideoPeer}
|
||||
<VideoMediaBox peer={streamable} clickable={isClickable} />
|
||||
{:else if streamable instanceof ScreenSharingPeer}
|
||||
<ScreenSharingMediaBox peer={streamable} clickable={isClickable} />
|
||||
{:else}
|
||||
<LocalStreamMediaBox peer={streamable} clickable={isClickable} cssClass="" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
.media-container {
|
||||
display: flex;
|
||||
margin-top: 4%;
|
||||
margin-bottom: 4%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s,
|
||||
max-width 0.2s;
|
||||
pointer-events: auto;
|
||||
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
max-width: 85%;
|
||||
|
||||
&:hover {
|
||||
margin-top: 2%;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
|
||||
&.hightlighted {
|
||||
margin-top: 0% !important;
|
||||
margin-bottom: 0% !important;
|
||||
margin-left: 0% !important;
|
||||
|
||||
max-height: 100% !important;
|
||||
max-width: 96% !important;
|
||||
|
||||
&:hover {
|
||||
margin-top: 0% !important;
|
||||
margin-bottom: 0% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.mozaic-solo {
|
||||
max-height: inherit !important;
|
||||
width: 90% !important;
|
||||
}
|
||||
|
||||
&.mozaic-full-width {
|
||||
width: 95%;
|
||||
max-width: 95%;
|
||||
margin-left: 3%;
|
||||
margin-right: 3%;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
max-height: 95%;
|
||||
|
||||
&:hover {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.mozaic-quarter {
|
||||
width: 95%;
|
||||
max-width: 95%;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
max-height: 95%;
|
||||
|
||||
&:hover {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.nes-container.is-rounded {
|
||||
border-image-outset: 1;
|
||||
}
|
||||
|
||||
> div {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-only(md) {
|
||||
.media-container {
|
||||
margin-top: 10%;
|
||||
margin-bottom: 10%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||
import { afterUpdate } from "svelte";
|
||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
||||
import MediaBox from "./MediaBox.svelte";
|
||||
|
||||
afterUpdate(() => {
|
||||
biggestAvailableAreaStore.recompute();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="main-section">
|
||||
{#if $videoFocusStore}
|
||||
{#key $videoFocusStore.uniqueId}
|
||||
<MediaBox streamable={$videoFocusStore} />
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
<aside class="sidebar">
|
||||
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||
{#if peer !== $videoFocusStore}
|
||||
<MediaBox streamable={peer} />
|
||||
{/if}
|
||||
{/each}
|
||||
</aside>
|
||||
@@ -1,12 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
|
||||
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||
import { getColorByString, srcObject } from "./utils";
|
||||
|
||||
export let clickable = false;
|
||||
|
||||
export let peer: ScreenSharingPeer;
|
||||
let streamStore = peer.streamStore;
|
||||
let name = peer.userName;
|
||||
let statusStore = peer.statusStore;
|
||||
|
||||
let embedScreen: EmbedScreen;
|
||||
|
||||
if (peer) {
|
||||
embedScreen = {
|
||||
type: "streamable",
|
||||
embed: peer as unknown as Streamable,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="video-container">
|
||||
@@ -16,11 +30,17 @@
|
||||
{#if $statusStore === "error"}
|
||||
<div class="rtc-error" />
|
||||
{/if}
|
||||
{#if $streamStore === null}
|
||||
<i style="background-color: {getColorByString(name)};">{name}</i>
|
||||
{:else}
|
||||
{#if $streamStore !== null}
|
||||
<i class="container">
|
||||
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||
</i>
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
||||
<video
|
||||
use:srcObject={$streamStore}
|
||||
autoplay
|
||||
playsinline
|
||||
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -29,5 +49,10 @@
|
||||
video {
|
||||
width: 100%;
|
||||
}
|
||||
i {
|
||||
span {
|
||||
padding: 2px 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
import microphoneCloseImg from "../images/microphone-close.svg";
|
||||
import reportImg from "./images/report.svg";
|
||||
import blockSignImg from "./images/blockSign.svg";
|
||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||
import { getColorByString, srcObject } from "./utils";
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
|
||||
import Woka from "../Woka/Woka.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { isMediaBreakpointOnly } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
export let clickable = false;
|
||||
|
||||
export let peer: VideoPeer;
|
||||
let streamStore = peer.streamStore;
|
||||
@@ -19,9 +25,37 @@
|
||||
function openReport(peer: VideoPeer): void {
|
||||
showReportScreenStore.set({ userId: peer.userId, userName: peer.userName });
|
||||
}
|
||||
|
||||
let embedScreen: EmbedScreen;
|
||||
let videoContainer: HTMLDivElement;
|
||||
let minimized = isMediaBreakpointOnly("md");
|
||||
|
||||
if (peer) {
|
||||
embedScreen = {
|
||||
type: "streamable",
|
||||
embed: peer as unknown as Streamable,
|
||||
};
|
||||
}
|
||||
|
||||
function noDrag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
minimized = isMediaBreakpointOnly("md");
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
resizeObserver.observe(videoContainer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="video-container nes-container is-rounded is-dark">
|
||||
<div
|
||||
class="video-container"
|
||||
class:no-clikable={!clickable}
|
||||
bind:this={videoContainer}
|
||||
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||
>
|
||||
{#if $statusStore === "connecting"}
|
||||
<div class="connecting-spinner" />
|
||||
{/if}
|
||||
@@ -29,43 +63,46 @@
|
||||
<div class="rtc-error" />
|
||||
{/if}
|
||||
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
||||
<i
|
||||
class="container {!$constraintStore || $constraintStore.video === false ? '' : 'minimized'}"
|
||||
style="background-color: {getColorByString(name)};"
|
||||
>
|
||||
<span>{peer.userName}</span>
|
||||
<div class="woka-icon"><Woka userId={peer.userId} placeholderSrc={""} /></div>
|
||||
<i class="container">
|
||||
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||
</i>
|
||||
<div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}">
|
||||
<Woka userId={peer.userId} placeholderSrc={""} />
|
||||
</div>
|
||||
<!-- {/if} -->
|
||||
{#if $constraintStore && $constraintStore.audio === false}
|
||||
<img src={microphoneCloseImg} class="active" alt="Muted" />
|
||||
<img
|
||||
src={microphoneCloseImg}
|
||||
class="active noselect"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
alt="Muted"
|
||||
/>
|
||||
{/if}
|
||||
<button class="report nes-button is-dark" on:click={() => openReport(peer)}>
|
||||
<img alt="Report this user" src={reportImg} />
|
||||
<span>Report/Block</span>
|
||||
<img alt="Report this user" draggable="false" on:dragstart|preventDefault={noDrag} src={reportImg} />
|
||||
<span class="noselect">Report/Block</span>
|
||||
</button>
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
||||
<img src={blockSignImg} class="block-logo" alt="Block" />
|
||||
<video
|
||||
class:no-video={!$constraintStore || $constraintStore.video === false}
|
||||
use:srcObject={$streamStore}
|
||||
autoplay
|
||||
playsinline
|
||||
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||
/>
|
||||
<img src={blockSignImg} draggable="false" on:dragstart|preventDefault={noDrag} class="block-logo" alt="Block" />
|
||||
{#if $constraintStore && $constraintStore.audio !== false}
|
||||
<SoundMeterWidget stream={$streamStore} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.minimized {
|
||||
left: auto;
|
||||
transform: scale(0.5);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.woka-icon {
|
||||
margin-right: 3px;
|
||||
video.no-video {
|
||||
visibility: collapse;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
||||
import { layoutModeStore } from "../../Stores/StreamableCollectionStore";
|
||||
import PresentationLayout from "./PresentationLayout.svelte";
|
||||
import ChatLayout from "./ChatLayout.svelte";
|
||||
// import {LayoutMode} from "../../WebRtc/LayoutManager";
|
||||
// import {layoutModeStore} from "../../Stores/StreamableCollectionStore";
|
||||
// import PresentationLayout from "./PresentationLayout.svelte";
|
||||
// import ChatLayout from "./ChatLayout.svelte";
|
||||
</script>
|
||||
|
||||
<div class="video-overlay">
|
||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
||||
<!-- {#if $layoutModeStore === LayoutMode.Presentation }
|
||||
<PresentationLayout />
|
||||
{:else}
|
||||
<ChatLayout />
|
||||
{/if}
|
||||
{/if} -->
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
height: 120px;
|
||||
margin: auto;
|
||||
animation: spin 2s linear infinite;
|
||||
z-index: 350;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
||||
@@ -27,18 +27,21 @@
|
||||
<style lang="scss">
|
||||
main.warningMain {
|
||||
pointer-events: auto;
|
||||
width: 100vw;
|
||||
width: 80%;
|
||||
background-color: #f9e81e;
|
||||
color: #14304c;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
top: 4%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-family: Lato;
|
||||
min-width: 300px;
|
||||
opacity: 0.9;
|
||||
z-index: 2;
|
||||
|
||||
z-index: 700;
|
||||
h2 {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,21 @@
|
||||
src = source ?? placeholderSrc;
|
||||
});
|
||||
|
||||
function noDrag() {
|
||||
return false;
|
||||
}
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
</script>
|
||||
|
||||
<img {src} alt="" class="nes-pointer" style="--theme-width: {width}; --theme-height: {height}" />
|
||||
<img
|
||||
{src}
|
||||
alt=""
|
||||
class="nes-pointer noselect"
|
||||
style="--theme-width: {width}; --theme-height: {height}"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
/>
|
||||
|
||||
<style>
|
||||
img {
|
||||
|
||||
@@ -49,6 +49,8 @@
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
form.selectCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
@@ -91,7 +93,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
@include media-breakpoint-up(md) {
|
||||
form.selectCharacterScene button.selectCharacterButtonLeft {
|
||||
left: 5vw;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { showLimitRoomModalStore } from "../Stores/ModalStore";
|
||||
import { locales } from "../i18n/i18n-util";
|
||||
import type { Locales } from "../i18n/i18n-types";
|
||||
import { setCurrentLocale } from "../i18n/locales";
|
||||
import type { World } from "./World";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
@@ -187,14 +188,14 @@ class ConnectionManager {
|
||||
window.location.hash;
|
||||
}
|
||||
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
//use href to keep # value
|
||||
await localUserStore.setLastRoomUrl(new URL(roomPath).href);
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
//use href to keep # value
|
||||
await localUserStore.setLastRoomUrl(this._currentRoom.href);
|
||||
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||
await this.anonymousLogin();
|
||||
@@ -384,7 +385,7 @@ class ConnectionManager {
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
||||
async getWorlds() {
|
||||
async getWorlds() : Promise<World[]> {
|
||||
const token = localUserStore.getAuthToken();
|
||||
if (!token) {
|
||||
throw new Error("No token provided");
|
||||
|
||||
@@ -1,33 +1,46 @@
|
||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||
const START_ROOM_URL: string =
|
||||
process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
||||
const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost";
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||
const TURN_USER: string = process.env.TURN_USER || "";
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || "";
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
declare global {
|
||||
interface Window {
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
const getEnv = (key: string): string | undefined => {
|
||||
if (global.window?.env) {
|
||||
return global.window.env[key];
|
||||
}
|
||||
if (global.process?.env) {
|
||||
return global.process.env[key];
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true";
|
||||
const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||
const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re";
|
||||
const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost";
|
||||
const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost";
|
||||
const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = getEnv("TURN_SERVER") || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true";
|
||||
const TURN_USER: string = getEnv("TURN_USER") || "";
|
||||
const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || "";
|
||||
const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL");
|
||||
const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true";
|
||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
|
||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
||||
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
||||
const FALLBACK_LOCALE = process.env.FALLBACK_LOCALE || undefined;
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8;
|
||||
export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true";
|
||||
export const NODE_ENV = getEnv("NODE_ENV") || "development";
|
||||
export const CONTACT_URL = getEnv("CONTACT_URL") || undefined;
|
||||
export const PROFILE_URL = getEnv("PROFILE_URL") || undefined;
|
||||
export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || "";
|
||||
export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined;
|
||||
export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true";
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER");
|
||||
const FALLBACK_LOCALE = getEnv("FALLBACK_LOCALE") || undefined;
|
||||
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
|
||||
@@ -9,4 +9,7 @@ export interface UserInputHandlerInterface {
|
||||
handlePointerUpEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void;
|
||||
handlePointerDownEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void;
|
||||
handleSpaceKeyUpEvent: (event: Event) => Event;
|
||||
|
||||
addSpaceEventListener: (callback: Function) => void;
|
||||
removeSpaceEventListner: (callback: Function) => void;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Anima
|
||||
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||
import { Writable, writable } from "svelte/store";
|
||||
import type { PictureStore } from "../../Stores/PictureStore";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export interface CompanionStatus {
|
||||
x: number;
|
||||
@@ -25,8 +26,9 @@ export class Companion extends Container {
|
||||
private direction: PlayerAnimationDirections;
|
||||
private animationType: PlayerAnimationTypes;
|
||||
private readonly _pictureStore: Writable<string | undefined>;
|
||||
private texturePromise: CancelablePromise<string | void> | undefined;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: CancelablePromise<string>) {
|
||||
super(scene, x + 14, y + 4);
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
@@ -41,7 +43,7 @@ export class Companion extends Container {
|
||||
this.companionName = name;
|
||||
this._pictureStore = writable(undefined);
|
||||
|
||||
texturePromise
|
||||
this.texturePromise = texturePromise
|
||||
.then((resource) => {
|
||||
this.addResource(resource);
|
||||
this.invisible = false;
|
||||
@@ -234,6 +236,7 @@ export class Companion extends Container {
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.texturePromise?.cancel();
|
||||
for (const sprite of this.sprites.values()) {
|
||||
if (this.scene) {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
|
||||
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
||||
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
||||
@@ -9,8 +10,12 @@ export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourc
|
||||
return COMPANION_RESOURCES;
|
||||
};
|
||||
|
||||
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): CancelablePromise<string> => {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
cancel(() => {
|
||||
return;
|
||||
});
|
||||
|
||||
const resource = COMPANION_RESOURCES.find((item) => item.name === name);
|
||||
|
||||
if (typeof resource === "undefined") {
|
||||
|
||||
@@ -63,8 +63,6 @@ export class SoundMeter {
|
||||
//this.slow = 0.95 * that.slow + 0.05 * that.instant;
|
||||
//this.clip = clipcount / input.length;
|
||||
|
||||
//console.log('instant', this.instant, 'clip', this.clip);
|
||||
|
||||
return this.instant;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||
import type { PictureStore } from "../../Stores/PictureStore";
|
||||
import { Unsubscriber, Writable, writable } from "svelte/store";
|
||||
import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||
import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
@@ -28,14 +30,15 @@ interface AnimationData {
|
||||
|
||||
const interactiveRadius = 35;
|
||||
|
||||
export abstract class Character extends Container {
|
||||
export abstract class Character extends Container implements OutlineableInterface {
|
||||
private bubble: SpeechBubble | null = null;
|
||||
private readonly playerName: Text;
|
||||
public PlayerValue: string;
|
||||
private readonly playerNameText: Text;
|
||||
public playerName: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
//private teleportation: Sprite;
|
||||
private invisible: boolean;
|
||||
private clickable: boolean;
|
||||
public companion?: Companion;
|
||||
private emote: Phaser.GameObjects.Text | null = null;
|
||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||
@@ -43,30 +46,32 @@ export abstract class Character extends Container {
|
||||
private readonly _pictureStore: Writable<string | undefined>;
|
||||
private readonly outlineColorStore = createColorStore();
|
||||
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
||||
private texturePromise: CancelablePromise<string[] | void> | undefined;
|
||||
|
||||
constructor(
|
||||
scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
name: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
frame: string | number,
|
||||
isClickable: boolean,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
companionTexturePromise?: CancelablePromise<string>
|
||||
) {
|
||||
super(scene, x, y /*, texture, frame*/);
|
||||
this.scene = scene;
|
||||
this.PlayerValue = name;
|
||||
this.playerName = name;
|
||||
this.invisible = true;
|
||||
this.clickable = false;
|
||||
|
||||
this.sprites = new Map<string, Sprite>();
|
||||
this._pictureStore = writable(undefined);
|
||||
|
||||
//textures are inside a Promise in case they need to be lazyloaded before use.
|
||||
texturesPromise
|
||||
this.texturePromise = texturesPromise
|
||||
.then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
@@ -81,9 +86,12 @@ export abstract class Character extends Container {
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.texturePromise = undefined;
|
||||
});
|
||||
|
||||
this.playerName = new Text(scene, 0, playerNameY, name, {
|
||||
this.playerNameText = new Text(scene, 0, playerNameY, name, {
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
@@ -94,30 +102,17 @@ export abstract class Character extends Container {
|
||||
fontSize: 35,
|
||||
},
|
||||
});
|
||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerName);
|
||||
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerNameText);
|
||||
|
||||
if (isClickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
|
||||
this.on("pointerover", () => {
|
||||
this.outlineColorStore.pointerOver();
|
||||
});
|
||||
this.on("pointerout", () => {
|
||||
this.outlineColorStore.pointerOut();
|
||||
});
|
||||
}
|
||||
this.setClickable(isClickable);
|
||||
|
||||
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
|
||||
if (color === undefined) {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||
} else {
|
||||
this.getOutlinePlugin()?.remove(this.playerName);
|
||||
this.getOutlinePlugin()?.add(this.playerName, {
|
||||
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||
this.getOutlinePlugin()?.add(this.playerNameText, {
|
||||
thickness: 2,
|
||||
outlineColor: color,
|
||||
});
|
||||
@@ -140,6 +135,55 @@ export abstract class Character extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
public setClickable(clickable: boolean = true): void {
|
||||
if (this.clickable === clickable) {
|
||||
return;
|
||||
}
|
||||
this.clickable = clickable;
|
||||
if (clickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(8, 8, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.disableInteractive();
|
||||
}
|
||||
|
||||
public isClickable() {
|
||||
return this.clickable;
|
||||
}
|
||||
|
||||
public getPosition(): { x: number; y: number } {
|
||||
return { x: this.x, y: this.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns position based on where player is currently facing
|
||||
* @param shift How far from player should the point of interest be.
|
||||
*/
|
||||
public getDirectionalActivationPosition(shift: number): { x: number; y: number } {
|
||||
switch (this.lastDirection) {
|
||||
case PlayerAnimationDirections.Down: {
|
||||
return { x: this.x, y: this.y + shift };
|
||||
}
|
||||
case PlayerAnimationDirections.Left: {
|
||||
return { x: this.x - shift, y: this.y };
|
||||
}
|
||||
case PlayerAnimationDirections.Right: {
|
||||
return { x: this.x + shift, y: this.y };
|
||||
}
|
||||
case PlayerAnimationDirections.Up: {
|
||||
return { x: this.x, y: this.y - shift };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getObjectToOutline(): Phaser.GameObjects.GameObject {
|
||||
return this.playerNameText;
|
||||
}
|
||||
|
||||
private async getSnapshot(): Promise<string> {
|
||||
const sprites = Array.from(this.sprites.values()).map((sprite) => {
|
||||
return { sprite, frame: 1 };
|
||||
@@ -156,7 +200,7 @@ export abstract class Character extends Container {
|
||||
});
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
||||
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
|
||||
if (typeof texturePromise !== "undefined") {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
}
|
||||
@@ -326,6 +370,7 @@ export abstract class Character extends Container {
|
||||
this.scene.sys.updateList.remove(sprite);
|
||||
}
|
||||
}
|
||||
this.texturePromise?.cancel();
|
||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||
this.outlineColorStoreUnsubscribe();
|
||||
super.destroy();
|
||||
@@ -405,18 +450,42 @@ export abstract class Character extends Container {
|
||||
private destroyEmote() {
|
||||
this.emote?.destroy();
|
||||
this.emote = null;
|
||||
this.playerName.setVisible(true);
|
||||
this.playerNameText.setVisible(true);
|
||||
}
|
||||
|
||||
public get pictureStore(): PictureStore {
|
||||
return this._pictureStore;
|
||||
}
|
||||
|
||||
public setOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setColor(color);
|
||||
public setFollowOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setFollowColor(color);
|
||||
}
|
||||
|
||||
public removeOutlineColor(): void {
|
||||
this.outlineColorStore.removeColor();
|
||||
public removeFollowOutlineColor(): void {
|
||||
this.outlineColorStore.removeFollowColor();
|
||||
}
|
||||
|
||||
public setApiOutlineColor(color: number): void {
|
||||
this.outlineColorStore.setApiColor(color);
|
||||
}
|
||||
|
||||
public removeApiOutlineColor(): void {
|
||||
this.outlineColorStore.removeApiColor();
|
||||
}
|
||||
|
||||
public pointerOverOutline(color: number): void {
|
||||
this.outlineColorStore.pointerOver(color);
|
||||
}
|
||||
|
||||
public pointerOutOutline(): void {
|
||||
this.outlineColorStore.pointerOut();
|
||||
}
|
||||
|
||||
public characterCloseByOutline(color: number): void {
|
||||
this.outlineColorStore.characterCloseBy(color);
|
||||
}
|
||||
|
||||
public characterFarAwayOutline(): void {
|
||||
this.outlineColorStore.characterFarAway();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
|
||||
export interface FrameConfig {
|
||||
frameWidth: number;
|
||||
@@ -30,7 +31,7 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
||||
export const loadCustomTexture = (
|
||||
loaderPlugin: LoaderPlugin,
|
||||
texture: CharacterTexture
|
||||
): Promise<BodyResourceDescriptionInterface> => {
|
||||
): CancelablePromise<BodyResourceDescriptionInterface> => {
|
||||
const name = "customCharacterTexture" + texture.id;
|
||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||
@@ -42,8 +43,8 @@ export const loadCustomTexture = (
|
||||
export const lazyLoadPlayerCharacterTextures = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
||||
): Promise<string[]> => {
|
||||
const promisesList: Promise<unknown>[] = [];
|
||||
): CancelablePromise<string[]> => {
|
||||
const promisesList: CancelablePromise<unknown>[] = [];
|
||||
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
||||
try {
|
||||
//TODO refactor
|
||||
@@ -60,12 +61,12 @@ export const lazyLoadPlayerCharacterTextures = (
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
returnPromise = Promise.all(promisesList).then(() => texturekeys);
|
||||
returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
|
||||
} else {
|
||||
returnPromise = Promise.resolve(texturekeys);
|
||||
returnPromise = CancelablePromise.resolve(texturekeys);
|
||||
}
|
||||
|
||||
//If the loading fail, we render the default model instead.
|
||||
@@ -98,10 +99,17 @@ export const createLoadingPromise = (
|
||||
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||
frameConfig: FrameConfig
|
||||
) => {
|
||||
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
|
||||
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
|
||||
cancel(() => {
|
||||
loadPlugin.off("loaderror");
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
|
||||
return;
|
||||
});
|
||||
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||
const errorCallback = (file: { src: string }) => {
|
||||
if (file.src !== playerResourceDescriptor.img) return;
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { Character } from "../Entity/Character";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
||||
import { Character } from "../Entity/Character";
|
||||
import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
*/
|
||||
export class RemotePlayer extends Character {
|
||||
userId: number;
|
||||
export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
public userId: number;
|
||||
public readonly activationRadius: number;
|
||||
|
||||
private registeredActions: { actionName: string; callback: Function }[];
|
||||
private visitCardUrl: string | null;
|
||||
private isActionsMenuInitialized: boolean = false;
|
||||
private actionsMenuStoreUnsubscriber: Unsubscriber;
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
@@ -17,39 +26,31 @@ export class RemotePlayer extends Character {
|
||||
x: number,
|
||||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
visitCardUrl: string | null,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
companionTexturePromise?: CancelablePromise<string>,
|
||||
activationRadius?: number
|
||||
) {
|
||||
super(
|
||||
Scene,
|
||||
x,
|
||||
y,
|
||||
texturesPromise,
|
||||
name,
|
||||
direction,
|
||||
moving,
|
||||
1,
|
||||
!!visitCardUrl,
|
||||
companion,
|
||||
companionTexturePromise
|
||||
);
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
this.visitCardUrl = visitCardUrl;
|
||||
|
||||
this.on("pointerdown", (event: Phaser.Input.Pointer) => {
|
||||
if (event.downElement.nodeName === "CANVAS") {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
}
|
||||
this.registeredActions = [];
|
||||
this.registerDefaultActionsMenuActions();
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
this.activationRadius = activationRadius ?? 96;
|
||||
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
|
||||
this.isActionsMenuInitialized = value ? true : false;
|
||||
});
|
||||
|
||||
this.bindEventHandlers();
|
||||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
public updatePosition(position: PointInterface): void {
|
||||
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
||||
this.setX(position.x);
|
||||
this.setY(position.y);
|
||||
@@ -60,4 +61,66 @@ export class RemotePlayer extends Character {
|
||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||
}
|
||||
}
|
||||
|
||||
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
|
||||
this.registeredActions.push(action);
|
||||
this.updateIsClickable();
|
||||
}
|
||||
|
||||
public unregisterActionsMenuAction(actionName: string) {
|
||||
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
|
||||
if (index !== -1) {
|
||||
this.registeredActions.splice(index, 1);
|
||||
}
|
||||
this.updateIsClickable();
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
this.toggleActionsMenu();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.actionsMenuStoreUnsubscriber();
|
||||
actionsMenuStore.clear();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public isActivatable(): boolean {
|
||||
return this.isClickable();
|
||||
}
|
||||
|
||||
private updateIsClickable(): void {
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
}
|
||||
|
||||
private toggleActionsMenu(): void {
|
||||
if (this.isActionsMenuInitialized) {
|
||||
actionsMenuStore.clear();
|
||||
return;
|
||||
}
|
||||
actionsMenuStore.initialize(this.playerName);
|
||||
for (const action of this.registeredActions) {
|
||||
actionsMenuStore.addAction(action.actionName, action.callback);
|
||||
}
|
||||
}
|
||||
|
||||
private registerDefaultActionsMenuActions(): void {
|
||||
if (this.visitCardUrl) {
|
||||
this.registeredActions.push({
|
||||
actionName: "Visiting Card",
|
||||
callback: () => {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
actionsMenuStore.clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private bindEventHandlers(): void {
|
||||
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
|
||||
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
|
||||
this.toggleActionsMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ActivatableInterface {
|
||||
readonly activationRadius: number;
|
||||
isActivatable: () => boolean;
|
||||
activate: () => void;
|
||||
getPosition: () => { x: number; y: number };
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { isOutlineable } from "../../Utils/CustomTypeGuards";
|
||||
import { MathUtils } from "../../Utils/MathUtils";
|
||||
import type { Player } from "../Player/Player";
|
||||
import type { ActivatableInterface } from "./ActivatableInterface";
|
||||
|
||||
export class ActivatablesManager {
|
||||
// The item that can be selected by pressing the space key.
|
||||
private selectedActivatableObjectByDistance?: ActivatableInterface;
|
||||
private selectedActivatableObjectByPointer?: ActivatableInterface;
|
||||
private activatableObjectsDistances: Map<ActivatableInterface, number> = new Map<ActivatableInterface, number>();
|
||||
|
||||
private currentPlayer: Player;
|
||||
|
||||
private canSelectByDistance: boolean = true;
|
||||
|
||||
private readonly outlineColor = 0xffff00;
|
||||
private readonly directionalActivationPositionShift = 50;
|
||||
|
||||
constructor(currentPlayer: Player) {
|
||||
this.currentPlayer = currentPlayer;
|
||||
}
|
||||
|
||||
public handlePointerOverActivatableObject(object: ActivatableInterface): void {
|
||||
if (this.selectedActivatableObjectByPointer === object) {
|
||||
return;
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByPointer = object;
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOverOutline(this.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
public handlePointerOutActivatableObject(): void {
|
||||
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByPointer = undefined;
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
public getSelectedActivatableObject(): ActivatableInterface | undefined {
|
||||
return this.selectedActivatableObjectByPointer ?? this.selectedActivatableObjectByDistance;
|
||||
}
|
||||
|
||||
public deduceSelectedActivatableObjectByDistance(): void {
|
||||
if (!this.canSelectByDistance) {
|
||||
return;
|
||||
}
|
||||
const newNearestObject = this.findNearestActivatableObject();
|
||||
if (this.selectedActivatableObjectByDistance === newNearestObject) {
|
||||
return;
|
||||
}
|
||||
// update value but do not change the outline
|
||||
if (this.selectedActivatableObjectByPointer) {
|
||||
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||
return;
|
||||
}
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void {
|
||||
const currentPlayerPos = this.currentPlayer.getDirectionalActivationPosition(
|
||||
this.directionalActivationPositionShift
|
||||
);
|
||||
for (const object of objects) {
|
||||
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
|
||||
this.activatableObjectsDistances.set(object, distance);
|
||||
}
|
||||
}
|
||||
|
||||
public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void {
|
||||
this.activatableObjectsDistances.set(
|
||||
object,
|
||||
MathUtils.distanceBetween(
|
||||
this.currentPlayer.getDirectionalActivationPosition(this.directionalActivationPositionShift),
|
||||
object.getPosition()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public disableSelectingByDistance(): void {
|
||||
this.canSelectByDistance = false;
|
||||
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||
}
|
||||
this.selectedActivatableObjectByDistance = undefined;
|
||||
}
|
||||
|
||||
public enableSelectingByDistance(): void {
|
||||
this.canSelectByDistance = true;
|
||||
}
|
||||
|
||||
private findNearestActivatableObject(): ActivatableInterface | undefined {
|
||||
let shortestDistance: number = Infinity;
|
||||
let closestObject: ActivatableInterface | undefined = undefined;
|
||||
|
||||
for (const [object, distance] of this.activatableObjectsDistances.entries()) {
|
||||
if (object.isActivatable() && object.activationRadius > distance && shortestDistance > distance) {
|
||||
shortestDistance = distance;
|
||||
closestObject = object;
|
||||
}
|
||||
}
|
||||
return closestObject;
|
||||
}
|
||||
|
||||
public isSelectingByDistanceEnabled(): boolean {
|
||||
return this.canSelectByDistance;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ export class Game extends Phaser.Game {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public step(time: number, delta: number) {
|
||||
|
||||
@@ -85,6 +85,7 @@ export class GameMap {
|
||||
phaserMap
|
||||
.createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32)
|
||||
.setDepth(depth)
|
||||
.setScrollFactor(layer.parallaxx ?? 1, layer.parallaxy ?? 1)
|
||||
.setAlpha(layer.opacity)
|
||||
.setVisible(layer.visible)
|
||||
.setSize(layer.width, layer.height)
|
||||
@@ -120,7 +121,7 @@ export class GameMap {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getCollisionsGrid(): number[][] {
|
||||
public getCollisionGrid(): number[][] {
|
||||
const grid: number[][] = [];
|
||||
for (let y = 0; y < this.map.height; y += 1) {
|
||||
const row: number[] = [];
|
||||
@@ -335,12 +336,19 @@ export class GameMap {
|
||||
throw new Error("No possible position found");
|
||||
}
|
||||
|
||||
public getObjectWithName(name: string): ITiledMapObject | undefined {
|
||||
return this.tiledObjects.find((object) => object.name === name);
|
||||
}
|
||||
|
||||
private getLayersByKey(key: number): Array<ITiledMapLayer> {
|
||||
return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0);
|
||||
}
|
||||
|
||||
private isCollidingAt(x: number, y: number): boolean {
|
||||
for (const layer of this.phaserLayers) {
|
||||
if (!layer.visible) {
|
||||
continue;
|
||||
}
|
||||
if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
import type { GameScene } from "./GameScene";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import { scriptUtils } from "../../Api/ScriptUtils";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager, CoWebsiteState } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { get } from "svelte/store";
|
||||
import { ON_ACTION_TRIGGER_BUTTON, ON_ACTION_TRIGGER_DISABLE } from "../../WebRtc/LayoutManager";
|
||||
import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import type { ITiledMapLayer } from "../Map/ITiledMap";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import type { Subscription } from "rxjs";
|
||||
import { LL } from "../../i18n/i18n-svelte";
|
||||
|
||||
enum OpenCoWebsiteState {
|
||||
LOADING,
|
||||
OPENED,
|
||||
MUST_BE_CLOSE,
|
||||
TRIGGER,
|
||||
}
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
interface OpenCoWebsite {
|
||||
coWebsite: CoWebsite | undefined;
|
||||
state: OpenCoWebsiteState;
|
||||
actionId: string;
|
||||
coWebsite?: CoWebsite;
|
||||
}
|
||||
|
||||
export class GameMapPropertiesListener {
|
||||
private coWebsitesOpenByLayer = new Map<ITiledMapLayer, OpenCoWebsite>();
|
||||
private coWebsitesIframeListeners = new Map<ITiledMapLayer, Subscription>();
|
||||
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
|
||||
|
||||
constructor(private scene: GameScene, private gameMap: GameMap) {}
|
||||
|
||||
register() {
|
||||
// Website on new tab
|
||||
this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("openTab");
|
||||
@@ -43,7 +41,7 @@ export class GameMapPropertiesListener {
|
||||
if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = get(LL).message.openWebsiteTabTrigger();
|
||||
message = get(LL).trigger.newTab();
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "openTab",
|
||||
@@ -58,6 +56,129 @@ export class GameMapPropertiesListener {
|
||||
}
|
||||
});
|
||||
|
||||
// Jitsi room
|
||||
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
coWebsiteManager.getCoWebsites().forEach((coWebsite) => {
|
||||
if (coWebsite instanceof JitsiCoWebsite) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
|
||||
|
||||
this.scene.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
let domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
|
||||
}
|
||||
|
||||
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
|
||||
domain = `${location.protocol}//${domain}`;
|
||||
}
|
||||
|
||||
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
|
||||
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
|
||||
this.scene.initialiseJitsi(coWebsite, roomName, undefined);
|
||||
}
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
|
||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
||||
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = get(LL).trigger.jitsiRoom();
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.scene.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.scene
|
||||
.onMapExit(
|
||||
Room.getRoomPathFromExitSceneUrl(
|
||||
newValue as string,
|
||||
window.location.toString(),
|
||||
this.scene.MapUrlFile
|
||||
)
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.scene
|
||||
.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()))
|
||||
.catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.scene.connection?.setSilent(false);
|
||||
this.scene.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.scene.connection?.setSilent(true);
|
||||
this.scene.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
|
||||
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), volume, loop);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: Legacy functionnality replace by layer change
|
||||
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
|
||||
if (oldValue) {
|
||||
iframeListener.sendLeaveEvent(oldValue as string);
|
||||
}
|
||||
if (newValue) {
|
||||
iframeListener.sendEnterEvent(newValue as string);
|
||||
}
|
||||
});
|
||||
|
||||
// Open a new co-website by the property.
|
||||
this.gameMap.onEnterLayer((newLayers) => {
|
||||
const handler = () => {
|
||||
@@ -104,92 +225,75 @@ export class GameMapPropertiesListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionUuid = "openWebsite-" + (Math.random() + 1).toString(36).substring(7);
|
||||
const actionId = "openWebsite-" + (Math.random() + 1).toString(36).substring(7);
|
||||
|
||||
if (this.coWebsitesOpenByLayer.has(layer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
coWebsite: undefined,
|
||||
state: OpenCoWebsiteState.LOADING,
|
||||
});
|
||||
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager
|
||||
.loadCoWebsite(
|
||||
openWebsiteProperty as string,
|
||||
this.scene.MapUrlFile,
|
||||
allowApiProperty,
|
||||
websitePolicyProperty,
|
||||
websiteWidthProperty,
|
||||
websitePositionProperty
|
||||
)
|
||||
.then((coWebsite) => {
|
||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e));
|
||||
this.coWebsitesOpenByLayer.delete(layer);
|
||||
this.coWebsitesActionTriggerByLayer.delete(layer);
|
||||
} else {
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
coWebsite,
|
||||
state: OpenCoWebsiteState.OPENED,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
|
||||
layoutManagerActionStore.removeAction(actionUuid);
|
||||
const coWebsiteOpen: OpenCoWebsite = {
|
||||
actionId: actionId,
|
||||
};
|
||||
|
||||
const createWebsiteTrigger = () => {
|
||||
this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen);
|
||||
|
||||
const loadCoWebsiteFunction = (coWebsite: CoWebsite) => {
|
||||
coWebsiteManager.loadCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during loading a co-website: " + coWebsite.getUrl());
|
||||
});
|
||||
|
||||
layoutManagerActionStore.removeAction(actionId);
|
||||
};
|
||||
|
||||
const openCoWebsiteFunction = () => {
|
||||
const coWebsite = new SimpleCoWebsite(
|
||||
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
|
||||
allowApiProperty,
|
||||
websitePolicyProperty,
|
||||
websiteWidthProperty,
|
||||
false
|
||||
);
|
||||
|
||||
coWebsiteOpen.coWebsite = coWebsite;
|
||||
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
|
||||
|
||||
loadCoWebsiteFunction(coWebsite);
|
||||
};
|
||||
|
||||
if (
|
||||
localUserStore.getForceCowebsiteTrigger() ||
|
||||
websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON
|
||||
) {
|
||||
if (!websiteTriggerMessageProperty) {
|
||||
websiteTriggerMessageProperty = get(LL).message.openWebsiteTrigger();
|
||||
websiteTriggerMessageProperty = get(LL).trigger.cowebsite();
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
coWebsite: undefined,
|
||||
state: OpenCoWebsiteState.TRIGGER,
|
||||
});
|
||||
|
||||
this.coWebsitesActionTriggerByLayer.set(layer, actionUuid);
|
||||
this.coWebsitesActionTriggerByLayer.set(layer, actionId);
|
||||
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: actionUuid,
|
||||
uuid: actionId,
|
||||
type: "message",
|
||||
message: websiteTriggerMessageProperty,
|
||||
callback: () => openWebsiteFunction(),
|
||||
callback: () => openCoWebsiteFunction(),
|
||||
userInputManager: this.scene.userInputManager,
|
||||
});
|
||||
};
|
||||
} else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) {
|
||||
const coWebsite = new SimpleCoWebsite(
|
||||
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
|
||||
allowApiProperty,
|
||||
websitePolicyProperty,
|
||||
websiteWidthProperty,
|
||||
false
|
||||
);
|
||||
|
||||
this.coWebsitesIframeListeners.set(
|
||||
layer,
|
||||
iframeListener.unregisterIFrameStream.subscribe(() => {
|
||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||
if (
|
||||
coWebsiteOpen?.coWebsite?.state == CoWebsiteState.CLOSED &&
|
||||
(!websiteTriggerProperty || websiteTriggerProperty !== ON_ACTION_TRIGGER_DISABLE)
|
||||
) {
|
||||
createWebsiteTrigger();
|
||||
}
|
||||
})
|
||||
);
|
||||
coWebsiteOpen.coWebsite = coWebsite;
|
||||
|
||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
||||
if (
|
||||
forceTrigger ||
|
||||
(websiteTriggerProperty && websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON)
|
||||
) {
|
||||
createWebsiteTrigger();
|
||||
} else {
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
coWebsite: undefined,
|
||||
state: OpenCoWebsiteState.LOADING,
|
||||
});
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
|
||||
}
|
||||
|
||||
openWebsiteFunction();
|
||||
if (!websiteTriggerProperty) {
|
||||
openCoWebsiteFunction();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -223,37 +327,24 @@ export class GameMapPropertiesListener {
|
||||
return;
|
||||
}
|
||||
|
||||
const coWebsiteIframeListener = this.coWebsitesIframeListeners.get(layer);
|
||||
|
||||
if (coWebsiteIframeListener) {
|
||||
coWebsiteIframeListener.unsubscribe();
|
||||
this.coWebsitesIframeListeners.delete(layer);
|
||||
}
|
||||
|
||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||
|
||||
if (!coWebsiteOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) {
|
||||
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
|
||||
return;
|
||||
}
|
||||
const coWebsite = coWebsiteOpen.coWebsite;
|
||||
|
||||
if (
|
||||
coWebsiteOpen.state !== OpenCoWebsiteState.OPENED &&
|
||||
coWebsiteOpen.state !== OpenCoWebsiteState.TRIGGER
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (coWebsiteOpen.coWebsite !== undefined) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
|
||||
if (coWebsite) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.delete(layer);
|
||||
|
||||
if (!websiteTriggerProperty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionStore = get(layoutManagerActionStore);
|
||||
const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
|
||||
|
||||
@@ -269,6 +360,8 @@ export class GameMapPropertiesListener {
|
||||
if (action) {
|
||||
layoutManagerActionStore.removeAction(actionTriggerUuid);
|
||||
}
|
||||
|
||||
this.coWebsitesActionTriggerByLayer.delete(layer);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+176
-287
@@ -5,14 +5,14 @@ import { get, Unsubscriber } from "svelte/store";
|
||||
|
||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { urlManager } from "../../Url/UrlManager";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { UserInputManager } from "../UserInput/UserInputManager";
|
||||
import { gameManager } from "./GameManager";
|
||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { waScaleManager, WaScaleManagerEvent } from "../Services/WaScaleManager";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { EmoteManager } from "./EmoteManager";
|
||||
import { soundManager } from "./SoundManager";
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
@@ -20,9 +20,8 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
@@ -49,6 +48,7 @@ import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
import { PathfindingManager } from "../../Utils/PathfindingManager";
|
||||
import { ActivatablesManager } from "./ActivatablesManager";
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserMovedInterface,
|
||||
@@ -75,7 +75,7 @@ import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { contactPageStore } from "../../Stores/MenuStore";
|
||||
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
||||
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
|
||||
|
||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
@@ -89,11 +89,14 @@ import { deepCopy } from "deep-copy-ts";
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import { MapStore } from "../../Stores/Utils/MapStore";
|
||||
import { followUsersColorStore } from "../../Stores/FollowStore";
|
||||
import Camera = Phaser.Cameras.Scene2D.Camera;
|
||||
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
|
||||
import { locale, LL } from "../../i18n/i18n-svelte";
|
||||
import { locale } from "../../i18n/i18n-svelte";
|
||||
import { i18nJson } from "../../i18n/locales";
|
||||
|
||||
import { StringUtils } from "../../Utils/StringUtils";
|
||||
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@@ -190,8 +193,6 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
private gameMap!: GameMap;
|
||||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||
// The item that can be selected by pressing the space key.
|
||||
private outlinedItem: ActionableItem | null = null;
|
||||
public userInputManager!: UserInputManager;
|
||||
private isReconnecting: boolean | undefined = undefined;
|
||||
private playerName!: string;
|
||||
@@ -205,6 +206,7 @@ export class GameScene extends DirtyScene {
|
||||
private emoteManager!: EmoteManager;
|
||||
private cameraManager!: CameraManager;
|
||||
private pathfindingManager!: PathfindingManager;
|
||||
private activatablesManager!: ActivatablesManager;
|
||||
private preloading: boolean = true;
|
||||
private startPositionCalculator!: StartPositionCalculator;
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
@@ -255,7 +257,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
this.load.audio("audio-webrtc-in", "/resources/objects/webrtc-in.mp3");
|
||||
this.load.audio("audio-webrtc-out", "/resources/objects/webrtc-out.mp3");
|
||||
//this.load.audio('audio-report-message', '/resources/objects/report-message.mp3');
|
||||
this.load.audio("audio-report-message", "/resources/objects/report-message.mp3");
|
||||
this.sound.pauseOnBlur = false;
|
||||
|
||||
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
||||
@@ -561,6 +563,8 @@ export class GameScene extends DirtyScene {
|
||||
urlManager.getStartLayerNameFromUrl()
|
||||
);
|
||||
|
||||
startLayerNamesStore.set(this.startPositionCalculator.getStartPositionNames());
|
||||
|
||||
//add entities
|
||||
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
|
||||
@@ -580,7 +584,7 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionsGrid(),
|
||||
this.gameMap.getCollisionGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
|
||||
@@ -588,12 +592,22 @@ export class GameScene extends DirtyScene {
|
||||
this.createCurrentPlayer();
|
||||
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
|
||||
|
||||
this.tryMovePlayerWithMoveToParameter();
|
||||
|
||||
this.cameraManager = new CameraManager(
|
||||
this,
|
||||
{ x: this.Map.widthInPixels, y: this.Map.heightInPixels },
|
||||
waScaleManager
|
||||
);
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
|
||||
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
|
||||
|
||||
biggestAvailableAreaStore.recompute();
|
||||
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
||||
|
||||
@@ -639,7 +653,6 @@ export class GameScene extends DirtyScene {
|
||||
);
|
||||
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.scene.sleep();
|
||||
@@ -650,13 +663,9 @@ export class GameScene extends DirtyScene {
|
||||
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
||||
const newPeerNumber = peers.size;
|
||||
if (newPeerNumber > oldPeerNumber) {
|
||||
this.sound.play("audio-webrtc-in", {
|
||||
volume: 0.2,
|
||||
});
|
||||
this.playSound("audio-webrtc-in");
|
||||
} else if (newPeerNumber < oldPeerNumber) {
|
||||
this.sound.play("audio-webrtc-out", {
|
||||
volume: 0.2,
|
||||
});
|
||||
this.playSound("audio-webrtc-out");
|
||||
}
|
||||
oldPeerNumber = newPeerNumber;
|
||||
});
|
||||
@@ -679,10 +688,10 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
|
||||
if (color !== undefined) {
|
||||
this.CurrentPlayer.setOutlineColor(color);
|
||||
this.CurrentPlayer.setFollowOutlineColor(color);
|
||||
this.connection?.emitPlayerOutlineColor(color);
|
||||
} else {
|
||||
this.CurrentPlayer.removeOutlineColor();
|
||||
this.CurrentPlayer.removeFollowOutlineColor();
|
||||
this.connection?.emitPlayerOutlineColor(null);
|
||||
}
|
||||
});
|
||||
@@ -699,10 +708,6 @@ export class GameScene extends DirtyScene {
|
||||
);
|
||||
}
|
||||
|
||||
public activateOutlinedItem(): void {
|
||||
this.outlinedItem?.activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection to Pusher.
|
||||
*/
|
||||
@@ -814,7 +819,19 @@ export class GameScene extends DirtyScene {
|
||||
* Triggered when we receive the JWT token to connect to Jitsi
|
||||
*/
|
||||
this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
|
||||
this.startJitsi(message.jitsiRoom, message.jwt);
|
||||
if (!JITSI_URL) {
|
||||
throw new Error("Missing JITSI_URL environment variable.");
|
||||
}
|
||||
|
||||
let domain = JITSI_URL;
|
||||
|
||||
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
|
||||
domain = `${location.protocol}//${domain}`;
|
||||
}
|
||||
|
||||
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
|
||||
this.initialiseJitsi(coWebsite, message.jitsiRoom, message.jwt);
|
||||
});
|
||||
|
||||
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
|
||||
@@ -825,11 +842,8 @@ export class GameScene extends DirtyScene {
|
||||
this.simplePeer = new SimplePeer(this.connection);
|
||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||
|
||||
//listen event to share position of user
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this));
|
||||
this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this));
|
||||
this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
|
||||
this.gameMap.setPosition(event.x, event.y);
|
||||
this.handleCurrentPlayerHasMovedEvent(event);
|
||||
});
|
||||
|
||||
// Set up variables manager
|
||||
@@ -954,103 +968,6 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
private triggerOnMapLayerPropertyChange() {
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.onMapExit(
|
||||
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
|
||||
).catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) =>
|
||||
console.error(e)
|
||||
);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
this.stopJitsi();
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
|
||||
|
||||
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
|
||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
||||
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = get(LL).message.openJitsiTrigger();
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.connection?.setSilent(false);
|
||||
this.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.connection?.setSilent(true);
|
||||
this.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
|
||||
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: Legacy functionnality replace by layer change
|
||||
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
|
||||
if (oldValue) {
|
||||
iframeListener.sendLeaveEvent(oldValue as string);
|
||||
}
|
||||
if (newValue) {
|
||||
iframeListener.sendEnterEvent(newValue as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private listenToIframeEvents(): void {
|
||||
this.iframeSubscriptionList = [];
|
||||
this.iframeSubscriptionList.push(
|
||||
@@ -1296,21 +1213,20 @@ export class GameScene extends DirtyScene {
|
||||
throw new Error("Unknown query source");
|
||||
}
|
||||
|
||||
const coWebsite = await coWebsiteManager.loadCoWebsite(
|
||||
openCoWebsite.url,
|
||||
iframeListener.getBaseUrlFromSource(source),
|
||||
const coWebsite: SimpleCoWebsite = new SimpleCoWebsite(
|
||||
new URL(openCoWebsite.url, iframeListener.getBaseUrlFromSource(source)),
|
||||
openCoWebsite.allowApi,
|
||||
openCoWebsite.allowPolicy,
|
||||
openCoWebsite.position
|
||||
openCoWebsite.widthPercent,
|
||||
openCoWebsite.closable ?? true
|
||||
);
|
||||
|
||||
if (!coWebsite) {
|
||||
throw new Error("Error on opening co-website");
|
||||
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
|
||||
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
return {
|
||||
id: coWebsite.iframe.id,
|
||||
position: coWebsite.position,
|
||||
id: coWebsite.getId(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1319,28 +1235,23 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
return coWebsites.map((coWebsite: CoWebsite) => {
|
||||
return {
|
||||
id: coWebsite.iframe.id,
|
||||
position: coWebsite.position,
|
||||
id: coWebsite.getId(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("closeCoWebsite", async (coWebsiteId) => {
|
||||
iframeListener.registerAnswerer("closeCoWebsite", (coWebsiteId) => {
|
||||
const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId);
|
||||
|
||||
if (!coWebsite) {
|
||||
throw new Error("Unknown co-website");
|
||||
}
|
||||
|
||||
return coWebsiteManager.closeCoWebsite(coWebsite).catch((error) => {
|
||||
throw new Error("Error on closing co-website");
|
||||
});
|
||||
return coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("closeCoWebsites", async () => {
|
||||
return await coWebsiteManager.closeCoWebsites().catch((error) => {
|
||||
throw new Error("Error on closing all co-websites");
|
||||
});
|
||||
iframeListener.registerAnswerer("closeCoWebsites", () => {
|
||||
return coWebsiteManager.closeCoWebsites();
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("getProperty", (data) => {
|
||||
@@ -1439,7 +1350,7 @@ export class GameScene extends DirtyScene {
|
||||
//Create new colliders with the new GameMap
|
||||
this.createCollisionWithPlayer();
|
||||
//Create new trigger with the new GameMap
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
resolve(newFirstgid);
|
||||
});
|
||||
});
|
||||
@@ -1493,12 +1404,12 @@ export class GameScene extends DirtyScene {
|
||||
const green = normalizeColor(message.green);
|
||||
const blue = normalizeColor(message.blue);
|
||||
const color = (red << 16) | (green << 8) | blue;
|
||||
this.CurrentPlayer.setOutlineColor(color);
|
||||
this.CurrentPlayer.setApiOutlineColor(color);
|
||||
this.connection?.emitPlayerOutlineColor(color);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("removePlayerOutline", (message) => {
|
||||
this.CurrentPlayer.removeOutlineColor();
|
||||
this.CurrentPlayer.removeApiOutlineColor();
|
||||
this.connection?.emitPlayerOutlineColor(null);
|
||||
});
|
||||
|
||||
@@ -1510,9 +1421,9 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("movePlayerTo", async (message) => {
|
||||
const index = this.getGameMap().getTileIndexAt(message.x, message.y);
|
||||
const startTile = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
const path = await this.getPathfindingManager().findPath(startTile, index, true, true);
|
||||
const startTileIndex = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
const destinationTileIndex = this.getGameMap().getTileIndexAt(message.x, message.y);
|
||||
const path = await this.getPathfindingManager().findPath(startTileIndex, destinationTileIndex, true, true);
|
||||
path.shift();
|
||||
if (path.length === 0) {
|
||||
throw new Error("no path available");
|
||||
@@ -1552,14 +1463,15 @@ export class GameScene extends DirtyScene {
|
||||
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
|
||||
}
|
||||
}
|
||||
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
private getMapDirUrl(): string {
|
||||
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
||||
public getMapDirUrl(): string {
|
||||
return this.MapUrlFile.substring(0, this.MapUrlFile.lastIndexOf("/"));
|
||||
}
|
||||
|
||||
private async onMapExit(roomUrl: URL) {
|
||||
public async onMapExit(roomUrl: URL) {
|
||||
if (this.mapTransitioning) return;
|
||||
this.mapTransitioning = true;
|
||||
|
||||
@@ -1610,16 +1522,21 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
public playSound(sound: string) {
|
||||
this.sound.play(sound, {
|
||||
volume: 0.2,
|
||||
});
|
||||
}
|
||||
|
||||
public cleanupClosingScene(): void {
|
||||
// stop playing audio, close any open website, stop any open Jitsi
|
||||
coWebsiteManager.closeCoWebsites().catch((e) => console.error(e));
|
||||
coWebsiteManager.closeCoWebsites();
|
||||
// Stop the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
for (const script of scripts) {
|
||||
iframeListener.unregisterScript(script);
|
||||
}
|
||||
|
||||
this.stopJitsi();
|
||||
audioManagerFileStore.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
this.connection?.closeConnection();
|
||||
@@ -1647,7 +1564,10 @@ export class GameScene extends DirtyScene {
|
||||
this.sharedVariablesManager?.close();
|
||||
this.embeddedWebsiteManager?.close();
|
||||
|
||||
mediaManager.hideGameOverlay();
|
||||
//When we leave game, the camera is stop to be reopen after.
|
||||
// I think that we could keep camera status and the scene can manage camera setup
|
||||
//TODO find wy chrome don't manage correctly a multiple ask mediaDevices
|
||||
//mediaManager.hideMyCamera();
|
||||
|
||||
for (const iframeEvents of this.iframeSubscriptionList) {
|
||||
iframeEvents.unsubscribe();
|
||||
@@ -1667,6 +1587,36 @@ export class GameScene extends DirtyScene {
|
||||
this.MapPlayersByKey.clear();
|
||||
}
|
||||
|
||||
private tryMovePlayerWithMoveToParameter(): void {
|
||||
const moveToParam = urlManager.getHashParameter("moveTo");
|
||||
if (moveToParam) {
|
||||
try {
|
||||
let endPos;
|
||||
const posFromParam = StringUtils.parsePointFromParam(moveToParam);
|
||||
if (posFromParam) {
|
||||
endPos = this.gameMap.getTileIndexAt(posFromParam.x, posFromParam.y);
|
||||
} else {
|
||||
const destinationObject = this.gameMap.getObjectWithName(moveToParam);
|
||||
if (destinationObject) {
|
||||
endPos = this.gameMap.getTileIndexAt(destinationObject.x, destinationObject.y);
|
||||
} else {
|
||||
endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
|
||||
}
|
||||
}
|
||||
this.pathfindingManager
|
||||
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
|
||||
.then((path) => {
|
||||
if (path && path.length > 0) {
|
||||
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
|
||||
}
|
||||
})
|
||||
.catch((reason) => console.warn(reason));
|
||||
} catch (err) {
|
||||
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getExitUrl(layer: ITiledMapLayer): string | undefined {
|
||||
return this.getProperty(layer, GameMapProperties.EXIT_URL) as string | undefined;
|
||||
}
|
||||
@@ -1722,7 +1672,18 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
createCollisionWithPlayer() {
|
||||
private handleCurrentPlayerHasMovedEvent(event: HasPlayerMovedEvent): void {
|
||||
//listen event to share position of user
|
||||
this.pushPlayerPosition(event);
|
||||
this.gameMap.setPosition(event.x, event.y);
|
||||
this.activatablesManager.updateActivatableObjectsDistances([
|
||||
...Array.from(this.MapPlayersByKey.values()),
|
||||
...this.actionableItems.values(),
|
||||
]);
|
||||
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||
}
|
||||
|
||||
private createCollisionWithPlayer() {
|
||||
//add collision layer
|
||||
for (const phaserLayer of this.gameMap.phaserLayers) {
|
||||
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
||||
@@ -1741,7 +1702,7 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
createCurrentPlayer() {
|
||||
private createCurrentPlayer() {
|
||||
//TODO create animation moving between exit and start
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||
try {
|
||||
@@ -1756,7 +1717,7 @@ export class GameScene extends DirtyScene {
|
||||
this.companion,
|
||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||
);
|
||||
this.CurrentPlayer.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_DOWN, (pointer: Phaser.Input.Pointer) => {
|
||||
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
|
||||
return; //we don't want the menu to open when pinching on a touch screen.
|
||||
}
|
||||
@@ -1768,26 +1729,16 @@ export class GameScene extends DirtyScene {
|
||||
emoteMenuStore.openEmoteMenu();
|
||||
}
|
||||
});
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.pointerOverOutline(0x00ffff);
|
||||
});
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.pointerOutOutline();
|
||||
});
|
||||
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
||||
this.connection?.emitEmoteEvent(emoteKey);
|
||||
analyticsClient.launchEmote(emoteKey);
|
||||
});
|
||||
const moveToParam = urlManager.getHashParameter("moveTo");
|
||||
if (moveToParam) {
|
||||
try {
|
||||
const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
|
||||
this.pathfindingManager
|
||||
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
|
||||
.then((path) => {
|
||||
if (path && path.length > 0) {
|
||||
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
|
||||
}
|
||||
})
|
||||
.catch((reason) => console.warn(reason));
|
||||
} catch (err) {
|
||||
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof TextureError) {
|
||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
||||
@@ -1799,7 +1750,7 @@ export class GameScene extends DirtyScene {
|
||||
this.createCollisionWithPlayer();
|
||||
}
|
||||
|
||||
pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||
private pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||
if (this.lastMoveEventSent === event) {
|
||||
return;
|
||||
}
|
||||
@@ -1825,49 +1776,6 @@ export class GameScene extends DirtyScene {
|
||||
// Otherwise, do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the correct item to outline and outline it (if there is an item to be outlined)
|
||||
* @param event
|
||||
*/
|
||||
private outlineItem(event: HasPlayerMovedEvent): void {
|
||||
let x = event.x;
|
||||
let y = event.y;
|
||||
switch (event.direction) {
|
||||
case PlayerAnimationDirections.Up:
|
||||
y -= 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Down:
|
||||
y += 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Left:
|
||||
x -= 32;
|
||||
break;
|
||||
case PlayerAnimationDirections.Right:
|
||||
x += 32;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected direction "' + event.direction + '"');
|
||||
}
|
||||
|
||||
let shortestDistance: number = Infinity;
|
||||
let selectedItem: ActionableItem | null = null;
|
||||
for (const item of this.actionableItems.values()) {
|
||||
const distance = item.actionableDistance(x, y);
|
||||
if (distance !== null && distance < shortestDistance) {
|
||||
shortestDistance = distance;
|
||||
selectedItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.outlinedItem === selectedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.outlinedItem?.notSelectable();
|
||||
this.outlinedItem = selectedItem;
|
||||
this.outlinedItem?.selectable();
|
||||
}
|
||||
|
||||
private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
|
||||
this.lastMoveEventSent = event;
|
||||
this.lastSentTick = this.currentTick;
|
||||
@@ -1885,7 +1793,7 @@ export class GameScene extends DirtyScene {
|
||||
* @param time
|
||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||
*/
|
||||
update(time: number, delta: number): void {
|
||||
public update(time: number, delta: number): void {
|
||||
this.dirty = false;
|
||||
this.currentTick = time;
|
||||
this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick());
|
||||
@@ -1904,9 +1812,15 @@ export class GameScene extends DirtyScene {
|
||||
case "RemovePlayerEvent":
|
||||
this.doRemovePlayer(event.userId);
|
||||
break;
|
||||
case "UserMovedEvent":
|
||||
case "UserMovedEvent": {
|
||||
this.doUpdatePlayerPosition(event.event);
|
||||
const remotePlayer = this.MapPlayersByKey.get(event.event.userId);
|
||||
if (remotePlayer) {
|
||||
this.activatablesManager.updateDistanceForSingleActivatableObject(remotePlayer);
|
||||
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "GroupCreatedUpdatedEvent":
|
||||
this.doShareGroupPosition(event.event);
|
||||
break;
|
||||
@@ -1993,11 +1907,21 @@ export class GameScene extends DirtyScene {
|
||||
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
||||
);
|
||||
if (addPlayerData.outlineColor !== undefined) {
|
||||
player.setOutlineColor(addPlayerData.outlineColor);
|
||||
player.setApiOutlineColor(addPlayerData.outlineColor);
|
||||
}
|
||||
this.MapPlayers.add(player);
|
||||
this.MapPlayersByKey.set(player.userId, player);
|
||||
player.updatePosition(addPlayerData.position);
|
||||
|
||||
player.on(Phaser.Input.Events.POINTER_OVER, () => {
|
||||
this.activatablesManager.handlePointerOverActivatableObject(player);
|
||||
this.markDirty();
|
||||
});
|
||||
|
||||
player.on(Phaser.Input.Events.POINTER_OUT, () => {
|
||||
this.activatablesManager.handlePointerOutActivatableObject();
|
||||
this.markDirty();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2027,7 +1951,7 @@ export class GameScene extends DirtyScene {
|
||||
this.playersPositionInterpolator.removePlayer(userId);
|
||||
}
|
||||
|
||||
public updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||
private updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||
this.pendingEvents.enqueue({
|
||||
type: "UserMovedEvent",
|
||||
event: message,
|
||||
@@ -2057,7 +1981,7 @@ export class GameScene extends DirtyScene {
|
||||
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
|
||||
}
|
||||
|
||||
public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||
private shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||
this.pendingEvents.enqueue({
|
||||
type: "GroupCreatedUpdatedEvent",
|
||||
event: groupPositionMessage,
|
||||
@@ -2109,9 +2033,9 @@ export class GameScene extends DirtyScene {
|
||||
return;
|
||||
}
|
||||
if (message.removeOutlineColor) {
|
||||
character.removeOutlineColor();
|
||||
character.removeApiOutlineColor();
|
||||
} else {
|
||||
character.setOutlineColor(message.outlineColor);
|
||||
character.setApiOutlineColor(message.outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2156,7 +2080,18 @@ export class GameScene extends DirtyScene {
|
||||
biggestAvailableAreaStore.recompute();
|
||||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
public enableMediaBehaviors() {
|
||||
const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT);
|
||||
this.connection?.setSilent(!!silent);
|
||||
mediaManager.showMyCamera();
|
||||
}
|
||||
|
||||
public disableMediaBehaviors() {
|
||||
this.connection?.setSilent(true);
|
||||
mediaManager.hideMyCamera();
|
||||
}
|
||||
|
||||
public initialiseJitsi(coWebsite: JitsiCoWebsite, roomName: string, jwt?: string): void {
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(
|
||||
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined,
|
||||
@@ -2167,71 +2102,21 @@ export class GameScene extends DirtyScene {
|
||||
GameMapProperties.JITSI_INTERFACE_CONFIG
|
||||
);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
const jitsiWidth = allProps.get(GameMapProperties.JITSI_WIDTH) as number | undefined;
|
||||
|
||||
const jitsiKeepCircle = allProps.get("jitsiKeepCircle") as boolean | false;
|
||||
|
||||
jitsiFactory
|
||||
.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth)
|
||||
.catch((e) => console.error(e));
|
||||
this.connection?.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
if (jitsiKeepCircle) {
|
||||
const silent = this.gameMap.getCurrentProperties().get("silent");
|
||||
this.connection?.setSilent(!!silent);
|
||||
mediaManager.showGameOverlay();
|
||||
}
|
||||
|
||||
analyticsClient.enteredJitsi(roomName, this.room.id);
|
||||
|
||||
//permit to stop jitsi when user close iframe
|
||||
mediaManager.addTriggerCloseJitsiFrameButton("close-jitsi", () => {
|
||||
this.stopJitsi();
|
||||
coWebsite.setJitsiLoadPromise(() => {
|
||||
return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
});
|
||||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT);
|
||||
this.connection?.setSilent(!!silent);
|
||||
jitsiFactory.stop();
|
||||
mediaManager.showGameOverlay();
|
||||
coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
|
||||
if (allProps.get("jitsiRoom") === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
if (allProps.get("jitsiKeepCircle") as boolean | false) {
|
||||
this.enableMediaBehaviors();
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(
|
||||
allProps.get(GameMapProperties.JITSI_ROOM) as string,
|
||||
this.instance
|
||||
);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
|
||||
|
||||
this.connection && this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to enter Jitsi Meet room";
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
this.disableMediaBehaviors();
|
||||
}
|
||||
|
||||
mediaManager.removeTriggerCloseJitsiFrameButton("close-jitsi");
|
||||
analyticsClient.enteredJitsi(roomName, this.room.id);
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
@@ -2307,4 +2192,8 @@ export class GameScene extends DirtyScene {
|
||||
public getPathfindingManager(): PathfindingManager {
|
||||
return this.pathfindingManager;
|
||||
}
|
||||
|
||||
public getActivatablesManager(): ActivatablesManager {
|
||||
return this.activatablesManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface OutlineableInterface {
|
||||
setFollowOutlineColor(color: number): void;
|
||||
removeFollowOutlineColor(): void;
|
||||
setApiOutlineColor(color: number): void;
|
||||
removeApiOutlineColor(): void;
|
||||
pointerOverOutline(color: number): void;
|
||||
pointerOutOutline(): void;
|
||||
characterCloseByOutline(color: number): void;
|
||||
characterFarAwayOutline(): void;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export class PlayerMovement {
|
||||
oldX: this.startPosition.x,
|
||||
oldY: this.startPosition.y,
|
||||
direction: this.endPosition.direction,
|
||||
moving: this.endPosition.moving,
|
||||
moving: this.isOutdated(tick) ? false : this.endPosition.moving,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,32 +16,6 @@ export class StartPositionCalculator {
|
||||
) {
|
||||
this.initStartXAndStartY();
|
||||
}
|
||||
private initStartXAndStartY() {
|
||||
// If there is an init position passed
|
||||
if (this.initPosition !== null) {
|
||||
this.startPosition = this.initPosition;
|
||||
} else {
|
||||
// Now, let's find the start layer
|
||||
if (this.startLayerName) {
|
||||
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
|
||||
}
|
||||
if (this.startPosition === undefined) {
|
||||
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
|
||||
}
|
||||
}
|
||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||
if (this.startPosition === undefined) {
|
||||
console.warn(
|
||||
'This map is missing a layer named "start" that contains the available default start positions.'
|
||||
);
|
||||
// Let's start in the middle of the map
|
||||
this.startPosition = {
|
||||
x: this.mapFile.width * 16,
|
||||
y: this.mapFile.height * 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -77,6 +51,47 @@ export class StartPositionCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
public getStartPositionNames(): string[] {
|
||||
const names: string[] = [];
|
||||
for (const layer of this.gameMap.flatLayers) {
|
||||
if (layer.name === "start") {
|
||||
names.push(layer.name);
|
||||
continue;
|
||||
}
|
||||
if (this.isStartLayer(layer)) {
|
||||
names.push(layer.name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private initStartXAndStartY() {
|
||||
// If there is an init position passed
|
||||
if (this.initPosition !== null) {
|
||||
this.startPosition = this.initPosition;
|
||||
} else {
|
||||
// Now, let's find the start layer
|
||||
if (this.startLayerName) {
|
||||
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
|
||||
}
|
||||
if (this.startPosition === undefined) {
|
||||
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
|
||||
}
|
||||
}
|
||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||
if (this.startPosition === undefined) {
|
||||
console.warn(
|
||||
'This map is missing a layer named "start" that contains the available default start positions.'
|
||||
);
|
||||
// Let's start in the middle of the map
|
||||
this.startPosition = {
|
||||
x: this.mapFile.width * 16,
|
||||
y: this.mapFile.height * 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private isStartLayer(layer: ITiledMapLayer): boolean {
|
||||
return this.getProperty(layer, GameMapProperties.START_LAYER) == true;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||
|
||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||
|
||||
export class ActionableItem {
|
||||
export class ActionableItem implements ActivatableInterface {
|
||||
private readonly activationRadiusSquared: number;
|
||||
private isSelectable: boolean = false;
|
||||
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
|
||||
@@ -17,7 +18,7 @@ export class ActionableItem {
|
||||
private id: number,
|
||||
private sprite: Sprite,
|
||||
private eventHandler: GameScene,
|
||||
private activationRadius: number,
|
||||
public readonly activationRadius: number,
|
||||
private onActivateCallback: (item: ActionableItem) => void
|
||||
) {
|
||||
this.activationRadiusSquared = activationRadius * activationRadius;
|
||||
@@ -40,6 +41,10 @@ export class ActionableItem {
|
||||
}
|
||||
}
|
||||
|
||||
public getPosition(): { x: number; y: number } {
|
||||
return { x: this.sprite.x, y: this.sprite.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the outline of the sprite.
|
||||
*/
|
||||
@@ -70,9 +75,10 @@ export class ActionableItem {
|
||||
return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the "space" key is pressed and the object is in range of being activated.
|
||||
*/
|
||||
public isActivatable(): boolean {
|
||||
return this.isSelectable;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
this.onActivateCallback(this);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level === -1) {
|
||||
@@ -21,7 +22,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level !== -1) {
|
||||
|
||||
@@ -11,10 +11,10 @@ import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||
import { get } from "svelte/store";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
@@ -67,12 +67,12 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener("wake", () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||
customCharacterSceneVisibleStore.set(true);
|
||||
});
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||
|
||||
this.Rectangle = this.add.rectangle(
|
||||
this.cameras.main.worldView.x + this.cameras.main.width / 2,
|
||||
@@ -289,7 +289,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.events.removeListener("wake");
|
||||
gameManager.tryResumingGame(EnableCameraSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import LL from "../../i18n/i18n-svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { localeDetector } from "../../i18n/locales";
|
||||
|
||||
const $LL = get(LL);
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
/**
|
||||
@@ -43,6 +41,7 @@ export class EntryScene extends Scene {
|
||||
this.scene.start(nextSceneName);
|
||||
})
|
||||
.catch((err) => {
|
||||
const $LL = get(LL);
|
||||
if (err.response && err.response.status == 404) {
|
||||
ErrorScene.showError(
|
||||
new WAError(
|
||||
|
||||
@@ -12,8 +12,8 @@ import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
@@ -60,7 +60,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener("wake", () => {
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||
|
||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xffffff);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
import { PinchManager } from "../UserInput/PinchManager";
|
||||
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
|
||||
export const SelectCompanionSceneName = "SelectCompanionScene";
|
||||
|
||||
@@ -44,7 +44,7 @@ export class SelectCompanionScene extends ResizableScene {
|
||||
selectCompanionSceneVisibleStore.set(true);
|
||||
|
||||
waScaleManager.saveZoom();
|
||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
||||
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
new PinchManager(this);
|
||||
|
||||
@@ -78,6 +78,8 @@ export interface ITiledMapTileLayer {
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
parallaxx?: number;
|
||||
parallaxy?: number;
|
||||
|
||||
/**
|
||||
* Draw order (topdown (default), index)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Character } from "../Entity/Character";
|
||||
import { get } from "svelte/store";
|
||||
import { userMovingStore } from "../../Stores/GameStore";
|
||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export const hasMovedEventName = "hasMoved";
|
||||
export const requestEmoteEventName = "requestEmote";
|
||||
@@ -20,11 +21,11 @@ export class Player extends Character {
|
||||
x: number,
|
||||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
texturesPromise: CancelablePromise<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
companion: string | null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
companionTexturePromise?: CancelablePromise<string>
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||
|
||||
|
||||
@@ -37,19 +37,17 @@ export class WaScaleManager {
|
||||
height: height * devicePixelRatio,
|
||||
});
|
||||
|
||||
if (gameSize.width == 0) {
|
||||
return;
|
||||
if (realSize.width !== 0 && gameSize.width !== 0 && devicePixelRatio !== 0) {
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
}
|
||||
|
||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||
|
||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||
this.scaleManager.setZoom(this.actualZoom);
|
||||
|
||||
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
||||
const style = this.scaleManager.canvas.style;
|
||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + "px";
|
||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + "px";
|
||||
style.width = Math.ceil(realSize.width !== 0 ? realSize.width / devicePixelRatio : 0) + "px";
|
||||
style.height = Math.ceil(realSize.height !== 0 ? realSize.height / devicePixelRatio : 0) + "px";
|
||||
|
||||
// Resize the game element at the same size at the canvas
|
||||
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>("game").style;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Player } from "../Player/Player";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
|
||||
import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
|
||||
@@ -23,10 +26,16 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
}
|
||||
|
||||
public handlePointerUpEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {
|
||||
if (pointer.rightButtonReleased() || pointer.getDuration() > 250) {
|
||||
if ((!pointer.wasTouch && pointer.leftButtonReleased()) || pointer.getDuration() > 250) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const object of gameObjects) {
|
||||
if (object instanceof Player || object instanceof RemotePlayer) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.lastTime > 0 &&
|
||||
pointer.time - this.lastTime < 500 &&
|
||||
@@ -46,7 +55,7 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
.then((path) => {
|
||||
// Remove first step as it is for the tile we are currently standing on
|
||||
path.shift();
|
||||
this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => {});
|
||||
this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => { });
|
||||
})
|
||||
.catch((reason) => {
|
||||
console.warn(reason);
|
||||
@@ -58,10 +67,23 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
||||
this.lastY = pointer.y;
|
||||
}
|
||||
|
||||
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {}
|
||||
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void { }
|
||||
|
||||
public handleSpaceKeyUpEvent(event: Event): Event {
|
||||
this.gameScene.activateOutlinedItem();
|
||||
const activatableManager = this.gameScene.getActivatablesManager();
|
||||
const activatable = activatableManager.getSelectedActivatableObject();
|
||||
if (activatable && activatable.isActivatable() && activatableManager.isSelectingByDistanceEnabled()) {
|
||||
activatable.activate();
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
public addSpaceEventListener(callback: Function): void {
|
||||
this.gameScene.input.keyboard.addListener("keyup-SPACE", callback);
|
||||
this.gameScene.getActivatablesManager().disableSelectingByDistance();
|
||||
}
|
||||
public removeSpaceEventListner(callback: Function): void {
|
||||
this.gameScene.input.keyboard.removeListener("keyup-SPACE", callback);
|
||||
this.gameScene.getActivatablesManager().enableSelectingByDistance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,10 +223,10 @@ export class UserInputManager {
|
||||
}
|
||||
|
||||
addSpaceEventListner(callback: Function) {
|
||||
this.scene.input.keyboard.addListener("keyup-SPACE", callback);
|
||||
this.userInputHandler.addSpaceEventListener(callback);
|
||||
}
|
||||
removeSpaceEventListner(callback: Function) {
|
||||
this.scene.input.keyboard.removeListener("keyup-SPACE", callback);
|
||||
this.userInputHandler.removeSpaceEventListner(callback);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export interface ActionsMenuData {
|
||||
playerName: string;
|
||||
actions: { actionName: string; callback: Function }[];
|
||||
}
|
||||
|
||||
function createActionsMenuStore() {
|
||||
const { subscribe, update, set } = writable<ActionsMenuData | undefined>(undefined);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
initialize: (playerName: string) => {
|
||||
set({
|
||||
playerName,
|
||||
actions: [],
|
||||
});
|
||||
},
|
||||
addAction: (actionName: string, callback: Function) => {
|
||||
update((data) => {
|
||||
data?.actions.push({ actionName, callback });
|
||||
return data;
|
||||
});
|
||||
},
|
||||
removeAction: (actionName: string) => {
|
||||
update((data) => {
|
||||
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName);
|
||||
if (actionIndex !== undefined && actionIndex != -1) {
|
||||
data?.actions.splice(actionIndex, 1);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Hides menu
|
||||
*/
|
||||
clear: () => {
|
||||
set(undefined);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const actionsMenuStore = createActionsMenuStore();
|
||||
@@ -2,14 +2,14 @@ import { get, writable } from "svelte/store";
|
||||
import type { Box } from "../WebRtc/LayoutManager";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { layoutModeStore } from "./StreamableCollectionStore";
|
||||
import { embedScreenLayout } from "./EmbedScreensStore";
|
||||
|
||||
/**
|
||||
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
||||
*/
|
||||
function findBiggestAvailableArea(): Box {
|
||||
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>("#game canvas");
|
||||
if (get(layoutModeStore) === LayoutMode.VideoChat) {
|
||||
if (get(embedScreenLayout) === LayoutMode.VideoChat) {
|
||||
const children = document.querySelectorAll<HTMLDivElement>("div.chat-mode > div");
|
||||
const htmlChildren = Array.from(children.values());
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { derived, writable } from "svelte/store";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
|
||||
|
||||
function createCoWebsiteStore() {
|
||||
const { subscribe, set, update } = writable(Array<CoWebsite>());
|
||||
|
||||
set(Array<CoWebsite>());
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
add: (coWebsite: CoWebsite, position?: number) => {
|
||||
coWebsite.getStateSubscriber().subscribe((value) => {
|
||||
update((currentArray) => currentArray);
|
||||
});
|
||||
|
||||
if (position || position === 0) {
|
||||
update((currentArray) => {
|
||||
if (position === 0) {
|
||||
return [coWebsite, ...currentArray];
|
||||
} else if (currentArray.length > position) {
|
||||
const test = [...currentArray.splice(position, 0, coWebsite)];
|
||||
return [...currentArray.splice(position, 0, coWebsite)];
|
||||
}
|
||||
|
||||
return [...currentArray, coWebsite];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
update((currentArray) => [...currentArray, coWebsite]);
|
||||
},
|
||||
remove: (coWebsite: CoWebsite) => {
|
||||
update((currentArray) => [
|
||||
...currentArray.filter((currentCoWebsite) => currentCoWebsite.getId() !== coWebsite.getId()),
|
||||
]);
|
||||
},
|
||||
empty: () => {
|
||||
set(Array<CoWebsite>());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const coWebsites = createCoWebsiteStore();
|
||||
|
||||
export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) =>
|
||||
$coWebsites.filter((coWebsite) => coWebsite.getState() !== "asleep")
|
||||
);
|
||||
|
||||
export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) =>
|
||||
$coWebsites.find((coWebsite) => coWebsite.getState() !== "asleep")
|
||||
);
|
||||
@@ -0,0 +1,51 @@
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { coWebsites } from "./CoWebsiteStore";
|
||||
import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore";
|
||||
|
||||
export type EmbedScreen =
|
||||
| {
|
||||
type: "streamable";
|
||||
embed: Streamable;
|
||||
}
|
||||
| {
|
||||
type: "cowebsite";
|
||||
embed: CoWebsite;
|
||||
};
|
||||
|
||||
function createHighlightedEmbedScreenStore() {
|
||||
const { subscribe, set, update } = writable<EmbedScreen | null>(null);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
highlight: (embedScreen: EmbedScreen) => {
|
||||
set(embedScreen);
|
||||
},
|
||||
removeHighlight: () => {
|
||||
set(null);
|
||||
},
|
||||
toggleHighlight: (embedScreen: EmbedScreen) => {
|
||||
update((currentEmbedScreen) =>
|
||||
!currentEmbedScreen ||
|
||||
embedScreen.type !== currentEmbedScreen.type ||
|
||||
(embedScreen.type === "cowebsite" &&
|
||||
currentEmbedScreen.type === "cowebsite" &&
|
||||
embedScreen.embed.getId() !== currentEmbedScreen.embed.getId()) ||
|
||||
(embedScreen.type === "streamable" &&
|
||||
currentEmbedScreen.type === "streamable" &&
|
||||
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)
|
||||
? embedScreen
|
||||
: null
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const highlightedEmbedScreen = createHighlightedEmbedScreenStore();
|
||||
export const embedScreenLayout = writable<LayoutMode>(LayoutMode.Presentation);
|
||||
|
||||
export const hasEmbedScreen = derived(
|
||||
[streamableCollectionStore],
|
||||
($values) => get(streamableCollectionStore).size + get(coWebsites).length > 0
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
/**
|
||||
* A store that contains whether the game overlay is shown or not.
|
||||
* Typically, the overlay is hidden when entering Jitsi meet.
|
||||
*/
|
||||
function createGameOverlayVisibilityStore() {
|
||||
const { subscribe, set, update } = writable(false);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
showGameOverlay: () => set(true),
|
||||
hideGameOverlay: () => set(false),
|
||||
};
|
||||
}
|
||||
|
||||
export const gameOverlayVisibilityStore = createGameOverlayVisibilityStore();
|
||||
@@ -1,4 +1,5 @@
|
||||
import { derived, writable } from "svelte/store";
|
||||
import type { ActivatablesManager } from "../Phaser/Game/ActivatablesManager";
|
||||
import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
|
||||
|
||||
export interface LayoutManagerAction {
|
||||
@@ -51,6 +52,6 @@ function createLayoutManagerAction() {
|
||||
|
||||
export const layoutManagerActionStore = createLayoutManagerAction();
|
||||
|
||||
export const layoutManagerVisibilityStore = derived(layoutManagerActionStore, ($layoutManagerActionStore) => {
|
||||
export const layoutManagerActionVisibilityStore = derived(layoutManagerActionStore, ($layoutManagerActionStore) => {
|
||||
return !!$layoutManagerActionStore.length;
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { BrowserTooOldError } from "./Errors/BrowserTooOldError";
|
||||
import { errorStore } from "./ErrorStore";
|
||||
import { getNavigatorType, isIOS, NavigatorType } from "../WebRtc/DeviceUtils";
|
||||
import { WebviewOnOldIOS } from "./Errors/WebviewOnOldIOS";
|
||||
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
||||
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||
import { peerStore } from "./PeerStore";
|
||||
import { privacyShutdownStore } from "./PrivacyShutdownStore";
|
||||
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
|
||||
@@ -233,7 +233,7 @@ export const mediaStreamConstraintsStore = derived(
|
||||
[
|
||||
requestedCameraState,
|
||||
requestedMicrophoneState,
|
||||
gameOverlayVisibilityStore,
|
||||
myCameraVisibilityStore,
|
||||
enableCameraSceneVisibilityStore,
|
||||
videoConstraintStore,
|
||||
audioConstraintStore,
|
||||
@@ -245,7 +245,7 @@ export const mediaStreamConstraintsStore = derived(
|
||||
[
|
||||
$requestedCameraState,
|
||||
$requestedMicrophoneState,
|
||||
$gameOverlayVisibilityStore,
|
||||
$myCameraVisibilityStore,
|
||||
$enableCameraSceneVisibilityStore,
|
||||
$videoConstraintStore,
|
||||
$audioConstraintStore,
|
||||
@@ -283,7 +283,7 @@ export const mediaStreamConstraintsStore = derived(
|
||||
}
|
||||
|
||||
// Disable webcam and microphone when in a Jitsi
|
||||
if ($gameOverlayVisibilityStore === false) {
|
||||
if ($myCameraVisibilityStore === false) {
|
||||
currentVideoConstraint = false;
|
||||
currentAudioConstraint = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
/**
|
||||
* A store that contains whether my camera & actions is shown or not.
|
||||
* Typically, the overlay is hidden when entering Jitsi meet.
|
||||
*/
|
||||
|
||||
export const myCameraVisibilityStore = writable(false);
|
||||
@@ -3,37 +3,55 @@ import { writable } from "svelte/store";
|
||||
export function createColorStore() {
|
||||
const { subscribe, set } = writable<number | undefined>(undefined);
|
||||
|
||||
let color: number | undefined = undefined;
|
||||
let focused: boolean = false;
|
||||
let followColor: number | undefined = undefined;
|
||||
let apiColor: number | undefined = undefined;
|
||||
let pointedByPointer: number | undefined = undefined;
|
||||
let pointedByCharacter: number | undefined = undefined;
|
||||
|
||||
const updateColor = () => {
|
||||
if (focused) {
|
||||
set(0xffff00);
|
||||
} else {
|
||||
set(color);
|
||||
}
|
||||
set(pointedByPointer ?? pointedByCharacter ?? followColor ?? apiColor);
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
pointerOver() {
|
||||
focused = true;
|
||||
pointerOver(color: number) {
|
||||
pointedByPointer = color;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
pointerOut() {
|
||||
focused = false;
|
||||
pointedByPointer = undefined;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
setColor(newColor: number) {
|
||||
color = newColor;
|
||||
characterCloseBy(color: number) {
|
||||
pointedByCharacter = color;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
removeColor() {
|
||||
color = undefined;
|
||||
characterFarAway() {
|
||||
pointedByCharacter = undefined;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
setFollowColor(newColor: number) {
|
||||
followColor = newColor;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
removeFollowColor() {
|
||||
followColor = undefined;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
setApiColor(newColor: number) {
|
||||
apiColor = newColor;
|
||||
updateColor();
|
||||
},
|
||||
|
||||
removeApiColor() {
|
||||
apiColor = undefined;
|
||||
updateColor();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { derived, Readable, readable, writable } from "svelte/store";
|
||||
import { peerStore } from "./PeerStore";
|
||||
import type { LocalStreamStoreValue } from "./MediaStore";
|
||||
import { gameOverlayVisibilityStore } from "./GameOverlayStoreVisibility";
|
||||
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||
|
||||
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -41,8 +41,8 @@ let previousComputedAudioConstraint: boolean | MediaTrackConstraints = false;
|
||||
* A store containing the media constraints we want to apply.
|
||||
*/
|
||||
export const screenSharingConstraintsStore = derived(
|
||||
[requestedScreenSharingState, gameOverlayVisibilityStore, peerStore],
|
||||
([$requestedScreenSharingState, $gameOverlayVisibilityStore, $peerStore], set) => {
|
||||
[requestedScreenSharingState, myCameraVisibilityStore, peerStore],
|
||||
([$requestedScreenSharingState, $myCameraVisibilityStore, $peerStore], set) => {
|
||||
let currentVideoConstraint: boolean | MediaTrackConstraints = true;
|
||||
let currentAudioConstraint: boolean | MediaTrackConstraints = false;
|
||||
|
||||
@@ -53,7 +53,7 @@ export const screenSharingConstraintsStore = derived(
|
||||
}
|
||||
|
||||
// Disable screen sharing when in a Jitsi
|
||||
if (!$gameOverlayVisibilityStore) {
|
||||
if (!$myCameraVisibilityStore) {
|
||||
currentVideoConstraint = false;
|
||||
currentAudioConstraint = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Readable, writable } from "svelte/store";
|
||||
|
||||
/**
|
||||
* A store that contains the map starting layers names
|
||||
*/
|
||||
export const startLayerNamesStore = writable<string[]>([]);
|
||||
@@ -1,13 +1,11 @@
|
||||
import { derived, get, Readable, writable } from "svelte/store";
|
||||
import { derived, get, Readable } from "svelte/store";
|
||||
import { ScreenSharingLocalMedia, screenSharingLocalMedia } from "./ScreenSharingStore";
|
||||
import { peerStore, screenSharingStreamStore } from "./PeerStore";
|
||||
import type { RemotePeer } from "../WebRtc/SimplePeer";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { highlightedEmbedScreen } from "./EmbedScreensStore";
|
||||
|
||||
export type Streamable = RemotePeer | ScreenSharingLocalMedia;
|
||||
|
||||
export const layoutModeStore = writable<LayoutMode>(LayoutMode.Presentation);
|
||||
|
||||
/**
|
||||
* A store that contains everything that can produce a stream (so the peers + the local screen sharing stream)
|
||||
*/
|
||||
@@ -28,6 +26,12 @@ function createStreamableCollectionStore(): Readable<Map<string, Streamable>> {
|
||||
addPeer($screenSharingLocalMedia);
|
||||
}
|
||||
|
||||
const $highlightedEmbedScreen = get(highlightedEmbedScreen);
|
||||
|
||||
if ($highlightedEmbedScreen?.type === "streamable" && !peers.has($highlightedEmbedScreen.embed.uniqueId)) {
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
}
|
||||
|
||||
set(peers);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -96,6 +96,7 @@ export class MapStore<K, V> extends Map<K, V> implements Readable<Map<K, V>> {
|
||||
const unsubscribe = storeByKey.subscribe((newMapValue) => {
|
||||
if (unsubscribeDeepStore) {
|
||||
unsubscribeDeepStore();
|
||||
unsubscribeDeepStore = undefined;
|
||||
}
|
||||
if (newMapValue === undefined) {
|
||||
set(undefined);
|
||||
@@ -115,6 +116,7 @@ export class MapStore<K, V> extends Map<K, V> implements Readable<Map<K, V>> {
|
||||
unsubscribe();
|
||||
if (unsubscribeDeepStore) {
|
||||
unsubscribeDeepStore();
|
||||
unsubscribeDeepStore = undefined;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
type InternalBreakpoint = {
|
||||
beforeBreakpoint: InternalBreakpoint | undefined;
|
||||
nextBreakpoint: InternalBreakpoint | undefined;
|
||||
pixels: number;
|
||||
};
|
||||
|
||||
function generateBreakpointsMap(): Map<string, InternalBreakpoint> {
|
||||
// If is changed don't forget to also change it on SASS.
|
||||
const breakpoints: { [key: string]: number } = {
|
||||
xs: 0,
|
||||
sm: 576,
|
||||
md: 768,
|
||||
lg: 992,
|
||||
xl: 1200,
|
||||
xxl: 1400,
|
||||
};
|
||||
|
||||
let beforeBreakpoint: InternalBreakpoint | undefined;
|
||||
let beforeBreakpointTag: string | undefined;
|
||||
const mapRender = new Map<string, InternalBreakpoint>();
|
||||
|
||||
for (const breakpoint in breakpoints) {
|
||||
const newBreakpoint = {
|
||||
beforeBreakpoint: beforeBreakpoint,
|
||||
nextBreakpoint: undefined,
|
||||
pixels: breakpoints[breakpoint],
|
||||
};
|
||||
|
||||
if (beforeBreakpointTag && beforeBreakpoint) {
|
||||
beforeBreakpoint.nextBreakpoint = newBreakpoint;
|
||||
mapRender.set(beforeBreakpointTag, beforeBreakpoint);
|
||||
}
|
||||
|
||||
mapRender.set(breakpoint, {
|
||||
beforeBreakpoint: beforeBreakpoint,
|
||||
nextBreakpoint: undefined,
|
||||
pixels: breakpoints[breakpoint],
|
||||
});
|
||||
|
||||
beforeBreakpointTag = breakpoint;
|
||||
beforeBreakpoint = newBreakpoint;
|
||||
}
|
||||
|
||||
return mapRender;
|
||||
}
|
||||
|
||||
const breakpoints = generateBreakpointsMap();
|
||||
|
||||
export type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
|
||||
|
||||
export function isMediaBreakpointUp(breakpoint: Breakpoint): boolean {
|
||||
if (breakpoint === "xxl") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const breakpointObject = breakpoints.get(breakpoint);
|
||||
|
||||
if (!breakpointObject) {
|
||||
throw new Error(`Unknown breakpoint: ${breakpoint}`);
|
||||
}
|
||||
|
||||
if (!breakpointObject.nextBreakpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return breakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth;
|
||||
}
|
||||
|
||||
export function isMediaBreakpointDown(breakpoint: Breakpoint): boolean {
|
||||
if (breakpoint === "xs") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const breakpointObject = breakpoints.get(breakpoint);
|
||||
|
||||
if (!breakpointObject) {
|
||||
throw new Error(`Unknown breakpoint: ${breakpoint}`);
|
||||
}
|
||||
|
||||
return breakpointObject.pixels <= window.innerWidth;
|
||||
}
|
||||
|
||||
export function isMediaBreakpointOnly(breakpoint: Breakpoint): boolean {
|
||||
const breakpointObject = breakpoints.get(breakpoint);
|
||||
|
||||
if (!breakpointObject) {
|
||||
throw new Error(`Unknown breakpoint: ${breakpoint}`);
|
||||
}
|
||||
|
||||
return (
|
||||
breakpointObject.pixels <= window.innerWidth &&
|
||||
(!breakpointObject.nextBreakpoint || breakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth)
|
||||
);
|
||||
}
|
||||
|
||||
export function isMediaBreakpointBetween(startBreakpoint: Breakpoint, endBreakpoint: Breakpoint): boolean {
|
||||
const startBreakpointObject = breakpoints.get(startBreakpoint);
|
||||
const endBreakpointObject = breakpoints.get(endBreakpoint);
|
||||
|
||||
if (!startBreakpointObject) {
|
||||
throw new Error(`Unknown start breakpoint: ${startBreakpointObject}`);
|
||||
}
|
||||
|
||||
if (!endBreakpointObject) {
|
||||
throw new Error(`Unknown end breakpoint: ${endBreakpointObject}`);
|
||||
}
|
||||
|
||||
return (
|
||||
startBreakpointObject.pixels <= innerWidth &&
|
||||
(!endBreakpointObject.nextBreakpoint || endBreakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { OutlineableInterface } from "../Phaser/Game/OutlineableInterface";
|
||||
|
||||
export function isOutlineable(object: unknown): object is OutlineableInterface {
|
||||
return (object as OutlineableInterface)?.pointerOverOutline !== undefined;
|
||||
}
|
||||
@@ -19,6 +19,10 @@ export class PathfindingManager {
|
||||
this.setEasyStarGrid(collisionsGrid);
|
||||
}
|
||||
|
||||
public setCollisionGrid(collisionGrid: number[][]): void {
|
||||
this.setEasyStarGrid(collisionGrid);
|
||||
}
|
||||
|
||||
public async findPath(
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export class StringUtils {
|
||||
public static parsePointFromParam(param: string, separator: string = ","): { x: number; y: number } | undefined {
|
||||
const values = param.split(separator).map((val) => parseInt(val));
|
||||
if (values.length !== 2) {
|
||||
return;
|
||||
}
|
||||
if (isNaN(values[0]) || isNaN(values[1])) {
|
||||
return;
|
||||
}
|
||||
return { x: values[0], y: values[1] };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import type { Readable, Writable } from "svelte/store";
|
||||
|
||||
export type CoWebsiteState = "asleep" | "loading" | "ready";
|
||||
|
||||
export interface CoWebsite {
|
||||
getId(): string;
|
||||
getUrl(): URL;
|
||||
getState(): CoWebsiteState;
|
||||
getStateSubscriber(): Readable<CoWebsiteState>;
|
||||
getIframe(): HTMLIFrameElement | undefined;
|
||||
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined;
|
||||
getWidthPercent(): number | undefined;
|
||||
isClosable(): boolean;
|
||||
load(): CancelablePromise<HTMLIFrameElement>;
|
||||
unload(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { jitsiFactory } from "../JitsiFactory";
|
||||
import { SimpleCoWebsite } from "./SimpleCoWebsite";
|
||||
|
||||
export class JitsiCoWebsite extends SimpleCoWebsite {
|
||||
private jitsiLoadPromise?: () => CancelablePromise<HTMLIFrameElement>;
|
||||
|
||||
setJitsiLoadPromise(promise: () => CancelablePromise<HTMLIFrameElement>): void {
|
||||
this.jitsiLoadPromise = promise;
|
||||
}
|
||||
|
||||
load(): CancelablePromise<HTMLIFrameElement> {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
this.state.set("loading");
|
||||
|
||||
gameManager.getCurrentGameScene().disableMediaBehaviors();
|
||||
|
||||
if (!this.jitsiLoadPromise) {
|
||||
return reject("Undefined Jitsi start callback");
|
||||
}
|
||||
|
||||
const jitsiLoading = this.jitsiLoadPromise()
|
||||
.then((iframe) => {
|
||||
this.iframe = iframe;
|
||||
this.iframe.classList.add("pixel");
|
||||
this.state.set("ready");
|
||||
return resolve(iframe);
|
||||
})
|
||||
.catch((err) => {
|
||||
return reject(err);
|
||||
});
|
||||
|
||||
cancel(() => {
|
||||
jitsiLoading.cancel();
|
||||
this.unload().catch((err) => {
|
||||
console.error("Cannot unload Jitsi co-website while cancel loading", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
unload(): Promise<void> {
|
||||
jitsiFactory.destroy();
|
||||
gameManager.getCurrentGameScene().enableMediaBehaviors();
|
||||
|
||||
return super.unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { get, Readable, writable, Writable } from "svelte/store";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { coWebsiteManager } from "../CoWebsiteManager";
|
||||
import type { CoWebsite, CoWebsiteState } from "./CoWesbite";
|
||||
|
||||
export class SimpleCoWebsite implements CoWebsite {
|
||||
protected id: string;
|
||||
protected url: URL;
|
||||
protected state: Writable<CoWebsiteState>;
|
||||
protected iframe?: HTMLIFrameElement;
|
||||
protected loadIframe?: CancelablePromise<HTMLIFrameElement>;
|
||||
protected allowApi?: boolean;
|
||||
protected allowPolicy?: string;
|
||||
protected widthPercent?: number;
|
||||
protected closable: boolean;
|
||||
|
||||
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) {
|
||||
this.id = coWebsiteManager.generateUniqueId();
|
||||
this.url = url;
|
||||
this.state = writable("asleep" as CoWebsiteState);
|
||||
this.allowApi = allowApi;
|
||||
this.allowPolicy = allowPolicy;
|
||||
this.widthPercent = widthPercent;
|
||||
this.closable = closable ?? false;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getUrl(): URL {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
getState(): CoWebsiteState {
|
||||
return get(this.state);
|
||||
}
|
||||
|
||||
getStateSubscriber(): Readable<CoWebsiteState> {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
getIframe(): HTMLIFrameElement | undefined {
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined {
|
||||
return this.loadIframe;
|
||||
}
|
||||
|
||||
getWidthPercent(): number | undefined {
|
||||
return this.widthPercent;
|
||||
}
|
||||
|
||||
isClosable(): boolean {
|
||||
return this.closable;
|
||||
}
|
||||
|
||||
load(): CancelablePromise<HTMLIFrameElement> {
|
||||
this.loadIframe = new CancelablePromise((resolve, reject, cancel) => {
|
||||
this.state.set("loading");
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
this.iframe = iframe;
|
||||
this.iframe.src = this.url.toString();
|
||||
this.iframe.id = this.id;
|
||||
|
||||
if (this.allowPolicy) {
|
||||
this.iframe.allow = this.allowPolicy;
|
||||
}
|
||||
|
||||
if (this.allowApi) {
|
||||
iframeListener.registerIframe(this.iframe);
|
||||
}
|
||||
|
||||
this.iframe.classList.add("pixel");
|
||||
|
||||
const onloadPromise = new Promise<void>((resolve) => {
|
||||
if (this.iframe) {
|
||||
this.iframe.onload = () => {
|
||||
this.state.set("ready");
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const onTimeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
||||
coWebsiteManager.getCoWebsiteBuffer().appendChild(this.iframe);
|
||||
|
||||
const race = CancelablePromise.race([onloadPromise, onTimeoutPromise])
|
||||
.then(() => {
|
||||
return resolve(iframe);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error on co-website loading => ", err);
|
||||
return reject();
|
||||
});
|
||||
|
||||
cancel(() => {
|
||||
race.cancel();
|
||||
this.unload().catch((err) => {
|
||||
console.error("Cannot unload co-website while cancel loading", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return this.loadIframe;
|
||||
}
|
||||
|
||||
unload(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (this.iframe) {
|
||||
if (this.allowApi) {
|
||||
iframeListener.unregisterIframe(this.iframe);
|
||||
}
|
||||
this.iframe.parentNode?.removeChild(this.iframe);
|
||||
}
|
||||
|
||||
if (this.loadIframe) {
|
||||
this.loadIframe.cancel();
|
||||
this.loadIframe = undefined;
|
||||
}
|
||||
|
||||
this.state.set("asleep");
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user