diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index 2743d1ad..8d9fe595 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -3,7 +3,7 @@ ### Opening a web page in a new tab -``` +```ts WA.nav.openTab(url: string): void ``` @@ -11,13 +11,13 @@ Opens the webpage at "url" in your browser, in a new tab. Example: -```javascript +```ts WA.nav.openTab('https://www.wikipedia.org/'); ``` ### Opening a web page in the current tab -``` +```ts WA.nav.goToPage(url: string): void ``` @@ -25,14 +25,13 @@ Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdvent Example: -```javascript +```ts WA.nav.goToPage('https://www.wikipedia.org/'); ``` ### Going to a different map from the script -``` - +```ts WA.nav.goToRoom(url: string): void ``` @@ -43,7 +42,7 @@ global urls: "/_/global/domain/path/map.json[#start-layer-name]" Example: -```javascript +```ts WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls WA.nav.goToRoom('../otherMap/map.json'); WA.nav.goToRoom("/_/global/.json#start-layer-2") @@ -51,25 +50,25 @@ WA.nav.goToRoom("/_/global/.json#start-layer-2") ### Opening/closing web page in Co-Websites -``` -WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number, closable: boolean, lazy: boolean): Promise +```ts +WA.nav.openCoWebSite(url: string, allowApi?: boolean = false, allowPolicy?: string = "", percentWidth?: number, position?: number, closable?: boolean, lazy?: boolean): Promise ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow),widthPercent define the width of the main cowebsite beetween the min size and the max size (70% of the viewport), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy it's to add the cowebsite but don't load it. Example: -```javascript +```ts const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/'); -const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1, true, true); +const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 70, 1, true, true); // ... coWebsite.close(); ``` ### Get all Co-Websites -``` +```ts WA.nav.getCoWebSites(): Promise ``` @@ -77,6 +76,6 @@ Get all opened co-websites with their ids and positions. Example: -```javascript +```ts const coWebsites = await WA.nav.getCowebSites(); ``` diff --git a/docs/maps/images/icon_open_website.png b/docs/maps/images/icon_open_website.png new file mode 100644 index 00000000..f3a505bd Binary files /dev/null and b/docs/maps/images/icon_open_website.png differ diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md index 64b19f1c..a84bde30 100644 --- a/docs/maps/opening-a-website.md +++ b/docs/maps/opening-a-website.md @@ -52,6 +52,13 @@ If you set `openWebsiteTrigger: onaction`, when the user walks on the layer, an If you set `openWebsiteTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to open the web site'. +If you set `openWebsiteTrigger: onicon`, when the user walks on the layer, an icon will be displayed at the bottom of the screen: + +
+ +
The iFrame will only open if the user clicks on icon
+
+ ### Setting the iFrame "allow" attribute By default, iFrames have limited rights in browsers. For instance, they cannot put their content in fullscreen, they cannot start your webcam, etc... diff --git a/front/.prettierrc.json b/front/.prettierrc.json index 057ed062..e8980d15 100644 --- a/front/.prettierrc.json +++ b/front/.prettierrc.json @@ -1,5 +1,4 @@ { "printWidth": 120, - "tabWidth": 4, - "plugins": ["prettier-plugin-svelte"] + "tabWidth": 4 } diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts index 51a17763..b991d3f7 100644 --- a/front/src/Api/Events/OpenCoWebsiteEvent.ts +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -5,6 +5,7 @@ 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), diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index d5362b4b..b57f2456 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -45,6 +45,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution 1 ? coWebsites[1] : undefined; - if (newMain) { + if (newMain && newMain.iframe.id !== $mainCoWebsite.iframe.id) { coWebsiteManager.goToMain(newMain); + } else if (coWebsiteManager.getMainState() === iframeStates.closed) { + coWebsiteManager.displayMain(); + } else { + coWebsiteManager.hideMain(); } } else { highlightedEmbedScreen.toggleHighlight({ diff --git a/front/src/Components/Login/LoginScene.svelte b/front/src/Components/Login/LoginScene.svelte index 4efbc514..0576ac9f 100644 --- a/front/src/Components/Login/LoginScene.svelte +++ b/front/src/Components/Login/LoginScene.svelte @@ -53,7 +53,7 @@
Need for traduction

- {$LL.login.terms()} + {@html $LL.login.terms()}

