Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
commit
a633f79dd7
@ -1,5 +1,11 @@
|
|||||||
## Version develop
|
## Version develop
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- New scripting API features :
|
||||||
|
- Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu` to add a custom menu or an iframe to the menu.
|
||||||
|
|
||||||
|
## Version 1.4.14
|
||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
- New scripting API features :
|
- New scripting API features :
|
||||||
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
- Use `WA.room.loadTileset(url: string) : Promise<number>` to load a tileset from a JSON file.
|
||||||
|
@ -18,3 +18,4 @@ The list of functions below is **deprecated**. You should not use those but. use
|
|||||||
- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
|
- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
|
||||||
- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
|
- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
|
||||||
- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
|
- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
|
||||||
|
- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`.
|
@ -9,6 +9,7 @@ Moreover, `WA.state` functions can be used to persist this state across reloads.
|
|||||||
```
|
```
|
||||||
WA.state.saveVariable(key : string, data : unknown): void
|
WA.state.saveVariable(key : string, data : unknown): void
|
||||||
WA.state.loadVariable(key : string) : unknown
|
WA.state.loadVariable(key : string) : unknown
|
||||||
|
WA.state.hasVariable(key : string) : boolean
|
||||||
WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription
|
WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription
|
||||||
WA.state.[any property]: unknown
|
WA.state.[any property]: unknown
|
||||||
```
|
```
|
||||||
|
@ -68,25 +68,53 @@ WA.room.onLeaveZone('myZone', () => {
|
|||||||
|
|
||||||
### Add custom menu
|
### Add custom menu
|
||||||
|
|
||||||
```typescript
|
|
||||||
WA.ui.registerMenuCommand(menuCommand: string, callback: (menuCommand: string) => void): void
|
|
||||||
```
|
```
|
||||||
Add a custom menu item containing the text `commandDescriptor` in the main menu. A click on the menu will trigger the `callback`.
|
WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu
|
||||||
|
```
|
||||||
|
Add a custom menu item containing the text `commandDescriptor` in the navbar of the menu.
|
||||||
|
`options` attribute accepts an object with three properties :
|
||||||
|
- `callback : (commandDescriptor: string) => void` : A click on the custom menu will trigger the `callback`.
|
||||||
|
- `iframe: string` : A click on the custom menu will open the `iframe` inside the menu.
|
||||||
|
- `allowApi?: boolean` : Allow the iframe of the custom menu to use the Scripting API.
|
||||||
|
|
||||||
|
Important : `options` accepts only `callback` or `iframe` not both.
|
||||||
|
|
||||||
Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script.
|
Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script.
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<img src="images/custom-menu-navbar.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<img src="images/custom-menu-iframe.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
const menu = WA.ui.registerMenuCommand('menu test',
|
||||||
|
{
|
||||||
|
callback: () => {
|
||||||
|
WA.chat.sendChatMessage('test');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
WA.ui.registerMenuCommand("test", () => {
|
// Some time later, if you want to remove the menu:
|
||||||
WA.chat.sendChatMessage("test clicked", "menu cmd")
|
menu.remove();
|
||||||
})
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<div class="col">
|
Please note that `registerMenuCommand` returns an object of the `Menu` class.
|
||||||
<img src="images/menu-command.png" class="figure-img img-fluid rounded" alt="" />
|
|
||||||
</div>
|
The `Menu` class contains a single method: `remove(): void`. This will obviously remove the menu when called.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
class Menu {
|
||||||
|
/**
|
||||||
|
* Remove the menu
|
||||||
|
*/
|
||||||
|
remove() {};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BIN
docs/maps/images/custom-menu-iframe.png
Normal file
BIN
docs/maps/images/custom-menu-iframe.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
docs/maps/images/custom-menu-navbar.png
Normal file
BIN
docs/maps/images/custom-menu-navbar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
front/dist/static/images/logo-WA-min.png
vendored
Normal file
BIN
front/dist/static/images/logo-WA-min.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -15,7 +15,6 @@ import type { SetPropertyEvent } from "./setPropertyEvent";
|
|||||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||||
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
|
||||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||||
import type { SetTilesEvent } from "./SetTilesEvent";
|
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||||
import type { SetVariableEvent } from "./SetVariableEvent";
|
import type { SetVariableEvent } from "./SetVariableEvent";
|
||||||
@ -33,6 +32,7 @@ import type {
|
|||||||
TriggerActionMessageEvent,
|
TriggerActionMessageEvent,
|
||||||
} from "./ui/TriggerActionMessageEvent";
|
} from "./ui/TriggerActionMessageEvent";
|
||||||
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
||||||
|
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T;
|
data: T;
|
||||||
@ -63,7 +63,8 @@ export type IframeEventMap = {
|
|||||||
stopSound: null;
|
stopSound: null;
|
||||||
getState: undefined;
|
getState: undefined;
|
||||||
loadTileset: LoadTilesetEvent;
|
loadTileset: LoadTilesetEvent;
|
||||||
registerMenuCommand: MenuItemRegisterEvent;
|
registerMenu: MenuRegisterEvent;
|
||||||
|
unregisterMenu: UnregisterMenuEvent;
|
||||||
setTiles: SetTilesEvent;
|
setTiles: SetTilesEvent;
|
||||||
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
modifyEmbeddedWebsite: Partial<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as tg from "generic-type-guard";
|
import * as tg from "generic-type-guard";
|
||||||
import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
|
||||||
|
|
||||||
export const isSetVariableEvent = new tg.IsInterface()
|
export const isSetVariableEvent = new tg.IsInterface()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import * as tg from "generic-type-guard";
|
|
||||||
import { Subject } from "rxjs";
|
|
||||||
import { subMenusStore } from "../../../Stores/MenuStore";
|
|
||||||
|
|
||||||
export const isMenuItemRegisterEvent = new tg.IsInterface()
|
|
||||||
.withProperties({
|
|
||||||
menutItem: tg.isString,
|
|
||||||
})
|
|
||||||
.get();
|
|
||||||
/**
|
|
||||||
* A message sent from the iFrame to the game to add a new menu item.
|
|
||||||
*/
|
|
||||||
export type MenuItemRegisterEvent = tg.GuardedType<typeof isMenuItemRegisterEvent>;
|
|
||||||
|
|
||||||
export const isMenuItemRegisterIframeEvent = new tg.IsInterface()
|
|
||||||
.withProperties({
|
|
||||||
type: tg.isSingletonString("registerMenuCommand"),
|
|
||||||
data: isMenuItemRegisterEvent,
|
|
||||||
})
|
|
||||||
.get();
|
|
||||||
|
|
||||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
|
||||||
subMenusStore.addMenu(event.menutItem);
|
|
||||||
}
|
|
31
front/src/Api/Events/ui/MenuRegisterEvent.ts
Normal file
31
front/src/Api/Events/ui/MenuRegisterEvent.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from a script to the game to remove a custom menu from the menu
|
||||||
|
*/
|
||||||
|
export const isUnregisterMenuEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type UnregisterMenuEvent = tg.GuardedType<typeof isUnregisterMenuEvent>;
|
||||||
|
|
||||||
|
export const isMenuRegisterOptions = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
allowApi: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message sent from a script to the game to add a custom menu from the menu
|
||||||
|
*/
|
||||||
|
export const isMenuRegisterEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
name: tg.isString,
|
||||||
|
iframe: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
|
options: isMenuRegisterOptions,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type MenuRegisterEvent = tg.GuardedType<typeof isMenuRegisterEvent>;
|
@ -29,12 +29,12 @@ import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"
|
|||||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||||
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent";
|
||||||
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 { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
|
||||||
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
|
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
|
||||||
import { subMenusStore } from "../Stores/MenuStore";
|
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
query: IframeQueryMap[T]["query"],
|
query: IframeQueryMap[T]["query"],
|
||||||
@ -258,17 +258,23 @@ class IframeListener {
|
|||||||
this._removeBubbleStream.next();
|
this._removeBubbleStream.next();
|
||||||
} else if (payload.type == "onPlayerMove") {
|
} else if (payload.type == "onPlayerMove") {
|
||||||
this.sendPlayerMove = true;
|
this.sendPlayerMove = true;
|
||||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
|
||||||
const data = payload.data.menutItem;
|
|
||||||
// @ts-ignore
|
|
||||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
|
||||||
subMenusStore.removeMenu(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)) {
|
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
||||||
this._modifyEmbeddedWebsiteStream.next(payload.data);
|
this._modifyEmbeddedWebsiteStream.next(payload.data);
|
||||||
|
} else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) {
|
||||||
|
const dataName = payload.data.name;
|
||||||
|
this.iframeCloseCallbacks.get(iframe)?.push(() => {
|
||||||
|
handleMenuUnregisterEvent(dataName);
|
||||||
|
});
|
||||||
|
handleMenuRegistrationEvent(
|
||||||
|
payload.data.name,
|
||||||
|
payload.data.iframe,
|
||||||
|
foundSrc,
|
||||||
|
payload.data.options
|
||||||
|
);
|
||||||
|
} else if (payload.type == "unregisterMenu" && isUnregisterMenuEvent(payload.data)) {
|
||||||
|
handleMenuUnregisterEvent(payload.data.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
17
front/src/Api/iframe/Ui/Menu.ts
Normal file
17
front/src/Api/iframe/Ui/Menu.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { sendToWorkadventure } from "../IframeApiContribution";
|
||||||
|
|
||||||
|
export class Menu {
|
||||||
|
constructor(private menuName: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove the menu
|
||||||
|
*/
|
||||||
|
public remove() {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "unregisterMenu",
|
||||||
|
data: {
|
||||||
|
name: this.menuName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,10 @@ export class WorkadventureStateCommands extends IframeApiContribution<Workadvent
|
|||||||
return variables.get(key);
|
return variables.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasVariable(key: string): boolean {
|
||||||
|
return variables.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
onVariableChange(key: string): Observable<unknown> {
|
onVariableChange(key: string): Observable<unknown> {
|
||||||
let subject = variableSubscribers.get(key);
|
let subject = variableSubscribers.get(key);
|
||||||
if (subject === undefined) {
|
if (subject === undefined) {
|
||||||
@ -85,6 +89,12 @@ const proxyCommand = new Proxy(new WorkadventureStateCommands(), {
|
|||||||
target.saveVariable(p.toString(), value);
|
target.saveVariable(p.toString(), value);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
||||||
|
if (p in target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return target.hasVariable(p.toString());
|
||||||
|
},
|
||||||
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
||||||
|
|
||||||
export default proxyCommand;
|
export default proxyCommand;
|
||||||
|
@ -6,6 +6,8 @@ import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescrip
|
|||||||
import { Popup } from "./Ui/Popup";
|
import { Popup } from "./Ui/Popup";
|
||||||
import { ActionMessage } from "./Ui/ActionMessage";
|
import { ActionMessage } from "./Ui/ActionMessage";
|
||||||
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
|
||||||
|
import { Menu } from "./Ui/Menu";
|
||||||
|
import type { RequireOnlyOne } from "../types";
|
||||||
|
|
||||||
let popupId = 0;
|
let popupId = 0;
|
||||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||||
@ -14,9 +16,18 @@ const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
|
|||||||
Map<number, ButtonClickedCallback>
|
Map<number, ButtonClickedCallback>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
const menus: Map<string, Menu> = new Map<string, Menu>();
|
||||||
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
||||||
const actionMessages = new Map<string, ActionMessage>();
|
const actionMessages = new Map<string, ActionMessage>();
|
||||||
|
|
||||||
|
interface MenuDescriptor {
|
||||||
|
callback?: (commandDescriptor: string) => void;
|
||||||
|
iframe?: string;
|
||||||
|
allowApi?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuOptions = RequireOnlyOne<MenuDescriptor, "callback" | "iframe">;
|
||||||
|
|
||||||
interface ZonedPopupOptions {
|
interface ZonedPopupOptions {
|
||||||
zone: string;
|
zone: string;
|
||||||
objectLayerName?: string;
|
objectLayerName?: string;
|
||||||
@ -53,6 +64,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||||||
typeChecker: isMenuItemClickedEvent,
|
typeChecker: isMenuItemClickedEvent,
|
||||||
callback: (event) => {
|
callback: (event) => {
|
||||||
const callback = menuCallbacks.get(event.menuItem);
|
const callback = menuCallbacks.get(event.menuItem);
|
||||||
|
const menu = menus.get(event.menuItem);
|
||||||
|
if (menu === undefined) {
|
||||||
|
throw new Error('Could not find menu named "' + event.menuItem + '"');
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(event.menuItem);
|
callback(event.menuItem);
|
||||||
}
|
}
|
||||||
@ -106,14 +121,53 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu {
|
||||||
menuCallbacks.set(commandDescriptor, callback);
|
const menu = new Menu(commandDescriptor);
|
||||||
|
|
||||||
|
if (typeof options === "function") {
|
||||||
|
menuCallbacks.set(commandDescriptor, options);
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: "registerMenuCommand",
|
type: "registerMenu",
|
||||||
data: {
|
data: {
|
||||||
menutItem: commandDescriptor,
|
name: commandDescriptor,
|
||||||
|
options: {
|
||||||
|
allowApi: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
options.allowApi = options.allowApi === undefined ? options.iframe !== undefined : options.allowApi;
|
||||||
|
|
||||||
|
if (options.iframe !== undefined) {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "registerMenu",
|
||||||
|
data: {
|
||||||
|
name: commandDescriptor,
|
||||||
|
iframe: options.iframe,
|
||||||
|
options: {
|
||||||
|
allowApi: options.allowApi,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (options.callback !== undefined) {
|
||||||
|
menuCallbacks.set(commandDescriptor, options.callback);
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "registerMenu",
|
||||||
|
data: {
|
||||||
|
name: commandDescriptor,
|
||||||
|
options: {
|
||||||
|
allowApi: options.allowApi,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"When adding a menu with WA.ui.registerMenuCommand, you must pass either an iframe or a callback"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menus.set(commandDescriptor, menu);
|
||||||
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBubble(): void {
|
displayBubble(): void {
|
||||||
|
4
front/src/Api/types.ts
Normal file
4
front/src/Api/types.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type RequireOnlyOne<T, keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, keys>> &
|
||||||
|
{
|
||||||
|
[K in keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<keys, K>, undefined>>;
|
||||||
|
}[keys];
|
33
front/src/Components/Menu/CustomSubMenu.svelte
Normal file
33
front/src/Components/Menu/CustomSubMenu.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
|
import {iframeListener} from "../../Api/IframeListener";
|
||||||
|
|
||||||
|
export let url: string;
|
||||||
|
export let allowApi: boolean;
|
||||||
|
|
||||||
|
let HTMLIframe: HTMLIFrameElement;
|
||||||
|
|
||||||
|
onMount( () => {
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.registerIframe(HTMLIframe);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy( () => {
|
||||||
|
if (allowApi) {
|
||||||
|
iframeListener.unregisterIframe(HTMLIframe);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<iframe title="customSubMenu" src="{url}" bind:this={HTMLIframe}></iframe>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
iframe {
|
||||||
|
border: none;
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
@ -6,16 +6,32 @@
|
|||||||
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||||
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||||
import ContactSubMenu from "./ContactSubMenu.svelte";
|
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||||
import {menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore";
|
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||||
import {onMount} from "svelte";
|
import {customMenuIframe, menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore";
|
||||||
|
import {onDestroy, onMount} from "svelte";
|
||||||
import {get} from "svelte/store";
|
import {get} from "svelte/store";
|
||||||
|
import type {Unsubscriber} from "svelte/store";
|
||||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||||
|
|
||||||
let activeSubMenu: string = SubMenusInterface.settings;
|
let activeSubMenu: string = SubMenusInterface.settings;
|
||||||
let activeComponent: typeof SettingsSubMenu = SettingsSubMenu;
|
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
||||||
|
let props: { url: string, allowApi: boolean };
|
||||||
|
let unsubscriberSubMenuStore: Unsubscriber;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||||
|
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||||
switchMenu(SubMenusInterface.settings);
|
switchMenu(SubMenusInterface.settings);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
switchMenu(SubMenusInterface.settings);
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if(unsubscriberSubMenuStore) {
|
||||||
|
unsubscriberSubMenuStore();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function switchMenu(menu: string) {
|
function switchMenu(menu: string) {
|
||||||
@ -41,8 +57,14 @@
|
|||||||
activeComponent = ContactSubMenu;
|
activeComponent = ContactSubMenu;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
const customMenu = customMenuIframe.get(menu);
|
||||||
|
if (customMenu !== undefined) {
|
||||||
|
props = { url: customMenu.url, allowApi: customMenu.allowApi };
|
||||||
|
activeComponent = CustomSubMenu;
|
||||||
|
} else {
|
||||||
sendMenuClickedEvent(menu);
|
sendMenuClickedEvent(menu);
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else throw ("There is no menu called " + menu);
|
} else throw ("There is no menu called " + menu);
|
||||||
@ -74,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-submenu-container nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
<div class="menu-submenu-container nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
|
||||||
<h2>{activeSubMenu}</h2>
|
<h2>{activeSubMenu}</h2>
|
||||||
<svelte:component this={activeComponent}/>
|
<svelte:component this={activeComponent} {...props}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -80,8 +80,11 @@ export class GameMap {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
this.triggerAll();
|
||||||
|
}
|
||||||
|
|
||||||
const newProps = this.getProperties(key);
|
private triggerAll(): void {
|
||||||
|
const newProps = this.getProperties(this.key ?? 0);
|
||||||
const oldProps = this.lastProperties;
|
const oldProps = this.lastProperties;
|
||||||
this.lastProperties = newProps;
|
this.lastProperties = newProps;
|
||||||
|
|
||||||
@ -253,4 +256,34 @@ export class GameMap {
|
|||||||
}
|
}
|
||||||
return this.tileNameMap.get(tile);
|
return this.tileNameMap.get(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setLayerProperty(
|
||||||
|
layerName: string,
|
||||||
|
propertyName: string,
|
||||||
|
propertyValue: string | number | undefined | boolean
|
||||||
|
) {
|
||||||
|
const layer = this.findLayer(layerName);
|
||||||
|
if (layer === undefined) {
|
||||||
|
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (layer.properties === undefined) {
|
||||||
|
layer.properties = [];
|
||||||
|
}
|
||||||
|
const property = layer.properties.find((property) => property.name === propertyName);
|
||||||
|
if (property === undefined) {
|
||||||
|
if (propertyValue === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (propertyValue === undefined) {
|
||||||
|
const index = layer.properties.indexOf(property);
|
||||||
|
layer.properties.splice(index, 1);
|
||||||
|
}
|
||||||
|
property.value = propertyValue;
|
||||||
|
|
||||||
|
this.triggerAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1276,30 +1276,10 @@ export class GameScene extends DirtyScene {
|
|||||||
propertyName: string,
|
propertyName: string,
|
||||||
propertyValue: string | number | boolean | undefined
|
propertyValue: string | number | boolean | undefined
|
||||||
): void {
|
): void {
|
||||||
const layer = this.gameMap.findLayer(layerName);
|
|
||||||
if (layer === undefined) {
|
|
||||||
console.warn('Could not find layer "' + layerName + '" when calling setProperty');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (propertyName === "exitUrl" && typeof propertyValue === "string") {
|
if (propertyName === "exitUrl" && typeof propertyValue === "string") {
|
||||||
this.loadNextGameFromExitUrl(propertyValue);
|
this.loadNextGameFromExitUrl(propertyValue);
|
||||||
}
|
}
|
||||||
if (layer.properties === undefined) {
|
this.gameMap.setLayerProperty(layerName, propertyName, propertyValue);
|
||||||
layer.properties = [];
|
|
||||||
}
|
|
||||||
const property = layer.properties.find((property) => property.name === propertyName);
|
|
||||||
if (property === undefined) {
|
|
||||||
if (propertyValue === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (propertyValue === undefined) {
|
|
||||||
const index = layer.properties.indexOf(property);
|
|
||||||
layer.properties.splice(index, 1);
|
|
||||||
}
|
|
||||||
property.value = propertyValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setLayerVisibility(layerName: string, visible: boolean): void {
|
private setLayerVisibility(layerName: string, visible: boolean): void {
|
||||||
|
@ -82,3 +82,35 @@ function checkSubMenuToShow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkSubMenuToShow();
|
checkSubMenuToShow();
|
||||||
|
|
||||||
|
export const customMenuIframe = new Map<string, { url: string; allowApi: boolean }>();
|
||||||
|
|
||||||
|
export function handleMenuRegistrationEvent(
|
||||||
|
menuName: string,
|
||||||
|
iframeUrl: string | undefined = undefined,
|
||||||
|
source: string | undefined = undefined,
|
||||||
|
options: { allowApi: boolean }
|
||||||
|
) {
|
||||||
|
if (get(subMenusStore).includes(menuName)) {
|
||||||
|
console.warn("The menu " + menuName + " already exist.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subMenusStore.addMenu(menuName);
|
||||||
|
|
||||||
|
if (iframeUrl !== undefined) {
|
||||||
|
const url = new URL(iframeUrl, source);
|
||||||
|
customMenuIframe.set(menuName, { url: url.toString(), allowApi: options.allowApi });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleMenuUnregisterEvent(menuName: string) {
|
||||||
|
const subMenuGeneral: string[] = Object.values(SubMenusInterface);
|
||||||
|
if (subMenuGeneral.includes(menuName)) {
|
||||||
|
console.warn("The menu " + menuName + " is a mandatory menu. It can't be remove");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subMenusStore.removeMenu(menuName);
|
||||||
|
customMenuIframe.delete(menuName);
|
||||||
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type Phaser from "phaser";
|
import type Phaser from "phaser";
|
||||||
|
|
||||||
export type CursorKey = {
|
export type CursorKey = {
|
||||||
isDown: boolean
|
isDown: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Direction = 'left' | 'right' | 'up' | 'down'
|
export type Direction = "left" | "right" | "up" | "down";
|
||||||
|
|
||||||
export interface CursorKeys extends Record<Direction, CursorKey> {
|
export interface CursorKeys extends Record<Direction, CursorKey> {
|
||||||
left: CursorKey;
|
left: CursorKey;
|
||||||
@ -21,4 +21,3 @@ export interface IVirtualJoystick extends Phaser.GameObjects.GameObject {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
createCursorKeys: () => CursorKeys;
|
createCursorKeys: () => CursorKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
maps/tests/Metadata/customIframeMenu.html
Normal file
9
maps/tests/Metadata/customIframeMenu.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Custom Iframe Menu</title>
|
||||||
|
</head>
|
||||||
|
<body style="text-align: center">
|
||||||
|
<p style="color: whitesmoke">This is an iframe in a custom menu.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
20
maps/tests/Metadata/customIframeMenuApi.html
Normal file
20
maps/tests/Metadata/customIframeMenuApi.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>API in iframe menu</title>
|
||||||
|
<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 style="text-align: center">
|
||||||
|
<p style="color: whitesmoke">This is an iframe in a custom menu.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,6 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<script>
|
<script>
|
||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
|
// Don't do this at home kids! The "document.referrer" part is actually inserting a XSS security.
|
||||||
@ -8,13 +8,11 @@
|
|||||||
script.setAttribute('src', document.referrer + 'iframe_api.js');
|
script.setAttribute('src', document.referrer + 'iframe_api.js');
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
WA.ui.registerMenuCommand("test", () => {
|
WA.ui.registerMenuCommand('test', 'customIframeMenu.html', {autoClose: true});
|
||||||
WA.chat.sendChatMessage("test clicked", "menu cmd")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Add a custom menu</p>
|
<p>Add a custom menu</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
15
maps/tests/Metadata/customMenu.js
Normal file
15
maps/tests/Metadata/customMenu.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
let menuIframeApi = undefined;
|
||||||
|
|
||||||
|
WA.ui.registerMenuCommand('custom callback menu', () => {
|
||||||
|
WA.nav.openTab("https://workadventu.re/");
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.ui.registerMenuCommand('custom iframe menu', {iframe: 'customIframeMenu.html'});
|
||||||
|
|
||||||
|
WA.room.onEnterZone('iframeMenu', () => {
|
||||||
|
menuIframeApi = WA.ui.registerMenuCommand('IFRAME USE API', {iframe: 'customIframeMenuApi.html', allowApi: true});
|
||||||
|
})
|
||||||
|
|
||||||
|
WA.room.onLeaveZone('iframeMenu', () => {
|
||||||
|
menuIframeApi.remove();
|
||||||
|
})
|
@ -1,11 +1,4 @@
|
|||||||
{ "compressionlevel":-1,
|
{ "compressionlevel":-1,
|
||||||
"editorsettings":
|
|
||||||
{
|
|
||||||
"export":
|
|
||||||
{
|
|
||||||
"target":"."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"height":10,
|
"height":10,
|
||||||
"infinite":false,
|
"infinite":false,
|
||||||
"layers":[
|
"layers":[
|
||||||
@ -22,7 +15,7 @@
|
|||||||
"y":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],
|
"data":[33, 34, 34, 34, 34, 34, 35, 0, 0, 0, 41, 42, 42, 42, 42, 42, 43, 0, 0, 0, 41, 42, 42, 42, 42, 42, 43, 0, 0, 0, 41, 42, 42, 42, 42, 42, 59, 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, 0, 0, 0, 42, 42, 42, 42, 42, 42, 43, 0, 0, 0, 42, 42, 42, 42, 42, 42, 43, 0, 0, 0, 50, 50, 50, 50, 50, 50, 51],
|
||||||
"height":10,
|
"height":10,
|
||||||
"id":2,
|
"id":2,
|
||||||
"name":"bottom",
|
"name":"bottom",
|
||||||
@ -34,16 +27,16 @@
|
|||||||
"y":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, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
"data":[0, 0, 0, 0, 0, 0, 0, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, 0, 0, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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,
|
"height":10,
|
||||||
"id":6,
|
"id":3,
|
||||||
"name":"exit",
|
"name":"ZoneIframeMenu",
|
||||||
"opacity":1,
|
"opacity":1,
|
||||||
"properties":[
|
"properties":[
|
||||||
{
|
{
|
||||||
"name":"exitUrl",
|
"name":"zone",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"showHideLayer.json"
|
"value":"iframeMenu"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
@ -52,10 +45,10 @@
|
|||||||
"y":0
|
"y":0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"data":[0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, 0, 0, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0],
|
||||||
"height":10,
|
"height":10,
|
||||||
"id":4,
|
"id":4,
|
||||||
"name":"metadata",
|
"name":"CoWebSiteIframeMenu",
|
||||||
"opacity":1,
|
"opacity":1,
|
||||||
"properties":[
|
"properties":[
|
||||||
{
|
{
|
||||||
@ -73,41 +66,18 @@
|
|||||||
"width":10,
|
"width":10,
|
||||||
"x":0,
|
"x":0,
|
||||||
"y":0
|
"y":0
|
||||||
},
|
|
||||||
{
|
|
||||||
"draworder":"topdown",
|
|
||||||
"id":5,
|
|
||||||
"name":"floorLayer",
|
|
||||||
"objects":[
|
|
||||||
{
|
|
||||||
"height":217.142414860681,
|
|
||||||
"id":1,
|
|
||||||
"name":"",
|
|
||||||
"rotation":0,
|
|
||||||
"text":
|
|
||||||
{
|
|
||||||
"fontfamily":"Sans Serif",
|
|
||||||
"pixelsize":9,
|
|
||||||
"text":"Test : \nWalk on the grass, an iframe open.\nResult : \nOpen the menu, a new sub-menu is displayed.\n\nTest : \nExit the grass\nResult : \nOpen the menu, the submenu has disappeared.\n\nTest : \nClick on the 'HELP' menu.\nResult : \nChat open and a 'HELP' message is displayed.\n\nTest : \nWalk on the red tile then open the menu.\nResult : \nYou have exit the room to another room, the submenu has disappeared.\n",
|
|
||||||
"wrap":true
|
|
||||||
},
|
|
||||||
"type":"",
|
|
||||||
"visible":true,
|
|
||||||
"width":305.097705765524,
|
|
||||||
"x":15.1244925229277,
|
|
||||||
"y":103.029937496349
|
|
||||||
}],
|
}],
|
||||||
"opacity":1,
|
"nextlayerid":5,
|
||||||
"type":"objectgroup",
|
"nextobjectid":1,
|
||||||
"visible":true,
|
|
||||||
"x":0,
|
|
||||||
"y":0
|
|
||||||
}],
|
|
||||||
"nextlayerid":7,
|
|
||||||
"nextobjectid":2,
|
|
||||||
"orientation":"orthogonal",
|
"orientation":"orthogonal",
|
||||||
|
"properties":[
|
||||||
|
{
|
||||||
|
"name":"script",
|
||||||
|
"type":"string",
|
||||||
|
"value":"customMenu.js"
|
||||||
|
}],
|
||||||
"renderorder":"right-down",
|
"renderorder":"right-down",
|
||||||
"tiledversion":"1.4.3",
|
"tiledversion":"1.7.1",
|
||||||
"tileheight":32,
|
"tileheight":32,
|
||||||
"tilesets":[
|
"tilesets":[
|
||||||
{
|
{
|
||||||
@ -117,163 +87,14 @@
|
|||||||
"imageheight":256,
|
"imageheight":256,
|
||||||
"imagewidth":256,
|
"imagewidth":256,
|
||||||
"margin":0,
|
"margin":0,
|
||||||
"name":"TDungeon",
|
"name":"tileset_dungeon",
|
||||||
"spacing":0,
|
"spacing":0,
|
||||||
"tilecount":64,
|
"tilecount":64,
|
||||||
"tileheight":32,
|
"tileheight":32,
|
||||||
"tiles":[
|
|
||||||
{
|
|
||||||
"id":0,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":1,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":2,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":3,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":4,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":8,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":9,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":10,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":11,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":12,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":16,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":17,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":18,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":19,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":20,
|
|
||||||
"properties":[
|
|
||||||
{
|
|
||||||
"name":"collides",
|
|
||||||
"type":"bool",
|
|
||||||
"value":true
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
"tilewidth":32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns":8,
|
|
||||||
"firstgid":65,
|
|
||||||
"image":"floortileset.png",
|
|
||||||
"imageheight":288,
|
|
||||||
"imagewidth":256,
|
|
||||||
"margin":0,
|
|
||||||
"name":"Floor",
|
|
||||||
"spacing":0,
|
|
||||||
"tilecount":72,
|
|
||||||
"tileheight":32,
|
|
||||||
"tilewidth":32
|
"tilewidth":32
|
||||||
}],
|
}],
|
||||||
"tilewidth":32,
|
"tilewidth":32,
|
||||||
"type":"map",
|
"type":"map",
|
||||||
"version":1.4,
|
"version":"1.6",
|
||||||
"width":10
|
"width":10
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user