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

This commit is contained in:
GRL 2021-07-01 11:38:33 +02:00
commit 01d02124d1
27 changed files with 1151 additions and 806 deletions

View file

@ -2,6 +2,7 @@ name: Push @workadventure/iframe-api-typings to NPM
on: on:
release: release:
types: [created] types: [created]
push:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -13,10 +14,6 @@ jobs:
node-version: '14.x' node-version: '14.x'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
- name: Edit tsconfig.json to add declarations
run: "sed -i 's/\"declaration\": false/\"declaration\": true/g' tsconfig.json"
working-directory: "front"
- name: Replace version number - name: Replace version number
run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json' run: 'sed -i "s#VERSION_PLACEHOLDER#${GITHUB_REF/refs\/tags\//}#g" package.json'
working-directory: "front/packages/iframe-api-typings" working-directory: "front/packages/iframe-api-typings"
@ -47,15 +44,18 @@ jobs:
working-directory: "front" working-directory: "front"
- name: "Build" - name: "Build"
run: yarn run build run: yarn run build-typings
env: env:
API_URL: "localhost:8080" PUSHER_URL: "//localhost:8080"
working-directory: "front" working-directory: "front"
# We build the front to generate the typings of iframe_api, then we copy those typings in a separate package. # We build the front to generate the typings of iframe_api, then we copy those typings in a separate package.
- name: Copy typings to package dir - name: Copy typings to package dir
run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts run: cp front/dist/src/iframe_api.d.ts front/packages/iframe-api-typings/iframe_api.d.ts
- name: Copy typings to package dir (2)
run: cp -R front/dist/src/Api front/packages/iframe-api-typings/Api
- name: Install dependencies in package - name: Install dependencies in package
run: yarn install run: yarn install
working-directory: "front/packages/iframe-api-typings" working-directory: "front/packages/iframe-api-typings"
@ -65,3 +65,4 @@ jobs:
working-directory: "front/packages/iframe-api-typings" working-directory: "front/packages/iframe-api-typings"
env: env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ github.event_name == 'release' }}

View file

