Making embedded iframes scriptable using the WA.room.website namespace.
This commit is contained in:
parent
5bb29e99ba
commit
6b9b999996
@ -177,3 +177,101 @@ WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
|||||||
WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]);
|
WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]);
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Embedding websites in a map
|
||||||
|
|
||||||
|
You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using the ["website" objects](website-in-map.md)).
|
||||||
|
|
||||||
|
### Getting an instance of a website already embedded in the map
|
||||||
|
|
||||||
|
```
|
||||||
|
WA.room.website.get(objectName: string): Promise<EmbeddedWebsite>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get an instance of an embedded website by using the `WA.room.website.get()` method.
|
||||||
|
It returns a promise of an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map)
|
||||||
|
const website = await WA.room.website.get('my_website');
|
||||||
|
website.url = 'https://example.com';
|
||||||
|
website.visible = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Adding a new website in a map
|
||||||
|
|
||||||
|
```
|
||||||
|
WA.room.website.create(website: CreateEmbeddedWebsiteEvent): EmbeddedWebsite
|
||||||
|
|
||||||
|
interface CreateEmbeddedWebsiteEvent {
|
||||||
|
name: string; // A unique name for this iframe
|
||||||
|
url: string; // The URL the iframe points to.
|
||||||
|
position: {
|
||||||
|
x: number, // In pixels, relative to the map coordinates
|
||||||
|
y: number, // In pixels, relative to the map coordinates
|
||||||
|
width: number, // In pixels, sensitive to zoom level
|
||||||
|
height: number, // In pixels, sensitive to zoom level
|
||||||
|
},
|
||||||
|
visible?: boolean, // Whether to display the iframe or not
|
||||||
|
allowApi?: boolean, // Whether the scripting API should be available to the iframe
|
||||||
|
allow?: string, // The list of feature policies allowed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can create an instance of an embedded website by using the `WA.room.website.create()` method.
|
||||||
|
It returns an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Create a new website object
|
||||||
|
const website = WA.room.website.create({
|
||||||
|
name: "my_website",
|
||||||
|
url: "https://example.com",
|
||||||
|
position: {
|
||||||
|
x: 64,
|
||||||
|
y: 128,
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
visible: true,
|
||||||
|
allowApi: true,
|
||||||
|
allow: "fullscreen",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deleting a website from a map
|
||||||
|
|
||||||
|
```
|
||||||
|
WA.room.website.delete(name: string): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `WA.room.website.delete` to completely remove an embedded website from your map.
|
||||||
|
|
||||||
|
|
||||||
|
### The EmbeddedWebsite class
|
||||||
|
|
||||||
|
Instances of the `EmbeddedWebsite` class represent the website displayed on the map.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class EmbeddedWebsite {
|
||||||
|
readonly name: string;
|
||||||
|
url: string;
|
||||||
|
visible: boolean;
|
||||||
|
allow: string;
|
||||||
|
allowApi: boolean;
|
||||||
|
x: number; // In pixels, relative to the map coordinates
|
||||||
|
y: number; // In pixels, relative to the map coordinates
|
||||||
|
width: number; // In pixels, sensitive to zoom level
|
||||||
|
height: number; // In pixels, sensitive to zoom level
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map.
|
||||||
|
|
||||||
|
|
||||||
|
{.alert.alert-warning}
|
||||||
|
The websites you add/edit/delete via the scripting API are only shown locally. If you want them
|
||||||
|
to be displayed for every player, you can use [variables](api-start.md) to share a common state
|
||||||
|
between all users.
|
||||||
|
|
||||||
|
48
front/src/Api/Events/EmbeddedWebsiteEvent.ts
Normal file
48
front/src/Api/Events/EmbeddedWebsiteEvent.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isRectangle = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
width: tg.isNumber,
|
||||||
|
height: tg.isNumber,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export const isEmbeddedWebsiteEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
})
|
||||||
|
.withOptionalProperties({
|
||||||
|
url: tg.isString,
|
||||||
|
visible: tg.isBoolean,
|
||||||
|
allowApi: tg.isBoolean,
|
||||||
|
allow: tg.isString,
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
width: tg.isNumber,
|
||||||
|
height: tg.isNumber,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
url: tg.isString,
|
||||||
|
position: isRectangle,
|
||||||
|
})
|
||||||
|
.withOptionalProperties({
|
||||||
|
visible: tg.isBoolean,
|
||||||
|
allowApi: tg.isBoolean,
|
||||||
|
allow: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to modify an embedded website
|
||||||
|
*/
|
||||||
|
export type ModifyEmbeddedWebsiteEvent = tg.GuardedType<typeof isEmbeddedWebsiteEvent>;
|
||||||
|
|
||||||
|
export type CreateEmbeddedWebsiteEvent = tg.GuardedType<typeof isCreateEmbeddedWebsiteEvent>;
|
||||||
|
// TODO: make a variation that is all optional (except for the name)
|
||||||
|
export type Rectangle = tg.GuardedType<typeof isRectangle>;
|
@ -22,6 +22,8 @@ import type { SetVariableEvent } from "./SetVariableEvent";
|
|||||||
import { isGameStateEvent } from "./GameStateEvent";
|
import { isGameStateEvent } from "./GameStateEvent";
|
||||||
import { isMapDataEvent } from "./MapDataEvent";
|
import { isMapDataEvent } from "./MapDataEvent";
|
||||||
import { isSetVariableEvent } from "./SetVariableEvent";
|
import { isSetVariableEvent } from "./SetVariableEvent";
|
||||||
|
import type { EmbeddedWebsite } from "../iframe/Room/EmbeddedWebsite";
|
||||||
|
import { isCreateEmbeddedWebsiteEvent } from "./EmbeddedWebsiteEvent";
|
||||||
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
|
import type { LoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
|
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
|
||||||
import type {
|
import type {
|
||||||
@ -63,6 +65,7 @@ export type IframeEventMap = {
|
|||||||
loadTileset: LoadTilesetEvent;
|
loadTileset: LoadTilesetEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent;
|
registerMenuCommand: MenuItemRegisterEvent;
|
||||||
setTiles: SetTilesEvent;
|
setTiles: SetTilesEvent;
|
||||||
|
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
||||||
};
|
};
|
||||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||||
type: T;
|
type: T;
|
||||||
@ -122,6 +125,18 @@ export const iframeQueryMapTypeGuards = {
|
|||||||
query: isMessageReferenceEvent,
|
query: isMessageReferenceEvent,
|
||||||
answer: tg.isUndefined,
|
answer: tg.isUndefined,
|
||||||
},
|
},
|
||||||
|
getEmbeddedWebsite: {
|
||||||
|
query: tg.isString,
|
||||||
|
answer: isCreateEmbeddedWebsiteEvent,
|
||||||
|
},
|
||||||
|
deleteEmbeddedWebsite: {
|
||||||
|
query: tg.isString,
|
||||||
|
answer: tg.isUndefined,
|
||||||
|
},
|
||||||
|
createEmbeddedWebsite: {
|
||||||
|
query: isCreateEmbeddedWebsiteEvent,
|
||||||
|
answer: tg.isUndefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||||
@ -158,7 +173,12 @@ export const isIframeQuery = (event: any): event is IframeQuery<keyof IframeQuer
|
|||||||
if (!isIframeQueryKey(type)) {
|
if (!isIframeQueryKey(type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return iframeQueryMapTypeGuards[type].query(event.data);
|
|
||||||
|
const result = iframeQueryMapTypeGuards[type].query(event.data);
|
||||||
|
if (!result) {
|
||||||
|
console.warn('Received a query with type "' + type + '" but the payload is invalid.');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -32,6 +32,8 @@ import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
|||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||||
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
||||||
|
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
|
||||||
|
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
query: IframeQueryMap[T]["query"],
|
query: IframeQueryMap[T]["query"],
|
||||||
@ -109,6 +111,9 @@ class IframeListener {
|
|||||||
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||||
public readonly setTilesStream = this._setTilesStream.asObservable();
|
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _modifyEmbeddedWebsiteStream: Subject<ModifyEmbeddedWebsiteEvent> = new Subject();
|
||||||
|
public readonly modifyEmbeddedWebsiteStream = this._modifyEmbeddedWebsiteStream.asObservable();
|
||||||
|
|
||||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||||
@ -264,6 +269,8 @@ class IframeListener {
|
|||||||
handleMenuItemRegistrationEvent(payload.data);
|
handleMenuItemRegistrationEvent(payload.data);
|
||||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||||
this._setTilesStream.next(payload.data);
|
this._setTilesStream.next(payload.data);
|
||||||
|
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
||||||
|
this._modifyEmbeddedWebsiteStream.next(payload.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
90
front/src/Api/iframe/Room/EmbeddedWebsite.ts
Normal file
90
front/src/Api/iframe/Room/EmbeddedWebsite.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { sendToWorkadventure } from "../IframeApiContribution";
|
||||||
|
import type {
|
||||||
|
CreateEmbeddedWebsiteEvent,
|
||||||
|
ModifyEmbeddedWebsiteEvent,
|
||||||
|
Rectangle,
|
||||||
|
} from "../../Events/EmbeddedWebsiteEvent";
|
||||||
|
|
||||||
|
export class EmbeddedWebsite {
|
||||||
|
public readonly name: string;
|
||||||
|
private _url: string;
|
||||||
|
private _visible: boolean;
|
||||||
|
private _allow: string;
|
||||||
|
private _allowApi: boolean;
|
||||||
|
private _position: Rectangle;
|
||||||
|
|
||||||
|
constructor(private config: CreateEmbeddedWebsiteEvent) {
|
||||||
|
this.name = config.name;
|
||||||
|
this._url = config.url;
|
||||||
|
this._visible = config.visible ?? true;
|
||||||
|
this._allow = config.allow ?? "";
|
||||||
|
this._allowApi = config.allowApi ?? false;
|
||||||
|
this._position = config.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set url(url: string) {
|
||||||
|
this._url = url;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
url: this._url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set visible(visible: boolean) {
|
||||||
|
this._visible = visible;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
visible: this._visible,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set x(x: number) {
|
||||||
|
this._position.x = x;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
x: this._position.x,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set y(y: number) {
|
||||||
|
this._position.y = y;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
y: this._position.y,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set width(width: number) {
|
||||||
|
this._position.width = width;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
width: this._position.width,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public set height(height: number) {
|
||||||
|
this._position.height = height;
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "modifyEmbeddedWebsite",
|
||||||
|
data: {
|
||||||
|
name: this.name,
|
||||||
|
height: this._position.height,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "
|
|||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
|
|
||||||
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||||
|
import type { WorkadventureRoomWebsiteCommands } from "./website";
|
||||||
|
import website from "./website";
|
||||||
|
|
||||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||||
@ -105,6 +107,7 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||||||
}
|
}
|
||||||
return mapURL;
|
return mapURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTileset(url: string): Promise<number> {
|
async loadTileset(url: string): Promise<number> {
|
||||||
return await queryWorkadventure({
|
return await queryWorkadventure({
|
||||||
type: "loadTileset",
|
type: "loadTileset",
|
||||||
@ -113,6 +116,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get website(): WorkadventureRoomWebsiteCommands {
|
||||||
|
return website;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new WorkadventureRoomCommands();
|
export default new WorkadventureRoomCommands();
|
||||||
|
38
front/src/Api/iframe/website.ts
Normal file
38
front/src/Api/iframe/website.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
|
||||||
|
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
|
||||||
|
import type { StopSoundEvent } from "../Events/StopSoundEvent";
|
||||||
|
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
|
import { Sound } from "./Sound/Sound";
|
||||||
|
import { EmbeddedWebsite } from "./Room/EmbeddedWebsite";
|
||||||
|
import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent";
|
||||||
|
|
||||||
|
export class WorkadventureRoomWebsiteCommands extends IframeApiContribution<WorkadventureRoomWebsiteCommands> {
|
||||||
|
callbacks = [];
|
||||||
|
|
||||||
|
async get(objectName: string): Promise<EmbeddedWebsite> {
|
||||||
|
const websiteEvent = await queryWorkadventure({
|
||||||
|
type: "getEmbeddedWebsite",
|
||||||
|
data: objectName,
|
||||||
|
});
|
||||||
|
return new EmbeddedWebsite(websiteEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(createEmbeddedWebsiteEvent: CreateEmbeddedWebsiteEvent): EmbeddedWebsite {
|
||||||
|
queryWorkadventure({
|
||||||
|
type: "createEmbeddedWebsite",
|
||||||
|
data: createEmbeddedWebsiteEvent,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
return new EmbeddedWebsite(createEmbeddedWebsiteEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(objectName: string): Promise<void> {
|
||||||
|
return await queryWorkadventure({
|
||||||
|
type: "deleteEmbeddedWebsite",
|
||||||
|
data: objectName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new WorkadventureRoomWebsiteCommands();
|
198
front/src/Phaser/Game/EmbeddedWebsiteManager.ts
Normal file
198
front/src/Phaser/Game/EmbeddedWebsiteManager.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import type { GameScene } from "./GameScene";
|
||||||
|
import { iframeListener } from "../../Api/IframeListener";
|
||||||
|
import type { Subscription } from "rxjs";
|
||||||
|
import type { CreateEmbeddedWebsiteEvent, ModifyEmbeddedWebsiteEvent } from "../../Api/Events/EmbeddedWebsiteEvent";
|
||||||
|
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||||
|
|
||||||
|
type EmbeddedWebsite = CreateEmbeddedWebsiteEvent & { iframe: HTMLIFrameElement; phaserObject: DOMElement };
|
||||||
|
|
||||||
|
export class EmbeddedWebsiteManager {
|
||||||
|
private readonly embeddedWebsites = new Map<string, EmbeddedWebsite>();
|
||||||
|
private readonly subscription: Subscription;
|
||||||
|
|
||||||
|
constructor(private gameScene: GameScene) {
|
||||||
|
iframeListener.registerAnswerer("getEmbeddedWebsite", (name: string) => {
|
||||||
|
const website = this.embeddedWebsites.get(name);
|
||||||
|
if (website === undefined) {
|
||||||
|
throw new Error('Cannot find embedded website with name "' + name + '"');
|
||||||
|
}
|
||||||
|
const rect = website.iframe.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
url: website.url,
|
||||||
|
name: website.name,
|
||||||
|
visible: website.visible,
|
||||||
|
allowApi: website.allowApi,
|
||||||
|
allow: website.allow,
|
||||||
|
position: {
|
||||||
|
x: website.phaserObject.x,
|
||||||
|
y: website.phaserObject.y,
|
||||||
|
width: rect["width"],
|
||||||
|
height: rect["height"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
iframeListener.registerAnswerer("deleteEmbeddedWebsite", (name: string) => {
|
||||||
|
const website = this.embeddedWebsites.get(name);
|
||||||
|
if (!website) {
|
||||||
|
throw new Error('Could not find website to delete with the name "' + name + '" in your map');
|
||||||
|
}
|
||||||
|
|
||||||
|
website.iframe.remove();
|
||||||
|
website.phaserObject.destroy();
|
||||||
|
this.embeddedWebsites.delete(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
iframeListener.registerAnswerer(
|
||||||
|
"createEmbeddedWebsite",
|
||||||
|
(createEmbeddedWebsiteEvent: CreateEmbeddedWebsiteEvent) => {
|
||||||
|
if (this.embeddedWebsites.has(createEmbeddedWebsiteEvent.name)) {
|
||||||
|
throw new Error('An embedded website with the name "' + name + '" already exists in your map');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createEmbeddedWebsite(
|
||||||
|
createEmbeddedWebsiteEvent.name,
|
||||||
|
createEmbeddedWebsiteEvent.url,
|
||||||
|
createEmbeddedWebsiteEvent.position.x,
|
||||||
|
createEmbeddedWebsiteEvent.position.y,
|
||||||
|
createEmbeddedWebsiteEvent.position.width,
|
||||||
|
createEmbeddedWebsiteEvent.position.height,
|
||||||
|
createEmbeddedWebsiteEvent.visible ?? true,
|
||||||
|
createEmbeddedWebsiteEvent.allowApi ?? false,
|
||||||
|
createEmbeddedWebsiteEvent.allow ?? ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subscription = iframeListener.modifyEmbeddedWebsiteStream.subscribe(
|
||||||
|
(embeddedWebsiteEvent: ModifyEmbeddedWebsiteEvent) => {
|
||||||
|
const website = this.embeddedWebsites.get(embeddedWebsiteEvent.name);
|
||||||
|
if (!website) {
|
||||||
|
throw new Error(
|
||||||
|
'Could not find website with the name "' + embeddedWebsiteEvent.name + '" in your map'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameScene.markDirty();
|
||||||
|
|
||||||
|
if (embeddedWebsiteEvent.url !== undefined) {
|
||||||
|
website.url = embeddedWebsiteEvent.url;
|
||||||
|
const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString();
|
||||||
|
website.iframe.src = absoluteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddedWebsiteEvent.visible !== undefined) {
|
||||||
|
website.visible = embeddedWebsiteEvent.visible;
|
||||||
|
website.phaserObject.visible = embeddedWebsiteEvent.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddedWebsiteEvent.allowApi !== undefined) {
|
||||||
|
website.allowApi = embeddedWebsiteEvent.allowApi;
|
||||||
|
if (embeddedWebsiteEvent.allowApi) {
|
||||||
|
iframeListener.registerIframe(website.iframe);
|
||||||
|
} else {
|
||||||
|
iframeListener.unregisterIframe(website.iframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddedWebsiteEvent.allow !== undefined) {
|
||||||
|
website.allow = embeddedWebsiteEvent.allow;
|
||||||
|
website.iframe.allow = embeddedWebsiteEvent.allow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embeddedWebsiteEvent?.x !== undefined) {
|
||||||
|
website.phaserObject.x = embeddedWebsiteEvent.x;
|
||||||
|
}
|
||||||
|
if (embeddedWebsiteEvent?.y !== undefined) {
|
||||||
|
website.phaserObject.y = embeddedWebsiteEvent.y;
|
||||||
|
}
|
||||||
|
if (embeddedWebsiteEvent?.width !== undefined) {
|
||||||
|
website.iframe.style.width = embeddedWebsiteEvent.width + "px";
|
||||||
|
}
|
||||||
|
if (embeddedWebsiteEvent?.height !== undefined) {
|
||||||
|
website.iframe.style.height = embeddedWebsiteEvent.height + "px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createEmbeddedWebsite(
|
||||||
|
name: string,
|
||||||
|
url: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
visible: boolean,
|
||||||
|
allowApi: boolean,
|
||||||
|
allow: string
|
||||||
|
): void {
|
||||||
|
if (this.embeddedWebsites.has(name)) {
|
||||||
|
throw new Error('An embedded website with the name "' + name + '" already exists in your map');
|
||||||
|
}
|
||||||
|
|
||||||
|
const embeddedWebsiteEvent: CreateEmbeddedWebsiteEvent = {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
/*x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,*/
|
||||||
|
allow,
|
||||||
|
allowApi,
|
||||||
|
visible,
|
||||||
|
position: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const embeddedWebsite = this.doCreateEmbeddedWebsite(embeddedWebsiteEvent, visible);
|
||||||
|
|
||||||
|
this.embeddedWebsites.set(name, embeddedWebsite);
|
||||||
|
}
|
||||||
|
|
||||||
|
private doCreateEmbeddedWebsite(
|
||||||
|
embeddedWebsiteEvent: CreateEmbeddedWebsiteEvent,
|
||||||
|
visible: boolean
|
||||||
|
): EmbeddedWebsite {
|
||||||
|
const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString();
|
||||||
|
|
||||||
|
const iframe = document.createElement("iframe");
|
||||||
|
iframe.src = absoluteUrl;
|
||||||
|
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
||||||
|
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
||||||
|
iframe.style.margin = "0";
|
||||||
|
iframe.style.padding = "0";
|
||||||
|
iframe.style.border = "none";
|
||||||
|
|
||||||
|
const embeddedWebsite = {
|
||||||
|
...embeddedWebsiteEvent,
|
||||||
|
phaserObject: this.gameScene.add
|
||||||
|
.dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe)
|
||||||
|
.setVisible(visible)
|
||||||
|
.setOrigin(0, 0),
|
||||||
|
iframe: iframe,
|
||||||
|
};
|
||||||
|
if (embeddedWebsiteEvent.allowApi) {
|
||||||
|
iframeListener.registerIframe(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return embeddedWebsite;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
for (const [key, website] of this.embeddedWebsites) {
|
||||||
|
if (website.allowApi) {
|
||||||
|
iframeListener.unregisterIframe(website.iframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
iframeListener.unregisterAnswerer("getEmbeddedWebsite");
|
||||||
|
iframeListener.unregisterAnswerer("deleteEmbeddedWebsite");
|
||||||
|
iframeListener.unregisterAnswerer("createEmbeddedWebsite");
|
||||||
|
}
|
||||||
|
}
|
@ -89,6 +89,7 @@ import Tileset = Phaser.Tilemaps.Tileset;
|
|||||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -200,7 +201,7 @@ export class GameScene extends DirtyScene {
|
|||||||
private startPositionCalculator!: StartPositionCalculator;
|
private startPositionCalculator!: StartPositionCalculator;
|
||||||
private sharedVariablesManager!: SharedVariablesManager;
|
private sharedVariablesManager!: SharedVariablesManager;
|
||||||
private objectsByType = new Map<string, ITiledMapObject[]>();
|
private objectsByType = new Map<string, ITiledMapObject[]>();
|
||||||
private inMapIframes = new Array<HTMLIFrameElement>();
|
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||||
super({
|
super({
|
||||||
@ -419,6 +420,7 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
//hook create scene
|
//hook create scene
|
||||||
create(): void {
|
create(): void {
|
||||||
|
console.log("GAAAAAAAGAGAGAGAGA");
|
||||||
this.preloading = false;
|
this.preloading = false;
|
||||||
this.trackDirtyAnims();
|
this.trackDirtyAnims();
|
||||||
|
|
||||||
@ -460,6 +462,8 @@ export class GameScene extends DirtyScene {
|
|||||||
//permit to set bound collision
|
//permit to set bound collision
|
||||||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||||
|
|
||||||
|
this.embeddedWebsiteManager = new EmbeddedWebsiteManager(this);
|
||||||
|
|
||||||
//add layer on map
|
//add layer on map
|
||||||
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
||||||
for (const layer of this.gameMap.flatLayers) {
|
for (const layer of this.gameMap.flatLayers) {
|
||||||
@ -487,26 +491,20 @@ export class GameScene extends DirtyScene {
|
|||||||
object.properties,
|
object.properties,
|
||||||
'in the "' + object.name + '" object of type "website"'
|
'in the "' + object.name + '" object of type "website"'
|
||||||
);
|
);
|
||||||
const absoluteUrl = new URL(url, this.MapUrlFile).toString();
|
const allowApi = PropertyUtils.findBooleanProperty("allowApi", object.properties);
|
||||||
|
|
||||||
const iframe = document.createElement("iframe");
|
// TODO: add a "allow" property to iframe
|
||||||
iframe.src = absoluteUrl;
|
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
||||||
iframe.style.width = object.width + "px";
|
object.name,
|
||||||
iframe.style.height = object.height + "px";
|
url,
|
||||||
iframe.style.margin = "0";
|
object.x,
|
||||||
iframe.style.padding = "0";
|
object.y,
|
||||||
iframe.style.border = "none";
|
object.width,
|
||||||
|
object.height,
|
||||||
this.add.dom(object.x, object.y).setElement(iframe).setOrigin(0, 0);
|
object.visible,
|
||||||
|
allowApi ?? false,
|
||||||
const allowApi = PropertyUtils.findBooleanProperty(
|
""
|
||||||
"allowApi",
|
|
||||||
object.properties,
|
|
||||||
);
|
);
|
||||||
if (allowApi) {
|
|
||||||
iframeListener.registerIframe(iframe);
|
|
||||||
this.inMapIframes.push(iframe);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1197,6 +1195,27 @@ ${escapedMessage}
|
|||||||
iframeListener.registerAnswerer("removeActionMessage", (message) => {
|
iframeListener.registerAnswerer("removeActionMessage", (message) => {
|
||||||
layoutManagerActionStore.removeAction(message.uuid);
|
layoutManagerActionStore.removeAction(message.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.iframeSubscriptionList.push(
|
||||||
|
iframeListener.modifyEmbeddedWebsiteStream.subscribe((embeddedWebsite) => {
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
// TODO
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPropertyLayer(
|
private setPropertyLayer(
|
||||||
@ -1264,7 +1283,7 @@ ${escapedMessage}
|
|||||||
let targetRoom: Room;
|
let targetRoom: Room;
|
||||||
try {
|
try {
|
||||||
targetRoom = await Room.createRoom(roomUrl);
|
targetRoom = await Room.createRoom(roomUrl);
|
||||||
} catch (e) {
|
} catch (e /*: unknown*/) {
|
||||||
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
||||||
this.mapTransitioning = false;
|
this.mapTransitioning = false;
|
||||||
return;
|
return;
|
||||||
@ -1303,9 +1322,6 @@ ${escapedMessage}
|
|||||||
for (const script of scripts) {
|
for (const script of scripts) {
|
||||||
iframeListener.unregisterScript(script);
|
iframeListener.unregisterScript(script);
|
||||||
}
|
}
|
||||||
for (const iframe of this.inMapIframes) {
|
|
||||||
iframeListener.unregisterIframe(iframe);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.stopJitsi();
|
this.stopJitsi();
|
||||||
audioManager.unloadAudio();
|
audioManager.unloadAudio();
|
||||||
@ -1327,6 +1343,7 @@ ${escapedMessage}
|
|||||||
iframeListener.unregisterAnswerer("triggerActionMessage");
|
iframeListener.unregisterAnswerer("triggerActionMessage");
|
||||||
iframeListener.unregisterAnswerer("removeActionMessage");
|
iframeListener.unregisterAnswerer("removeActionMessage");
|
||||||
this.sharedVariablesManager?.close();
|
this.sharedVariablesManager?.close();
|
||||||
|
this.embeddedWebsiteManager?.close();
|
||||||
|
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
@ -1398,7 +1415,7 @@ ${escapedMessage}
|
|||||||
try {
|
try {
|
||||||
const room = await Room.createRoom(exitRoomPath);
|
const room = await Room.createRoom(exitRoomPath);
|
||||||
return gameManager.loadMap(room, this.scene);
|
return gameManager.loadMap(room, this.scene);
|
||||||
} catch (e) {
|
} catch (e /*: unknown*/) {
|
||||||
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
{
|
{
|
||||||
"columns":11,
|
"columns":11,
|
||||||
"firstgid":1,
|
"firstgid":1,
|
||||||
"image":"tileset1.png",
|
"image":"..\/tileset1.png",
|
||||||
"imageheight":352,
|
"imageheight":352,
|
||||||
"imagewidth":352,
|
"imagewidth":352,
|
||||||
"margin":0,
|
"margin":0,
|
85
maps/tests/EmbeddedWebsite/website_in_map_script.html
Normal file
85
maps/tests/EmbeddedWebsite/website_in_map_script.html
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<!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', () => {
|
||||||
|
console.log('On load');
|
||||||
|
WA.onInit().then(() => {
|
||||||
|
console.log('After WA init');
|
||||||
|
const createButton = document.getElementById('createEmbeddedWebsite');
|
||||||
|
const deleteButton = document.getElementById('deleteEmbeddedWebsite');
|
||||||
|
const xField = document.getElementById('x');
|
||||||
|
const yField = document.getElementById('y');
|
||||||
|
const widthField = document.getElementById('width');
|
||||||
|
const heightField = document.getElementById('height');
|
||||||
|
const urlField = document.getElementById('url');
|
||||||
|
const visibleField = document.getElementById('visible');
|
||||||
|
|
||||||
|
createButton.addEventListener('click', () => {
|
||||||
|
console.log('CREATING NEW EMBEDDED IFRAME');
|
||||||
|
WA.room.website.create({
|
||||||
|
name: "test",
|
||||||
|
url: urlField.value,
|
||||||
|
position: {
|
||||||
|
x: parseInt(xField.value),
|
||||||
|
y: parseInt(yField.value),
|
||||||
|
width: parseInt(widthField.value),
|
||||||
|
height: parseInt(heightField.value),
|
||||||
|
},
|
||||||
|
visible: !!visibleField.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteButton.addEventListener('click', () => {
|
||||||
|
WA.room.website.delete("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
xField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.x = parseInt(this.value);
|
||||||
|
});
|
||||||
|
yField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.y = parseInt(this.value);
|
||||||
|
});
|
||||||
|
widthField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.width = parseInt(this.value);
|
||||||
|
});
|
||||||
|
heightField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.height = parseInt(this.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
urlField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.url = this.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
visibleField.addEventListener('change', async function() {
|
||||||
|
const website = await WA.room.website.get('test');
|
||||||
|
website.visible = this.checked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
X: <input type="text" id="x" value="64" /><br/>
|
||||||
|
Y: <input type="text" id="y" value="64" /><br/>
|
||||||
|
width: <input type="text" id="width" value="600" /><br/>
|
||||||
|
height: <input type="text" id="height" value="400" /><br/>
|
||||||
|
URL: <input type="text" id="url" value="https://mensuel.framapad.org/p/rt6c904745-9oxm?lang=en" /><br/>
|
||||||
|
Visible: <input type="checkbox" id="visible" value=1 /><br/>
|
||||||
|
|
||||||
|
<button id="createEmbeddedWebsite">Create embedded website</button>
|
||||||
|
|
||||||
|
<button id="deleteEmbeddedWebsite">Delete embedded website</button>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
93
maps/tests/EmbeddedWebsite/website_in_map_script.json
Normal file
93
maps/tests/EmbeddedWebsite/website_in_map_script.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{ "compressionlevel":-1,
|
||||||
|
"height":30,
|
||||||
|
"infinite":false,
|
||||||
|
"layers":[
|
||||||
|
{
|
||||||
|
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
"height":30,
|
||||||
|
"id":1,
|
||||||
|
"name":"floor",
|
||||||
|
"opacity":1,
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"openWebsite",
|
||||||
|
"type":"string",
|
||||||
|
"value":"website_in_map_script.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteAllowApi",
|
||||||
|
"type":"bool",
|
||||||
|
"value":true
|
||||||
|
}],
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 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, 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, 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, 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, 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, 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, 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, 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":30,
|
||||||
|
"id":2,
|
||||||
|
"name":"start",
|
||||||
|
"opacity":1,
|
||||||
|
"type":"tilelayer",
|
||||||
|
"visible":true,
|
||||||
|
"width":30,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"draworder":"topdown",
|
||||||
|
"id":3,
|
||||||
|
"name":"floorLayer",
|
||||||
|
"objects":[
|
||||||
|
{
|
||||||
|
"height":393,
|
||||||
|
"id":1,
|
||||||
|
"name":"",
|
||||||
|
"rotation":0,
|
||||||
|
"text":
|
||||||
|
{
|
||||||
|
"fontfamily":"Sans Serif",
|
||||||
|
"pixelsize":13,
|
||||||
|
"text":"Test:\nClick the \"create\" button.\n\nResult:\nA website should appear.\n\nTest:\nUse the fields to modify settings.\n\nResult:\nThe iframe is modified accordingly.\n\nTest:\nClick the \"delete\" button.\n\nResult:\nThe iframe is deleted\n\nTest:\nClick the \"create\" button.\n\nResult:\nA website should appear.\n",
|
||||||
|
"wrap":true
|
||||||
|
},
|
||||||
|
"type":"",
|
||||||
|
"visible":true,
|
||||||
|
"width":315.4375,
|
||||||
|
"x":68.4021076998051,
|
||||||
|
"y":8.73391812865529
|
||||||
|
}],
|
||||||
|
"opacity":1,
|
||||||
|
"type":"objectgroup",
|
||||||
|
"visible":true,
|
||||||
|
"x":0,
|
||||||
|
"y":0
|
||||||
|
}],
|
||||||
|
"nextlayerid":6,
|
||||||
|
"nextobjectid":3,
|
||||||
|
"orientation":"orthogonal",
|
||||||
|
"renderorder":"right-down",
|
||||||
|
"tiledversion":"2021.03.23",
|
||||||
|
"tileheight":32,
|
||||||
|
"tilesets":[
|
||||||
|
{
|
||||||
|
"columns":11,
|
||||||
|
"firstgid":1,
|
||||||
|
"image":"..\/tileset1.png",
|
||||||
|
"imageheight":352,
|
||||||
|
"imagewidth":352,
|
||||||
|
"margin":0,
|
||||||
|
"name":"tileset1",
|
||||||
|
"spacing":0,
|
||||||
|
"tilecount":121,
|
||||||
|
"tileheight":32,
|
||||||
|
"tilewidth":32
|
||||||
|
}],
|
||||||
|
"tilewidth":32,
|
||||||
|
"type":"map",
|
||||||
|
"version":1.5,
|
||||||
|
"width":30
|
||||||
|
}
|
@ -215,7 +215,15 @@
|
|||||||
<input type="radio" name="test-website-objects"> Success <input type="radio" name="test-website-objects"> Failure <input type="radio" name="test-website-objects" checked> Pending
|
<input type="radio" name="test-website-objects"> Success <input type="radio" name="test-website-objects"> Failure <input type="radio" name="test-website-objects" checked> Pending
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="testLink" data-testmap="website_in_map.json" target="_blank">Testing websites inside a map</a>
|
<a href="#" class="testLink" data-testmap="EmbeddedWebsite/website_in_map.json" target="_blank">Testing websites inside a map</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="radio" name="test-website-objects-script"> Success <input type="radio" name="test-website-objects-script"> Failure <input type="radio" name="test-website-objects-script" checked> Pending
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="testLink" data-testmap="EmbeddedWebsite/website_in_map_script.json" target="_blank">Testing scripting API for websites inside a map</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
Reference in New Issue
Block a user