{/if} diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte index 69cfa25e..08957a5c 100644 --- a/front/src/Components/Menu/Menu.svelte +++ b/front/src/Components/Menu/Menu.svelte @@ -69,6 +69,7 @@ } else { const customMenu = customMenuIframe.get(menu.label); if (customMenu !== undefined) { + activeSubMenu = menu; props = { url: customMenu.url, allowApi: customMenu.allowApi }; activeComponent = CustomSubMenu; } else { diff --git a/front/src/Interfaces/UserInputHandlerInterface.ts b/front/src/Interfaces/UserInputHandlerInterface.ts index cf7b2f1c..2a8c6b3e 100644 --- a/front/src/Interfaces/UserInputHandlerInterface.ts +++ b/front/src/Interfaces/UserInputHandlerInterface.ts @@ -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; } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 5ec031bd..b411eee7 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -159,6 +159,27 @@ export abstract class Character extends Container implements OutlineableInterfac 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; } @@ -455,16 +476,16 @@ export abstract class Character extends Container implements OutlineableInterfac this.outlineColorStore.removeApiColor(); } - public pointerOverOutline(): void { - this.outlineColorStore.pointerOver(); + public pointerOverOutline(color: number): void { + this.outlineColorStore.pointerOver(color); } public pointerOutOutline(): void { this.outlineColorStore.pointerOut(); } - public characterCloseByOutline(): void { - this.outlineColorStore.characterCloseBy(); + public characterCloseByOutline(color: number): void { + this.outlineColorStore.characterCloseBy(color); } public characterFarAwayOutline(): void { diff --git a/front/src/Phaser/Game/ActivatablesManager.ts b/front/src/Phaser/Game/ActivatablesManager.ts index 60e967d9..74c637d7 100644 --- a/front/src/Phaser/Game/ActivatablesManager.ts +++ b/front/src/Phaser/Game/ActivatablesManager.ts @@ -11,6 +11,11 @@ export class ActivatablesManager { private currentPlayer: Player; + private canSelectByDistance: boolean = true; + + private readonly outlineColor = 0xffff00; + private readonly directionalActivationPositionShift = 50; + constructor(currentPlayer: Player) { this.currentPlayer = currentPlayer; } @@ -27,7 +32,7 @@ export class ActivatablesManager { } this.selectedActivatableObjectByPointer = object; if (isOutlineable(this.selectedActivatableObjectByPointer)) { - this.selectedActivatableObjectByPointer?.pointerOverOutline(); + this.selectedActivatableObjectByPointer?.pointerOverOutline(this.outlineColor); } } @@ -37,7 +42,7 @@ export class ActivatablesManager { } this.selectedActivatableObjectByPointer = undefined; if (isOutlineable(this.selectedActivatableObjectByDistance)) { - this.selectedActivatableObjectByDistance?.characterCloseByOutline(); + this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor); } } @@ -46,6 +51,9 @@ export class ActivatablesManager { } public deduceSelectedActivatableObjectByDistance(): void { + if (!this.canSelectByDistance) { + return; + } const newNearestObject = this.findNearestActivatableObject(); if (this.selectedActivatableObjectByDistance === newNearestObject) { return; @@ -60,10 +68,42 @@ export class ActivatablesManager { } this.selectedActivatableObjectByDistance = newNearestObject; if (isOutlineable(this.selectedActivatableObjectByDistance)) { - this.selectedActivatableObjectByDistance?.characterCloseByOutline(); + 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; @@ -76,18 +116,8 @@ export class ActivatablesManager { } return closestObject; } - public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void { - const currentPlayerPos = this.currentPlayer.getPosition(); - 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.getPosition(), object.getPosition()) - ); + public isSelectingByDistanceEnabled(): boolean { + return this.canSelectByDistance; } } diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts index 865026f7..783f2348 100644 --- a/front/src/Phaser/Game/Game.ts +++ b/front/src/Phaser/Game/Game.ts @@ -26,15 +26,6 @@ export class Game extends Phaser.Game { } } }); - - /*window.addEventListener('resize', (event) => { - // Let's trigger the onResize method of any active scene that is a ResizableScene - for (const scene of this.scene.getScenes(true)) { - if (scene instanceof ResizableScene) { - scene.onResize(event); - } - } - });*/ } public step(time: number, delta: number) { diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 56627ec3..b77bd02b 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -20,6 +20,7 @@ export enum GameMapProperties { OPEN_WEBSITE = "openWebsite", OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi", OPEN_WEBSITE_POLICY = "openWebsitePolicy", + OPEN_WEBSITE_WIDTH = "openWebsiteWidth", OPEN_WEBSITE_POSITION = "openWebsitePosition", OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger", OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage", diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index caa83cb0..497b6cbc 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -6,10 +6,9 @@ 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 } 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 { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; enum OpenCoWebsiteState { ASLEEP, @@ -18,12 +17,14 @@ enum OpenCoWebsiteState { } interface OpenCoWebsite { - coWebsite: CoWebsite; + actionId: string; + coWebsite?: CoWebsite; state: OpenCoWebsiteState; } export class GameMapPropertiesListener { private coWebsitesOpenByLayer = new Map(); + private coWebsitesActionTriggerByLayer = new Map(); constructor(private scene: GameScene, private gameMap: GameMap) {} @@ -64,6 +65,7 @@ export class GameMapPropertiesListener { let openWebsiteProperty: string | undefined; let allowApiProperty: boolean | undefined; let websitePolicyProperty: string | undefined; + let websiteWidthProperty: number | undefined; let websitePositionProperty: number | undefined; let websiteTriggerProperty: string | undefined; let websiteTriggerMessageProperty: string | undefined; @@ -79,6 +81,9 @@ export class GameMapPropertiesListener { case GameMapProperties.OPEN_WEBSITE_POLICY: websitePolicyProperty = property.value as string | undefined; break; + case GameMapProperties.OPEN_WEBSITE_WIDTH: + websiteWidthProperty = property.value as number | undefined; + break; case GameMapProperties.OPEN_WEBSITE_POSITION: websitePositionProperty = property.value as number | undefined; break; @@ -95,27 +100,19 @@ 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; } - const coWebsite = coWebsiteManager.addCoWebsite( - openWebsiteProperty, - this.scene.MapUrlFile, - allowApiProperty, - websitePolicyProperty, - websitePositionProperty, - false - ); - this.coWebsitesOpenByLayer.set(layer, { - coWebsite: coWebsite, + actionId: actionId, + coWebsite: undefined, state: OpenCoWebsiteState.ASLEEP, }); - const openWebsiteFunction = () => { + const loadCoWebsiteFunction = (coWebsite: CoWebsite) => { coWebsiteManager .loadCoWebsite(coWebsite) .then((coWebsite) => { @@ -125,8 +122,10 @@ export class GameMapPropertiesListener { console.error("Error during a co-website closing"); }); this.coWebsitesOpenByLayer.delete(layer); + this.coWebsitesActionTriggerByLayer.delete(layer); } else { this.coWebsitesOpenByLayer.set(layer, { + actionId, coWebsite, state: OpenCoWebsiteState.OPENED, }); @@ -136,14 +135,60 @@ export class GameMapPropertiesListener { console.error("Error during loading a co-website: " + coWebsite.url); }); - layoutManagerActionStore.removeAction(actionUuid); + layoutManagerActionStore.removeAction(actionId); + }; + + const openCoWebsiteFunction = () => { + const coWebsite = coWebsiteManager.addCoWebsite( + openWebsiteProperty ?? "", + this.scene.MapUrlFile, + allowApiProperty, + websitePolicyProperty, + websiteWidthProperty, + websitePositionProperty, + false + ); + + loadCoWebsiteFunction(coWebsite); }; if ( - !localUserStore.getForceCowebsiteTrigger() && - websiteTriggerProperty !== ON_ACTION_TRIGGER_BUTTON + localUserStore.getForceCowebsiteTrigger() || + websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON ) { - openWebsiteFunction(); + if (!websiteTriggerMessageProperty) { + websiteTriggerMessageProperty = "Press SPACE or touch here to open web site"; + } + + this.coWebsitesActionTriggerByLayer.set(layer, actionId); + + layoutManagerActionStore.addAction({ + uuid: actionId, + type: "message", + message: websiteTriggerMessageProperty, + callback: () => openCoWebsiteFunction(), + userInputManager: this.scene.userInputManager, + }); + } else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) { + const coWebsite = coWebsiteManager.addCoWebsite( + openWebsiteProperty, + this.scene.MapUrlFile, + allowApiProperty, + websitePolicyProperty, + websiteWidthProperty, + websitePositionProperty, + false + ); + + const ObjectByLayer = this.coWebsitesOpenByLayer.get(layer); + + if (ObjectByLayer) { + ObjectByLayer.coWebsite = coWebsite; + } + } + + if (!websiteTriggerProperty) { + openCoWebsiteFunction(); } }); }; @@ -192,6 +237,28 @@ export class GameMapPropertiesListener { } this.coWebsitesOpenByLayer.delete(layer); + + if (!websiteTriggerProperty) { + return; + } + + const actionStore = get(layoutManagerActionStore); + const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer); + + if (!actionTriggerUuid) { + return; + } + + const action = + actionStore && actionStore.length > 0 + ? actionStore.find((action) => action.uuid === actionTriggerUuid) + : undefined; + + if (action) { + layoutManagerActionStore.removeAction(actionTriggerUuid); + } + + this.coWebsitesActionTriggerByLayer.delete(layer); }); }; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 242d97c8..9b758a45 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1265,6 +1265,7 @@ ${escapedMessage} iframeListener.getBaseUrlFromSource(source), openCoWebsite.allowApi, openCoWebsite.allowPolicy, + openCoWebsite.widthPercent, openCoWebsite.position, openCoWebsite.closable ?? true ); @@ -1737,6 +1738,12 @@ ${escapedMessage} 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); diff --git a/front/src/Phaser/Game/OutlineableInterface.ts b/front/src/Phaser/Game/OutlineableInterface.ts index bee560cc..7112fe84 100644 --- a/front/src/Phaser/Game/OutlineableInterface.ts +++ b/front/src/Phaser/Game/OutlineableInterface.ts @@ -3,8 +3,8 @@ export interface OutlineableInterface { removeFollowOutlineColor(): void; setApiOutlineColor(color: number): void; removeApiOutlineColor(): void; - pointerOverOutline(): void; + pointerOverOutline(color: number): void; pointerOutOutline(): void; - characterCloseByOutline(): void; + characterCloseByOutline(color: number): void; characterFarAwayOutline(): void; } diff --git a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts index 4d9ac8a9..fc9e83cf 100644 --- a/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts +++ b/front/src/Phaser/UserInput/GameSceneUserInputHandler.ts @@ -53,10 +53,20 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface { public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {} public handleSpaceKeyUpEvent(event: Event): Event { - const activatable = this.gameScene.getActivatablesManager().getSelectedActivatableObject(); - if (activatable && activatable.isActivatable()) { + 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(); + } } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index ffa67c3a..e7f814b9 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -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 { @@ -255,6 +255,11 @@ export class UserInputManager { (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => { this.joystick?.hide(); this.userInputHandler.handlePointerUpEvent(pointer, gameObjects); + + // Disable focus on iframe (need by Firefox) + if (pointer.downElement.nodeName === "CANVAS" && document.activeElement instanceof HTMLIFrameElement) { + document.activeElement.blur(); + } } ); diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts index b6f428aa..e0f8d955 100644 --- a/front/src/Stores/LayoutManagerStore.ts +++ b/front/src/Stores/LayoutManagerStore.ts @@ -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 { diff --git a/front/src/Stores/OutlineColorStore.ts b/front/src/Stores/OutlineColorStore.ts index a35cc9c9..8ecd7293 100644 --- a/front/src/Stores/OutlineColorStore.ts +++ b/front/src/Stores/OutlineColorStore.ts @@ -5,38 +5,33 @@ export function createColorStore() { let followColor: number | undefined = undefined; let apiColor: number | undefined = undefined; - - let pointedByPointer: boolean = false; - let pointedByCharacter: boolean = false; + let pointedByPointer: number | undefined = undefined; + let pointedByCharacter: number | undefined = undefined; const updateColor = () => { - if (pointedByPointer || pointedByCharacter) { - set(0xffff00); - } else { - set(followColor ?? apiColor); - } + set(pointedByPointer ?? pointedByCharacter ?? followColor ?? apiColor); }; return { subscribe, - pointerOver() { - pointedByPointer = true; + pointerOver(color: number) { + pointedByPointer = color; updateColor(); }, pointerOut() { - pointedByPointer = false; + pointedByPointer = undefined; updateColor(); }, - characterCloseBy() { - pointedByCharacter = true; + characterCloseBy(color: number) { + pointedByCharacter = color; updateColor(); }, characterFarAway() { - pointedByCharacter = false; + pointedByCharacter = undefined; updateColor(); }, diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 8e646d9d..476526da 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -10,7 +10,7 @@ import { jitsiFactory } from "./JitsiFactory"; import { gameManager } from "../Phaser/Game/GameManager"; import { LayoutMode } from "./LayoutManager"; -enum iframeStates { +export enum iframeStates { closed = 1, loading, // loading an iframe can be slow, so we show some placeholder until it is ready opened, @@ -21,7 +21,7 @@ const gameOverlayDomId = "game-overlay"; const cowebsiteBufferDomId = "cowebsite-buffer"; // the id of the container who contains cowebsite iframes. const cowebsiteAsideHolderDomId = "cowebsite-aside-holder"; const cowebsiteLoaderDomId = "cowebsite-loader"; -export const cowebsiteCloseButtonId = "cowebsite-close"; +const cowebsiteCloseButtonId = "cowebsite-close"; const cowebsiteFullScreenButtonId = "cowebsite-fullscreen"; const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open"; const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close"; @@ -43,6 +43,7 @@ export type CoWebsite = { closable: boolean; allowPolicy: string | undefined; allowApi: boolean | undefined; + widthPercent?: number | undefined; jitsi?: boolean; altMessage?: string; }; @@ -74,6 +75,10 @@ class CoWebsiteManager { this.resizeAllIframes(); }); + public getMainState() { + return this.openedMain; + } + get width(): number { return this.cowebsiteDom.clientWidth; } @@ -82,8 +87,13 @@ class CoWebsiteManager { this.cowebsiteDom.style.width = width + "px"; } - set widthPercent(width: number) { - this.cowebsiteDom.style.width = width + "%"; + get maxWidth(): number { + let maxWidth = 75 * window.innerWidth; + if (maxWidth !== 0) { + maxWidth = Math.round(maxWidth / 100); + } + + return maxWidth; } get height(): number { @@ -94,6 +104,15 @@ class CoWebsiteManager { this.cowebsiteDom.style.height = height + "px"; } + get maxHeight(): number { + let maxHeight = 60 * window.innerHeight; + if (maxHeight !== 0) { + maxHeight = Math.round(maxHeight / 100); + } + + return maxHeight; + } + get verticalMode(): boolean { return window.innerWidth < window.innerHeight; } @@ -191,29 +210,21 @@ class CoWebsiteManager { if (this.verticalMode) { const tempValue = this.height + y; - let maxHeight = 60 * window.innerHeight; - if (maxHeight !== 0) { - maxHeight = Math.round(maxHeight / 100); - } if (tempValue < this.cowebsiteAsideHolderDom.offsetHeight) { this.height = this.cowebsiteAsideHolderDom.offsetHeight; - } else if (tempValue > maxHeight) { - this.height = maxHeight; + } else if (tempValue > this.maxHeight) { + this.height = this.maxHeight; } else { this.height = tempValue; } } else { const tempValue = this.width - x; - let maxWidth = 75 * window.innerWidth; - if (maxWidth !== 0) { - maxWidth = Math.round(maxWidth / 100); - } if (tempValue < this.cowebsiteAsideHolderDom.offsetWidth) { this.width = this.cowebsiteAsideHolderDom.offsetWidth; - } else if (tempValue > maxWidth) { - this.width = maxWidth; + } else if (tempValue > this.maxWidth) { + this.width = this.maxWidth; } else { this.width = tempValue; } @@ -299,6 +310,27 @@ class CoWebsiteManager { }); } + public displayMain() { + const coWebsite = this.getMainCoWebsite(); + if (coWebsite) { + coWebsite.iframe.style.display = "block"; + } + this.loadMain(); + this.openMain(); + this.fire(); + } + + public hideMain() { + const coWebsite = this.getMainCoWebsite(); + if (coWebsite) { + coWebsite.iframe.style.display = "none"; + } + this.cowebsiteDom.classList.add("closing"); + this.cowebsiteDom.classList.remove("opened"); + this.openedMain = iframeStates.closed; + this.fire(); + } + private closeMain(): void { this.toggleFullScreenIcon(true); this.cowebsiteDom.classList.add("closing"); @@ -308,7 +340,7 @@ class CoWebsiteManager { this.fire(); } - private loadMain(): void { + private loadMain(openingWidth?: number): void { this.loaderAnimationInterval.interval = setInterval(() => { if (!this.loaderAnimationInterval.trails) { this.loaderAnimationInterval.trails = [0, 1, 2]; @@ -337,6 +369,25 @@ class CoWebsiteManager { trail === 3 ? 0 : trail + 1 ); }, 200); + + if (!this.verticalMode && openingWidth) { + let newWidth = 50; + + if (openingWidth > 100) { + newWidth = 100; + } else if (openingWidth > 1) { + newWidth = openingWidth; + } + + newWidth = Math.round((newWidth * this.maxWidth) / 100); + + if (newWidth < this.cowebsiteAsideHolderDom.offsetWidth) { + newWidth = this.cowebsiteAsideHolderDom.offsetWidth; + } + + this.width = newWidth; + } + this.cowebsiteDom.classList.add("opened"); this.openedMain = iframeStates.loading; } @@ -346,7 +397,6 @@ class CoWebsiteManager { this.resizeAllIframes(); }); this.openedMain = iframeStates.opened; - this.resetStyleMain(); } public resetStyleMain() { @@ -533,6 +583,7 @@ class CoWebsiteManager { base: string, allowApi?: boolean, allowPolicy?: string, + widthPercent?: number, position?: number, closable?: boolean, altMessage?: string @@ -549,6 +600,7 @@ class CoWebsiteManager { closable: closable ?? false, allowPolicy, allowApi, + widthPercent, altMessage, }; @@ -561,12 +613,13 @@ class CoWebsiteManager { iframe: HTMLIFrameElement, allowApi?: boolean, allowPolicy?: string, + widthPercent?: number, position?: number, closable?: boolean, jitsi?: boolean ): CoWebsite { if (get(coWebsitesNotAsleep).length < 1) { - this.loadMain(); + this.loadMain(widthPercent); } iframe.id = this.generateUniqueId(); @@ -578,6 +631,7 @@ class CoWebsiteManager { closable: closable ?? false, allowPolicy, allowApi, + widthPercent, jitsi, }; @@ -597,7 +651,12 @@ class CoWebsiteManager { if (get(coWebsitesNotAsleep).length < 1) { coWebsites.remove(coWebsite); coWebsites.add(coWebsite, 0); - this.loadMain(); + this.loadMain(coWebsite.widthPercent); + } + + // Check if the main is hide + if (this.getMainCoWebsite() && this.openedMain === iframeStates.closed) { + this.displayMain(); } coWebsite.state.set("loading"); diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index b273a64c..8f7ed952 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -179,7 +179,15 @@ class JitsiFactory { const doResolve = (): void => { const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector('[id*="jitsi" i]'); if (iframe && this.jitsiApi) { - const coWebsite = coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true); + const coWebsite = coWebsiteManager.addCoWebsiteFromIframe( + iframe, + false, + undefined, + undefined, + 0, + false, + true + ); this.jitsiApi.addListener("videoConferenceLeft", () => { this.closeOrUnload(coWebsite); diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index de79047f..a926cb41 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -13,5 +13,6 @@ export enum DivImportance { } export const ON_ACTION_TRIGGER_BUTTON = "onaction"; +export const ON_ICON_TRIGGER_BUTTON = "onicon"; export type Box = { xStart: number; yStart: number; xEnd: number; yEnd: number }; diff --git a/maps/tests/CoWebsite/cowebsite_property_trigger.json b/maps/tests/CoWebsite/cowebsite_property_trigger.json index 183fad3a..116f399c 100644 --- a/maps/tests/CoWebsite/cowebsite_property_trigger.json +++ b/maps/tests/CoWebsite/cowebsite_property_trigger.json @@ -47,6 +47,11 @@ "name":"openWebsiteTrigger", "type":"string", "value":"onaction" + }, + { + "name":"openWebsiteWidth", + "type":"int", + "value":100 }], "type":"tilelayer", "visible":true, diff --git a/yarn.lock b/yarn.lock index b9698f61..adbed748 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,7 @@ # yarn lockfile v1 -"husky@^6.0.0": - "resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz" - "version" "6.0.0" +husky@^7.0.1: + version "7.0.4" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==