@ -17,6 +17,18 @@
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.ui.registerMenuCommand(): void` to add a custom menu
- Use `WA.room.setTiles(): void` to change an array of tiles - Use `WA.room.setTiles(): void` to change an array of tiles
## Version 1.4.3 - 1.4.4 - 1.4.5
## Bugfixes
- Fixing the generation of @workadventure/iframe-api-typings
## Version 1.4.2
## Updates
- A script in an iframe opened by another script can use the IFrame API.
## Version 1.4.1 ## Version 1.4.1
### Bugfixes ### Bugfixes

View file

@ -52,11 +52,11 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
### Opening/closing a web page in an iFrame ### Opening/closing a web page in an iFrame
``` ```
WA.nav.openCoWebSite(url: string): void WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void
WA.nav.closeCoWebSite(): void WA.nav.closeCoWebSite(): void
``` ```
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. 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).
Example: Example:
@ -65,4 +65,3 @@ WA.nav.openCoWebSite('https://www.wikipedia.org/');
// ... // ...
WA.nav.closeCoWebSite(); WA.nav.closeCoWebSite();
``` ```

View file

@ -9,4 +9,4 @@
- [Sound functions](api-sound.md) - [Sound functions](api-sound.md)
- [Controls functions](api-controls.md) - [Controls functions](api-controls.md)
- [List of deprecated functions](api-deprecated.md) - [List of deprecated functions](api-deprecated.md)

View file

@ -60,6 +60,7 @@
"templater": "cross-env ./templater.sh", "templater": "cross-env ./templater.sh",
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
"build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack",
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts", "lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",

View file

@ -5,6 +5,8 @@ import * as tg from "generic-type-guard";
export const isOpenCoWebsite = export const isOpenCoWebsite =
new tg.IsInterface().withProperties({ new tg.IsInterface().withProperties({
url: tg.isString, url: tg.isString,
allowApi: tg.isBoolean,
allowPolicy: tg.isString,
}).get(); }).get();
/** /**

View file

@ -138,6 +138,8 @@ class IframeListener {
return; return;
} }
foundSrc = this.getBaseUrl(foundSrc, message.source);
if (isIframeEventWrapper(payload)) { if (isIframeEventWrapper(payload)) {
if (payload.type === 'showLayer' && isLayerEvent(payload.data)) { if (payload.type === 'showLayer' && isLayerEvent(payload.data)) {
this._showLayerStream.next(payload.data); this._showLayerStream.next(payload.data);
@ -171,7 +173,7 @@ class IframeListener {
this._loadSoundStream.next(payload.data); this._loadSoundStream.next(payload.data);
} }
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) { else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
scriptUtils.openCoWebsite(payload.data.url, foundSrc); scriptUtils.openCoWebsite(payload.data.url, foundSrc, payload.data.allowApi, payload.data.allowPolicy);
} }
else if (payload.type === 'closeCoWebSite') { else if (payload.type === 'closeCoWebSite') {
@ -286,6 +288,15 @@ class IframeListener {
} }
private getBaseUrl(src: string, source: MessageEventSource | null): string{
for (const script of this.scripts) {
if (script[1].contentWindow === source) {
return script[0];
}
}
return src;
}
private static getIFrameId(scriptUrl: string): string { private static getIFrameId(scriptUrl: string): string {
return 'script' + btoa(scriptUrl); return 'script' + btoa(scriptUrl);
} }

View file

@ -11,8 +11,8 @@ class ScriptUtils {
} }
public openCoWebsite(url: string, base: string) { public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
coWebsiteManager.loadCoWebsite(url, base); coWebsiteManager.loadCoWebsite(url, base, api, policy);
} }
public closeCoWebSite(){ public closeCoWebSite(){

View file

@ -1,30 +1,30 @@
import type { ChatEvent } from '../Events/ChatEvent' import type { ChatEvent } from "../Events/ChatEvent";
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent' import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution' import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import {Subject} from "rxjs"; import { Subject } from "rxjs";
const chatStream = new Subject<string>(); const chatStream = new Subject<string>();
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> { export class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
callbacks = [
callbacks = [apiCallback({ apiCallback({
callback: (event: UserInputChatEvent) => { callback: (event: UserInputChatEvent) => {
chatStream.next(event.message); chatStream.next(event.message);
}, },
type: "userInputChat", type: "userInputChat",
typeChecker: isUserInputChatEvent typeChecker: isUserInputChatEvent,
})] }),
];
sendChatMessage(message: string, author: string) { sendChatMessage(message: string, author: string) {
sendToWorkadventure({ sendToWorkadventure({
type: 'chat', type: "chat",
data: { data: {
'message': message, message: message,
'author': author author: author,
} },
}) });
} }
/** /**
@ -35,4 +35,4 @@ class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatC
} }
} }
export default new WorkadventureChatCommands() export default new WorkadventureChatCommands();

View file

@ -1,16 +1,15 @@
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> { export class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
callbacks = [] callbacks = [];
disablePlayerControls(): void { disablePlayerControls(): void {
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null }); sendToWorkadventure({ type: "disablePlayerControls", data: null });
} }
restorePlayerControls(): void { restorePlayerControls(): void {
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null }); sendToWorkadventure({ type: "restorePlayerControls", data: null });
} }
} }
export default new WorkadventureControlsCommands(); export default new WorkadventureControlsCommands();

View file

@ -1,57 +1,56 @@
import type { GoToPageEvent } from '../Events/GoToPageEvent'; import type { GoToPageEvent } from "../Events/GoToPageEvent";
import type { OpenTabEvent } from '../Events/OpenTabEvent'; import type { OpenTabEvent } from "../Events/OpenTabEvent";
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent"; import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
import type {LoadPageEvent} from "../Events/LoadPageEvent"; import type { LoadPageEvent } from "../Events/LoadPageEvent";
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = []
export class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = [];
openTab(url: string): void { openTab(url: string): void {
sendToWorkadventure({ sendToWorkadventure({
"type": 'openTab', type: "openTab",
"data": { data: {
url url,
} },
}); });
} }
goToPage(url: string): void { goToPage(url: string): void {
sendToWorkadventure({ sendToWorkadventure({
"type": 'goToPage', type: "goToPage",
"data": { data: {
url url,
} },
}); });
} }
goToRoom(url: string): void { goToRoom(url: string): void {
sendToWorkadventure({ sendToWorkadventure({
"type": 'loadPage', type: "loadPage",
"data": { data: {
url url,
} },
}); });
} }
openCoWebSite(url: string): void { openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
sendToWorkadventure({ sendToWorkadventure({
"type": 'openCoWebSite', type: "openCoWebSite",
"data": { data: {
url url,
} allowApi,
allowPolicy,
},
}); });
} }
closeCoWebSite(): void { closeCoWebSite(): void {
sendToWorkadventure({ sendToWorkadventure({
"type": 'closeCoWebSite', type: "closeCoWebSite",
data: null data: null,
}); });
} }
} }
export default new WorkadventureNavigationCommands(); export default new WorkadventureNavigationCommands();

View file

@ -1,29 +1,29 @@
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution"; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent"; import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events/HasPlayerMovedEvent";
import {Subject} from "rxjs"; import { Subject } from "rxjs";
import {apiCallback} from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
const moveStream = new Subject<HasPlayerMovedEvent>(); const moveStream = new Subject<HasPlayerMovedEvent>();
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> { export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
callbacks = [ callbacks = [
apiCallback({ apiCallback({
type: 'hasPlayerMoved', type: "hasPlayerMoved",
typeChecker: isHasPlayerMovedEvent, typeChecker: isHasPlayerMovedEvent,
callback: (payloadData) => { callback: (payloadData) => {
moveStream.next(payloadData); moveStream.next(payloadData);
} },
}), }),
] ];
onPlayerMove(callback: HasPlayerMovedEventCallback): void { onPlayerMove(callback: HasPlayerMovedEventCallback): void {
moveStream.subscribe(callback); moveStream.subscribe(callback);
sendToWorkadventure({ sendToWorkadventure({
type: 'onPlayerMove', type: "onPlayerMove",
data: null data: null,
}) });
} }
} }
export default new WorkadventurePlayerCommands(); export default new WorkadventurePlayerCommands();

View file

@ -56,7 +56,7 @@ function getDataLayer(): Promise<DataLayerEvent> {
}); });
} }
class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> { export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
callbacks = [ callbacks = [
apiCallback({ apiCallback({
callback: (payloadData: EnterLeaveEvent) => { callback: (payloadData: EnterLeaveEvent) => {

View file

@ -1,17 +1,15 @@
import type { LoadSoundEvent } from '../Events/LoadSoundEvent'; import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
import type { PlaySoundEvent } from '../Events/PlaySoundEvent'; import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
import type { StopSoundEvent } from '../Events/StopSoundEvent'; import type { StopSoundEvent } from "../Events/StopSoundEvent";
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import {Sound} from "./Sound/Sound"; import { Sound } from "./Sound/Sound";
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> { export class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
callbacks = [] callbacks = [];
loadSound(url: string): Sound { loadSound(url: string): Sound {
return new Sound(url); return new Sound(url);
} }
} }
export default new WorkadventureSoundCommands(); export default new WorkadventureSoundCommands();

View file

@ -1,53 +1,55 @@
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent'; import { isButtonClickedEvent } from "../Events/ButtonClickedEvent";
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent'; import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent";
import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent'; import type { MenuItemRegisterEvent } from "../Events/ui/MenuItemRegisterEvent";
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor"; import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
import { Popup } from "./Ui/Popup"; import { Popup } from "./Ui/Popup";
let popupId = 0; let popupId = 0;
const popups: Map<number, Popup> = new Map<number, Popup>(); const popups: Map<number, Popup> = new Map<number, Popup>();
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>(); const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
number,
Map<number, ButtonClickedCallback>
>();
const menuCallbacks: Map<string, (command: string) => void> = new Map() const menuCallbacks: Map<string, (command: string) => void> = new Map();
interface ZonedPopupOptions { interface ZonedPopupOptions {
zone: string zone: string;
objectLayerName?: string, objectLayerName?: string;
popupText: string, popupText: string;
delay?: number delay?: number;
popupOptions: Array<ButtonDescriptor> popupOptions: Array<ButtonDescriptor>;
} }
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> { callbacks = [
apiCallback({
callbacks = [apiCallback({ type: "buttonClickedEvent",
type: "buttonClickedEvent", typeChecker: isButtonClickedEvent,
typeChecker: isButtonClickedEvent, callback: (payloadData) => {
callback: (payloadData) => { const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId); const popup = popups.get(payloadData.popupId);
const popup = popups.get(payloadData.popupId); if (popup === undefined) {
if (popup === undefined) { throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"'); }
} if (callback) {
if (callback) { callback(popup);
callback(popup); }
} },
} }),
}), apiCallback({
apiCallback({ type: "menuItemClicked",
type: "menuItemClicked", typeChecker: isMenuItemClickedEvent,
typeChecker: isMenuItemClickedEvent, callback: (event) => {
callback: event => { const callback = menuCallbacks.get(event.menuItem);
const callback = menuCallbacks.get(event.menuItem); if (callback) {
if (callback) { callback(event.menuItem);
callback(event.menuItem) }
} },
} }),
})]; ];
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
popupId++; popupId++;
@ -66,40 +68,40 @@ class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiComma
} }
sendToWorkadventure({ sendToWorkadventure({
'type': 'openPopup', type: "openPopup",
'data': { data: {
popupId, popupId,
targetObject, targetObject,
message, message,
buttons: buttons.map((button) => { buttons: buttons.map((button) => {
return { return {
label: button.label, label: button.label,
className: button.className className: button.className,
}; };
}) }),
} },
}); });
popups.set(popupId, popup) popups.set(popupId, popup);
return popup; return popup;
} }
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) { registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
menuCallbacks.set(commandDescriptor, callback); menuCallbacks.set(commandDescriptor, callback);
sendToWorkadventure({ sendToWorkadventure({
'type': 'registerMenuCommand', type: "registerMenuCommand",
'data': { data: {
menutItem: commandDescriptor menutItem: commandDescriptor,
} },
}); });
} }
displayBubble(): void { displayBubble(): void {
sendToWorkadventure({ 'type': 'displayBubble', data: null }); sendToWorkadventure({ type: "displayBubble", data: null });
} }
removeBubble(): void { removeBubble(): void {
sendToWorkadventure({ 'type': 'removeBubble', data: null }); sendToWorkadventure({ type: "removeBubble", data: null });
} }
} }

View file

@ -1,11 +1,11 @@
<script lang="typescript"> <script lang="typescript">
import type { Game } from "../../Phaser/Game/Game"; import type { Game } from "../../Phaser/Game/Game";
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene"; import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
import {activeRowStore} from "../../Stores/CustomCharacterStore";
export let game: Game; export let game: Game;
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene; const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
let activeRow = customCharacterScene.activeRow;
function selectLeft() { function selectLeft() {
customCharacterScene.moveCursorHorizontally(-1); customCharacterScene.moveCursorHorizontally(-1);
@ -17,12 +17,10 @@
function selectUp() { function selectUp() {
customCharacterScene.moveCursorVertically(-1); customCharacterScene.moveCursorVertically(-1);
activeRow = customCharacterScene.activeRow;
} }
function selectDown() { function selectDown() {
customCharacterScene.moveCursorVertically(1); customCharacterScene.moveCursorVertically(1);
activeRow = customCharacterScene.activeRow;
} }
function previousScene() { function previousScene() {
@ -44,16 +42,16 @@
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> &gt; </button> <button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> &gt; </button>
</section> </section>
<section class="action"> <section class="action">
{#if activeRow === 0} {#if $activeRowStore === 0}
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button> <button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
{/if} {/if}
{#if activeRow !== 0} {#if $activeRowStore !== 0}
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button> <button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
{/if} {/if}
{#if activeRow === 5} {#if $activeRowStore === 5}
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button> <button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
{/if} {/if}
{#if activeRow !== 5} {#if $activeRowStore !== 5}
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button> <button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
{/if} {/if}
</section> </section>

View file

@ -1,90 +1,124 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin; import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type {CharacterTexture} from "../../Connexion/LocalUser"; import type { CharacterTexture } from "../../Connexion/LocalUser";
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures"; import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
export interface FrameConfig { export interface FrameConfig {
frameWidth: number, frameWidth: number;
frameHeight: number, frameHeight: number;
} }
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => { export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
const returnArray:BodyResourceDescriptionInterface[][] = []; const returnArray: BodyResourceDescriptionInterface[][] = [];
LAYERS.forEach(layer => { LAYERS.forEach((layer) => {
const layerArray:BodyResourceDescriptionInterface[] = []; const layerArray: BodyResourceDescriptionInterface[] = [];
Object.values(layer).forEach((textureDescriptor) => { Object.values(layer).forEach((textureDescriptor) => {
layerArray.push(textureDescriptor); layerArray.push(textureDescriptor);
load.spritesheet(textureDescriptor.name,textureDescriptor.img,{frameWidth: 32, frameHeight: 32}); load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
}) });
returnArray.push(layerArray) returnArray.push(layerArray);
}); });
return returnArray; return returnArray;
} };
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => { export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
const returnArray = Object.values(PLAYER_RESOURCES); const returnArray = Object.values(PLAYER_RESOURCES);
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => { returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
load.spritesheet(playerResource.name, playerResource.img, {frameWidth: 32, frameHeight: 32}); load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 });
}); });
return returnArray; return returnArray;
} };
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => { export const loadCustomTexture = (
const name = 'customCharacterTexture'+texture.id; loaderPlugin: LoaderPlugin,
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level} texture: CharacterTexture
): Promise<BodyResourceDescriptionInterface> => {
const name = "customCharacterTexture" + texture.id;
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
frameWidth: 32, frameWidth: 32,
frameHeight: 32 frameHeight: 32,
}); });
} };
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => { export const lazyLoadPlayerCharacterTextures = (
const promisesList:Promise<unknown>[] = []; loadPlugin: LoaderPlugin,
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => { texturekeys: Array<string | BodyResourceDescriptionInterface>
): Promise<string[]> => {
const promisesList: Promise<unknown>[] = [];
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
try { try {
//TODO refactor //TODO refactor
const playerResourceDescriptor = getRessourceDescriptor(textureKey); const playerResourceDescriptor = getRessourceDescriptor(textureKey);
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor, { promisesList.push(
frameWidth: 32, createLoadingPromise(loadPlugin, playerResourceDescriptor, {
frameHeight: 32 frameWidth: 32,
})); frameHeight: 32,
})
);
} }
}catch (err){ } catch (err) {
console.error(err); console.error(err);
} }
}); });
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>; let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) { if (promisesList.length > 0) {
loadPlugin.start(); loadPlugin.start();
returnPromise = Promise.all(promisesList).then(() => texturekeys); returnPromise = Promise.all(promisesList).then(() => texturekeys);
} else { } else {
returnPromise = Promise.resolve(texturekeys); returnPromise = Promise.resolve(texturekeys);
} }
return returnPromise.then((keys) => keys.map((key) => {
return typeof key !== 'string' ? key.name : key;
}))
}
export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptionInterface): BodyResourceDescriptionInterface => { //If the loading fail, we render the default model instead.
if (typeof textureKey !== 'string' && textureKey.img) { return returnPromise
.then((keys) =>
keys.map((key) => {
return typeof key !== "string" ? key.name : key;
})
)
.catch(() => lazyLoadPlayerCharacterTextures(loadPlugin, ["color_22", "eyes_23"]));
};
export const getRessourceDescriptor = (
textureKey: string | BodyResourceDescriptionInterface
): BodyResourceDescriptionInterface => {
if (typeof textureKey !== "string" && textureKey.img) {
return textureKey; return textureKey;
} }
const textureName:string = typeof textureKey === 'string' ? textureKey : textureKey.name; const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
const playerResource = PLAYER_RESOURCES[textureName]; const playerResource = PLAYER_RESOURCES[textureName];
if (playerResource !== undefined) return playerResource; if (playerResource !== undefined) return playerResource;
for (let i=0; i<LAYERS.length;i++) { for (let i = 0; i < LAYERS.length; i++) {
const playerResource = LAYERS[i][textureName]; const playerResource = LAYERS[i][textureName];
if (playerResource !== undefined) return playerResource; if (playerResource !== undefined) return playerResource;
} }
throw 'Could not find a data for texture '+textureName; throw "Could not find a data for texture " + textureName;
} };
export const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, frameConfig: FrameConfig) => { export const createLoadingPromise = (
return new Promise<BodyResourceDescriptionInterface>((res) => { loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig
) => {
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
console.log("count", loadPlugin.listenerCount("loaderror"));
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
return res(playerResourceDescriptor); return res(playerResourceDescriptor);
} }
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig); loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor)); const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return;
console.error("failed loading player ressource: ", playerResourceDescriptor);
rej(playerResourceDescriptor);
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
loadPlugin.off("loaderror", errorCallback);
};
const successCallback = () => {
loadPlugin.off("loaderror", errorCallback);
res(playerResourceDescriptor);
};
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
loadPlugin.on("loaderror", errorCallback);
}); });
} };

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,19 @@
import {EnableCameraSceneName} from "./EnableCameraScene"; import { EnableCameraSceneName } from "./EnableCameraScene";
import Rectangle = Phaser.GameObjects.Rectangle; import Rectangle = Phaser.GameObjects.Rectangle;
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager"; import { loadAllLayers } from "../Entity/PlayerTexturesLoadingManager";
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import {gameManager} from "../Game/GameManager"; import { gameManager } from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import {addLoader} from "../Components/Loader"; import { addLoader } from "../Components/Loader";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene"; import { AbstractCharacterScene } from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser"; import { areCharacterLayersValid } from "../../Connexion/LocalUser";
import { SelectCharacterSceneName } from "./SelectCharacterScene"; import { SelectCharacterSceneName } from "./SelectCharacterScene";
import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore"; import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
import {waScaleManager} from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
import {isMobile} from "../../Enum/EnvironmentVariable"; import { isMobile } from "../../Enum/EnvironmentVariable";
import {CustomizedCharacter} from "../Entity/CustomizedCharacter"; import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
import { get } from "svelte/store";
export const CustomizeSceneName = "CustomizeScene"; export const CustomizeSceneName = "CustomizeScene";
@ -21,7 +22,6 @@ export class CustomizeScene extends AbstractCharacterScene {
private selectedLayers: number[] = [0]; private selectedLayers: number[] = [0];
private containersRow: CustomizedCharacter[][] = []; private containersRow: CustomizedCharacter[][] = [];
public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = []; private layers: BodyResourceDescriptionInterface[][] = [];
protected lazyloadingAttempt = true; //permit to update texture loaded after renderer protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
@ -31,16 +31,19 @@ export class CustomizeScene extends AbstractCharacterScene {
constructor() { constructor() {
super({ super({
key: CustomizeSceneName key: CustomizeSceneName,
}); });
} }
preload() { preload() {
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => { bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){ if (
throw 'Texture level is null'; bodyResourceDescription.level == undefined ||
bodyResourceDescription.level < 0 ||
bodyResourceDescription.level > 5
) {
throw "Texture level is null";
} }
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
}); });
@ -50,14 +53,13 @@ export class CustomizeScene extends AbstractCharacterScene {
this.layers = loadAllLayers(this.load); this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
//this function must stay at the end of preload function //this function must stay at the end of preload function
addLoader(this); addLoader(this);
} }
create() { create() {
customCharacterSceneVisibleStore.set(true); customCharacterSceneVisibleStore.set(true);
this.events.addListener('wake', () => { this.events.addListener("wake", () => {
waScaleManager.saveZoom(); waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1; waScaleManager.zoomModifier = isMobile() ? 3 : 1;
customCharacterSceneVisibleStore.set(true); customCharacterSceneVisibleStore.set(true);
@ -66,8 +68,13 @@ export class CustomizeScene extends AbstractCharacterScene {
waScaleManager.saveZoom(); waScaleManager.saveZoom();
waScaleManager.zoomModifier = isMobile() ? 3 : 1; waScaleManager.zoomModifier = isMobile() ? 3 : 1;
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33) this.Rectangle = this.add.rectangle(
this.Rectangle.setStrokeStyle(2, 0xFFFFFF); this.cameras.main.worldView.x + this.cameras.main.width / 2,
this.cameras.main.worldView.y + this.cameras.main.height / 3,
32,
33
);
this.Rectangle.setStrokeStyle(2, 0xffffff);
this.add.existing(this.Rectangle); this.add.existing(this.Rectangle);
this.createCustomizeLayer(0, 0, 0); this.createCustomizeLayer(0, 0, 0);
@ -78,24 +85,24 @@ export class CustomizeScene extends AbstractCharacterScene {
this.createCustomizeLayer(0, 0, 5); this.createCustomizeLayer(0, 0, 5);
this.moveLayers(); this.moveLayers();
this.input.keyboard.on('keyup-ENTER', () => { this.input.keyboard.on("keyup-ENTER", () => {
this.nextSceneToCamera(); this.nextSceneToCamera();
}); });
this.input.keyboard.on('keyup-BACKSPACE', () => { this.input.keyboard.on("keyup-BACKSPACE", () => {
this.backToPreviousScene(); this.backToPreviousScene();
}); });
// Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods // Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
// because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot // because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
// explain, the list of sprites managed by the update list become immense // explain, the list of sprites managed by the update list become immense
this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1); this.input.keyboard.on("keyup-RIGHT", () => (this.moveHorizontally = 1));
this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1); this.input.keyboard.on("keyup-LEFT", () => (this.moveHorizontally = -1));
this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1); this.input.keyboard.on("keyup-DOWN", () => (this.moveVertically = 1));
this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1); this.input.keyboard.on("keyup-UP", () => (this.moveVertically = -1));
const customCursorPosition = localUserStore.getCustomCursorPosition(); const customCursorPosition = localUserStore.getCustomCursorPosition();
if (customCursorPosition) { if (customCursorPosition) {
this.activeRow = customCursorPosition.activeRow; activeRowStore.set(customCursorPosition.activeRow);
this.selectedLayers = customCursorPosition.selectedLayers; this.selectedLayers = customCursorPosition.selectedLayers;
this.moveLayers(); this.moveLayers();
this.updateSelectedLayer(); this.updateSelectedLayer();
@ -113,31 +120,30 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
private doMoveCursorHorizontally(index: number): void { private doMoveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index; this.selectedLayers[get(activeRowStore)] += index;
if (this.selectedLayers[this.activeRow] < 0) { if (this.selectedLayers[get(activeRowStore)] < 0) {
this.selectedLayers[this.activeRow] = 0 this.selectedLayers[get(activeRowStore)] = 0;
} else if(this.selectedLayers[this.activeRow] > this.layers[this.activeRow].length - 1) { } else if (this.selectedLayers[get(activeRowStore)] > this.layers[get(activeRowStore)].length - 1) {
this.selectedLayers[this.activeRow] = this.layers[this.activeRow].length - 1 this.selectedLayers[get(activeRowStore)] = this.layers[get(activeRowStore)].length - 1;
} }
this.moveLayers(); this.moveLayers();
this.updateSelectedLayer(); this.updateSelectedLayer();
this.saveInLocalStorage(); this.saveInLocalStorage();
} }
private doMoveCursorVertically(index:number): void { private doMoveCursorVertically(index: number): void {
activeRowStore.set(get(activeRowStore) + index);
this.activeRow += index; if (get(activeRowStore) < 0) {
if (this.activeRow < 0) { activeRowStore.set(0);
this.activeRow = 0 } else if (get(activeRowStore) > this.layers.length - 1) {
} else if (this.activeRow > this.layers.length - 1) { activeRowStore.set(this.layers.length - 1);
this.activeRow = this.layers.length - 1
} }
this.moveLayers(); this.moveLayers();
this.saveInLocalStorage(); this.saveInLocalStorage();
} }
private saveInLocalStorage() { private saveInLocalStorage() {
localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers); localUserStore.setCustomCursorPosition(get(activeRowStore), this.selectedLayers);
} }
/** /**
@ -173,7 +179,7 @@ export class CustomizeScene extends AbstractCharacterScene {
* @param selectedItem, The number of the item select (0 for black body...) * @param selectedItem, The number of the item select (0 for black body...)
*/ */
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) { private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem)); return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber, selectedItem));
} }
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> { private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
@ -188,7 +194,7 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
children.push(this.layers[j][layer].name); children.push(this.layers[j][layer].name);
} }
} }
return children; return children;
} }
@ -202,17 +208,16 @@ export class CustomizeScene extends AbstractCharacterScene {
const screenHeight = this.game.renderer.height; const screenHeight = this.game.renderer.height;
for (let i = 0; i < this.containersRow.length; i++) { for (let i = 0; i < this.containersRow.length; i++) {
for (let j = 0; j < this.containersRow[i].length; j++) { for (let j = 0; j < this.containersRow[i].length; j++) {
let selectedX = this.selectedLayers[i]; let selectedX = this.selectedLayers[i];
if (selectedX === undefined) { if (selectedX === undefined) {
selectedX = 0; selectedX = 0;
} }
this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40; this.containersRow[i][j].x = screenCenterX + (j - selectedX) * 40;
this.containersRow[i][j].y = screenCenterY + (i - this.activeRow) * 40; this.containersRow[i][j].y = screenCenterY + (i - get(activeRowStore)) * 40;
const alpha1 = Math.abs(selectedX - j)*47*2/screenWidth; const alpha1 = (Math.abs(selectedX - j) * 47 * 2) / screenWidth;
const alpha2 = Math.abs(this.activeRow - i)*49*2/screenHeight; const alpha2 = (Math.abs(get(activeRowStore) - i) * 49 * 2) / screenHeight;
this.containersRow[i][j].setAlpha((1 -alpha1)*(1 - alpha2)); this.containersRow[i][j].setAlpha((1 - alpha1) * (1 - alpha2));
} }
} }
} }
@ -228,8 +233,8 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
private updateSelectedLayer() { private updateSelectedLayer() {
for(let i = 0; i < this.containersRow.length; i++){ for (let i = 0; i < this.containersRow.length; i++) {
for(let j = 0; j < this.containersRow[i].length; j++){ for (let j = 0; j < this.containersRow[i].length; j++) {
const children = this.getContainerChildren(i, j); const children = this.getContainerChildren(i, j);
this.containersRow[i][j].updateSprites(children); this.containersRow[i][j].updateSprites(children);
} }
@ -237,8 +242,7 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
update(time: number, delta: number): void { update(time: number, delta: number): void {
if (this.lazyloadingAttempt) {
if(this.lazyloadingAttempt){
this.moveLayers(); this.moveLayers();
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;
} }
@ -253,38 +257,35 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
} }
public onResize(): void {
public onResize(): void {
this.moveLayers(); this.moveLayers();
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2; this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3; this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
}
public nextSceneToCamera(){
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener('wake');
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
} }
public backToPreviousScene(){ public nextSceneToCamera() {
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
if (layerItem !== undefined) {
layers.push(this.layers[i][layerItem].name);
}
i++;
}
if (!areCharacterLayersValid(layers)) {
return;
}
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom();
this.events.removeListener("wake");
gameManager.tryResumingGame(this, EnableCameraSceneName);
customCharacterSceneVisibleStore.set(false);
}
public backToPreviousScene() {
this.scene.sleep(CustomizeSceneName); this.scene.sleep(CustomizeSceneName);
waScaleManager.restoreZoom(); waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName); this.scene.run(SelectCharacterSceneName);

View file

@ -1,3 +1,5 @@
import { derived, writable, Writable } from "svelte/store"; import { derived, writable, Writable } from "svelte/store";
export const customCharacterSceneVisibleStore = writable(false); export const customCharacterSceneVisibleStore = writable(false);
export const activeRowStore = writable(0);

View file

@ -105,9 +105,9 @@ const wa = {
/** /**
* @deprecated Use WA.nav.openCoWebSite instead * @deprecated Use WA.nav.openCoWebSite instead
*/ */
openCoWebSite(url: string): void { openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
console.warn('Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead'); console.warn('Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead');
nav.openCoWebSite(url); nav.openCoWebSite(url, allowApi, allowPolicy);
}, },
/** /**

View file

@ -1,35 +1,37 @@
import type {Configuration} from "webpack"; import type { Configuration } from "webpack";
import type WebpackDevServer from "webpack-dev-server"; import type WebpackDevServer from "webpack-dev-server";
import path from 'path'; import path from "path";
import webpack from 'webpack'; import webpack from "webpack";
import HtmlWebpackPlugin from 'html-webpack-plugin'; import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import MiniCssExtractPlugin from "mini-css-extract-plugin";
import sveltePreprocess from 'svelte-preprocess'; import sveltePreprocess from "svelte-preprocess";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
import {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable"; import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable";
const mode = process.env.NODE_ENV ?? 'development'; const mode = process.env.NODE_ENV ?? "development";
const isProduction = mode === 'production'; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
const isProduction = mode === "production";
const isDevelopment = !isProduction; const isDevelopment = !isProduction;
const entries: { [key: string]: string } = {};
if (!buildNpmTypingsForApi) {
entries.main = "./src/index.ts";
}
entries.iframe_api = "./src/iframe_api.ts";
module.exports = { module.exports = {
entry: { entry: entries,
'main': './src/index.ts',
'iframe_api': './src/iframe_api.ts'
},
mode: mode, mode: mode,
devtool: isDevelopment ? 'inline-source-map' : 'source-map', devtool: isDevelopment ? "inline-source-map" : "source-map",
devServer: { devServer: {
contentBase: './dist', contentBase: "./dist",
host: '0.0.0.0', host: "0.0.0.0",
sockPort: 80, sockPort: 80,
disableHostCheck: true, disableHostCheck: true,
historyApiFallback: { historyApiFallback: {
rewrites: [ rewrites: [{ from: /^_\/.*$/, to: "/index.html" }],
{ from: /^_\/.*$/, to: '/index.html' } disableDotRule: true,
],
disableDotRule: true
}, },
}, },
module: { module: {
@ -38,22 +40,28 @@ module.exports = {
test: /\.tsx?$/, test: /\.tsx?$/,
//use: 'ts-loader', //use: 'ts-loader',
exclude: /node_modules/, exclude: /node_modules/,
loader: 'ts-loader', loader: "ts-loader",
options: { options: {
transpileOnly: true, transpileOnly: !buildNpmTypingsForApi,
compilerOptions: {
declaration: buildNpmTypingsForApi,
},
}, },
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [
MiniCssExtractPlugin.loader, { MiniCssExtractPlugin.loader,
loader: 'css-loader', {
loader: "css-loader",
options: { options: {
//url: false, //url: false,
sourceMap: true sourceMap: true,
} },
}, 'sass-loader'], },
"sass-loader",
],
}, },
{ {
test: /\.css$/, test: /\.css$/,
@ -61,23 +69,23 @@ module.exports = {
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
{ {
loader: 'css-loader', loader: "css-loader",
options: { options: {
//url: false, //url: false,
sourceMap: true sourceMap: true,
} },
} },
] ],
}, },
{ {
test: /\.(html|svelte)$/, test: /\.(html|svelte)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'svelte-loader', loader: "svelte-loader",
options: { options: {
compilerOptions: { compilerOptions: {
// Dev mode must be enabled for HMR to work! // Dev mode must be enabled for HMR to work!
dev: isDevelopment dev: isDevelopment,
}, },
emitCss: isProduction, emitCss: isProduction,
hotReload: isDevelopment, hotReload: isDevelopment,
@ -90,18 +98,27 @@ module.exports = {
scss: true, scss: true,
sass: true, sass: true,
}), }),
onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) { onwarn: function (
warning: { code: string },
handleWarning: (warning: { code: string }) => void
) {
// See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782 // See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782
if (warning.code === 'a11y-no-onchange') { return } if (warning.code === "a11y-no-onchange") {
if (warning.code === 'a11y-autofocus') { return } return;
if (warning.code === 'a11y-media-has-caption') { return } }
if (warning.code === "a11y-autofocus") {
return;
}
if (warning.code === "a11y-media-has-caption") {
return;
}
// process as usual // process as usual
handleWarning(warning); handleWarning(warning);
} },
} },
} },
}, },
// Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4 // Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4
@ -109,86 +126,82 @@ module.exports = {
{ {
test: /node_modules\/svelte\/.*\.mjs$/, test: /node_modules\/svelte\/.*\.mjs$/,
resolve: { resolve: {
fullySpecified: false fullySpecified: false,
} },
}, },
{ {
test: /\.(eot|svg|png|gif|jpg)$/, test: /\.(eot|svg|png|gif|jpg)$/,
exclude: /node_modules/, exclude: /node_modules/,
type: 'asset' type: "asset",
}, },
{ {
test: /\.(woff(2)?|ttf)$/, test: /\.(woff(2)?|ttf)$/,
type: 'asset', type: "asset",
generator: { generator: {
filename: 'fonts/[name][ext]' filename: "fonts/[name][ext]",
} },
},
}
], ],
}, },
resolve: { resolve: {
alias: { alias: {
svelte: path.resolve('node_modules', 'svelte') svelte: path.resolve("node_modules", "svelte"),
}, },
extensions: [ '.tsx', '.ts', '.js', '.svelte' ], extensions: [".tsx", ".ts", ".js", ".svelte"],
mainFields: ['svelte', 'browser', 'module', 'main'] mainFields: ["svelte", "browser", "module", "main"],
}, },
output: { output: {
filename: (pathData) => { filename: (pathData) => {
// Add a content hash only for the main bundle. // Add a content hash only for the main bundle.
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes. // We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
return pathData.chunk?.name === 'main' ? 'js/[name].[contenthash].js': '[name].js'; return pathData.chunk?.name === "main" ? "js/[name].[contenthash].js" : "[name].js";
}, },
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, "dist"),
publicPath: '/' publicPath: "/",
}, },
plugins: [ plugins: [
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
eslint: { eslint: {
files: './src/**/*.ts' files: "./src/**/*.ts",
} },
}),
new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }),
new HtmlWebpackPlugin({
template: "./dist/index.tmpl.html.tmp",
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
},
chunks: ["main"],
}), }),
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
chunks: ['main']
}
),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Phaser: 'phaser' Phaser: "phaser",
}), }),
new NodePolyfillPlugin(), new NodePolyfillPlugin(),
new webpack.EnvironmentPlugin({ new webpack.EnvironmentPlugin({
'API_URL': null, API_URL: null,
'SKIP_RENDER_OPTIMIZATIONS': false, SKIP_RENDER_OPTIMIZATIONS: false,
'DISABLE_NOTIFICATIONS': false, DISABLE_NOTIFICATIONS: false,
'PUSHER_URL': undefined, PUSHER_URL: undefined,
'UPLOADER_URL': null, UPLOADER_URL: null,
'ADMIN_URL': null, ADMIN_URL: null,
'DEBUG_MODE': null, DEBUG_MODE: null,
'STUN_SERVER': null, STUN_SERVER: null,
'TURN_SERVER': null, TURN_SERVER: null,
'TURN_USER': null, TURN_USER: null,
'TURN_PASSWORD': null, TURN_PASSWORD: null,
'JITSI_URL': null, JITSI_URL: null,
'JITSI_PRIVATE_MODE': null, JITSI_PRIVATE_MODE: null,
'START_ROOM_URL': null, START_ROOM_URL: null,
'MAX_USERNAME_LENGTH': 8, MAX_USERNAME_LENGTH: 8,
'MAX_PER_GROUP': 4, MAX_PER_GROUP: 4,
'DISPLAY_TERMS_OF_USE': false, DISPLAY_TERMS_OF_USE: false,
}) }),
], ],
} as Configuration & WebpackDevServer.Configuration; } as Configuration & WebpackDevServer.Configuration;

View file

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<script>
var script = document.createElement('script');
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
// We are OK in this precise case because the HTML page is hosted on the "maps" domain that contains only static files.
script.setAttribute('src', document.referrer + 'iframe_api.js');
document.head.appendChild(script);
window.addEventListener('load', () => {
WA.chat.sendChatMessage('The iframe opened by a script works !', 'Mr Robot');
})
</script>
</head>
<body>
<p>Website opened by script.</p>
</body>
</html>

View file

@ -0,0 +1 @@
WA.nav.openCoWebSite("cowebsiteAllowApi.html", true, "");

View file

@ -0,0 +1,98 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51],
"height":10,
"id":1,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":116.5,
"id":1,
"name":"",
"rotation":0,
"text":
{
"text":"Test : \nThe iframe is opened by script.\n\nResult : \nA message is send to the chat.",
"wrap":true
},
"type":"",
"visible":true,
"width":295.875,
"x":11.8125,
"y":188.5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 16, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 16, 16, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 16, 16, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":4,
"name":"mushroom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
}],
"nextlayerid":5,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"cowebsiteAllowApi.js"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"tileset_dungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View file

@ -106,6 +106,14 @@
<a href="#" class="testLink" data-testmap="help_camera_setting.json" target="_blank">Test the HelpCameraSettingScene</a> <a href="#" class="testLink" data-testmap="help_camera_setting.json" target="_blank">Test the HelpCameraSettingScene</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test a iframe opened by a script can use Iframe API</a>
</td>
</tr>
<tr> <tr>
<td> <td>
<input type="radio" name="test-custom-menu"> Success <input type="radio" name="test-custom-menu"> Failure <input type="radio" name="test-custom-menu" checked> Pending <input type="radio" name="test-custom-menu"> Success <input type="radio" name="test-custom-menu"> Failure <input type="radio" name="test-custom-menu" checked> Pending
@ -162,6 +170,14 @@
<a href="#" class="testLink" data-testmap="animated_tiles.json" target="_blank">Test animated tiles</a> <a href="#" class="testLink" data-testmap="animated_tiles.json" target="_blank">Test animated tiles</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-cowebsite-allowAPI"> Success <input type="radio" name="test-cowebsite-allowAPI"> Failure <input type="radio" name="test-cowebsite-allowAPI" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="Metadata/cowebsiteAllowApi.json" target="_blank">Test cowebsite opened by script is allowed to use IFrame API</a>
</td>
</tr>
<tr> <tr>
<td> <td>
<input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending <input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending

View file

@ -2,6 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
husky@^6.0.0: "husky@^6.0.0":
version "6.0.0" "resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz"
resolved "https://registry.yarnpkg.com/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" "version" "6.0.0"