Merge branch 'metadataScriptingApi' of github.com:thecodingmachine/workadventure into metadataScriptAPIV2
35
.github/workflows/build-and-deploy.yml
vendored
@ -2,7 +2,7 @@ name: Build, push and deploy Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, develop]
|
||||
release:
|
||||
types: [created]
|
||||
pull_request:
|
||||
@ -149,6 +149,37 @@ jobs:
|
||||
# Create a slugified value of the branch
|
||||
- uses: rlespinasse/github-slug-action@3.1.0
|
||||
|
||||
- name: Write certificate
|
||||
run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key
|
||||
env:
|
||||
CERTS_PRIVATE_KEY: ${{ secrets.CERTS_PRIVATE_KEY }}
|
||||
|
||||
- name: Download certificate
|
||||
run: mkdir secrets && scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i secret.key ubuntu@cert.workadventu.re:./config/live/workadventu.re/* secrets/
|
||||
|
||||
- name: Create namespace
|
||||
uses: steebchen/kubectl@v1.0.0
|
||||
env:
|
||||
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
|
||||
with:
|
||||
args: create namespace workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Delete old certificates in namespace
|
||||
uses: steebchen/kubectl@v1.0.0
|
||||
env:
|
||||
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
|
||||
with:
|
||||
args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} delete secret certificate-tls
|
||||
continue-on-error: true
|
||||
|
||||
- name: Install certificates in namespace
|
||||
uses: steebchen/kubectl@v1.0.0
|
||||
env:
|
||||
KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
|
||||
with:
|
||||
args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} create secret tls certificate-tls --key="secrets/privkey.pem" --cert="secrets/fullchain.pem"
|
||||
|
||||
- name: Deploy
|
||||
uses: thecodingmachine/deeployer-action@master
|
||||
env:
|
||||
@ -168,4 +199,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
||||
msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
||||
|
4
.github/workflows/continuous_integration.yml
vendored
@ -52,6 +52,10 @@ jobs:
|
||||
PUSHER_URL: "//localhost:8080"
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Svelte check"
|
||||
run: yarn run svelte-check
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Lint"
|
||||
run: yarn run lint
|
||||
working-directory: "front"
|
||||
|
41
CHANGELOG.md
@ -1,4 +1,28 @@
|
||||
## Version 1.3.9 - in dev
|
||||
## Version 1.4.x-dev
|
||||
|
||||
### Updates
|
||||
|
||||
- Added the ability to have animated tiles in maps #1216 #1217
|
||||
- Enabled outlines on actionable item again (they were disabled when migrating to Phaser 3.50) #1218
|
||||
- Enabled outlines on player names (when the mouse hovers on a player you can interact with) #1219
|
||||
- Migrated the admin console to Svelte, and redesigned the console #1211
|
||||
- Layer properties (like `exitUrl`, `silent`, etc...) can now also used in tile properties #1210 (@jonnytest1)
|
||||
- New scripting API features :
|
||||
- Use `WA.room.showLayer(): void` to show a layer
|
||||
- Use `WA.room.hideLayer(): void` to hide a layer
|
||||
- Use `WA.room.setProperty() : void` to add or change existing property of a layer
|
||||
- Use `WA.player.onPlayerMove(): void` to track the movement of the current player
|
||||
- Use `WA.room.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player
|
||||
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
||||
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
||||
|
||||
## Version 1.4.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Loading errors after the preload stage should not crash the game anymore
|
||||
|
||||
## Version 1.4.0
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
@ -12,14 +36,29 @@
|
||||
- The emote menu can be opened by clicking on your character.
|
||||
- Clicking on one of its element will close the menu and play an emote above your character.
|
||||
- This emote can be seen by other players.
|
||||
- Player names were improved. (@Kharhamel)
|
||||
- We now create a GameObject.Text instead of GameObject.BitmapText
|
||||
- now use the 'Press Start 2P' font family and added an outline
|
||||
- As a result, we can now allow non-standard letters like french accents or chinese characters!
|
||||
|
||||
- Added the contact card feature. (@Kharhamel)
|
||||
- Click on another player to see its contact info.
|
||||
- Premium-only feature unfortunately. I need to find a way to make it available for all.
|
||||
- If no contact data is found (either because the user is anonymous or because no admin backend), display an error card.
|
||||
|
||||
- Mobile support has been improved
|
||||
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
|
||||
- Mouse wheel support to zoom in / out
|
||||
- Pinch support on mobile to zoom in / out
|
||||
- Improved virtual joystick size (adapts to the zoom level)
|
||||
- Redesigned intermediate scenes
|
||||
- Redesigned Select Companion scene
|
||||
- Redesigned Enter Your Name scene
|
||||
- Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use
|
||||
- New scripting API features:
|
||||
- Use `WA.loadSound(): Sound` to load / play / stop a sound
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Pinch gesture does no longer move the character
|
||||
|
@ -38,23 +38,17 @@
|
||||
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"busboy": "^0.3.1",
|
||||
"circular-json": "^0.5.9",
|
||||
"debug": "^4.3.1",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"grpc": "^1.24.4",
|
||||
"http-status-codes": "^1.4.0",
|
||||
"iterall": "^1.3.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"multer": "^1.4.2",
|
||||
"prom-client": "^12.0.0",
|
||||
"query-string": "^6.13.3",
|
||||
"systeminformation": "^4.31.1",
|
||||
"ts-node-dev": "^1.0.0-pre.44",
|
||||
"typescript": "^3.8.3",
|
||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||
"uuidv4": "^6.0.7"
|
||||
},
|
||||
@ -71,6 +65,8 @@
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"eslint": "^6.8.0",
|
||||
"jasmine": "^3.5.0"
|
||||
"jasmine": "^3.5.0",
|
||||
"ts-node-dev": "^1.0.0-pre.44",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
import {
|
||||
BatchMessage,
|
||||
PusherToBackMessage,
|
||||
ServerToAdminClientMessage,
|
||||
ServerToClientMessage,
|
||||
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
||||
UserJoinedRoomMessage, UserLeftRoomMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {AdminSocket} from "../RoomManager";
|
||||
|
||||
|
@ -89,7 +89,10 @@ export class GameRoom {
|
||||
public getUserByUuid(uuid: string): User|undefined {
|
||||
return this.usersByUuid.get(uuid);
|
||||
}
|
||||
|
||||
public getUserById(id: number): User|undefined {
|
||||
return this.users.get(id);
|
||||
}
|
||||
|
||||
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
|
||||
const positionMessage = joinRoomMessage.getPositionmessage();
|
||||
if (positionMessage === undefined) {
|
||||
@ -105,6 +108,7 @@ export class GameRoom {
|
||||
this.positionNotifier,
|
||||
socket,
|
||||
joinRoomMessage.getTagList(),
|
||||
joinRoomMessage.getVisitcardurl(),
|
||||
joinRoomMessage.getName(),
|
||||
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
|
||||
joinRoomMessage.getCompanion()
|
||||
|
@ -22,6 +22,7 @@ export class User implements Movable {
|
||||
private positionNotifier: PositionNotifier,
|
||||
public readonly socket: UserSocket,
|
||||
public readonly tags: string[],
|
||||
public readonly visitCardUrl: string|null,
|
||||
public readonly name: string,
|
||||
public readonly characterLayers: CharacterLayer[],
|
||||
public readonly companion?: CompanionMessage
|
||||
|
@ -299,6 +299,9 @@ export class SocketManager {
|
||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
|
||||
if (thing.visitCardUrl) {
|
||||
userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
|
||||
}
|
||||
userJoinedZoneMessage.setCompanion(thing.companion);
|
||||
|
||||
const subMessage = new SubToPusherMessage();
|
||||
@ -604,6 +607,9 @@ export class SocketManager {
|
||||
userJoinedMessage.setName(thing.name);
|
||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||
if (thing.visitCardUrl) {
|
||||
userJoinedMessage.setVisitcardurl(thing.visitCardUrl);
|
||||
}
|
||||
userJoinedMessage.setCompanion(thing.companion);
|
||||
|
||||
const subMessage = new SubToPusherMessage();
|
||||
|
@ -1,10 +1,6 @@
|
||||
import "jasmine";
|
||||
import {GameRoom, ConnectCallback, DisconnectCallback } from "_Model/GameRoom";
|
||||
import {Point} from "../src/Model/Websocket/MessageUserPosition";
|
||||
import { Group } from "../src/Model/Group";
|
||||
import {PositionNotifier} from "../src/Model/PositionNotifier";
|
||||
import {User, UserSocket} from "../src/Model/User";
|
||||
import {PointInterface} from "../src/Model/Websocket/PointInterface";
|
||||
import {Zone} from "_Model/Zone";
|
||||
import {Movable} from "_Model/Movable";
|
||||
import {PositionInterface} from "_Model/PositionInterface";
|
||||
@ -30,14 +26,14 @@ describe("PositionNotifier", () => {
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
|
||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||
x: -9999,
|
||||
y: -9999,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
|
||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
||||
@ -105,14 +101,14 @@ describe("PositionNotifier", () => {
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
|
||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||
x: 0,
|
||||
y: 0,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], 'foo', []);
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
|
||||
const listener = {} as ZoneSocket;
|
||||
positionNotifier.addZoneListener(listener, 0, 0);
|
||||
|
209
back/yarn.lock
@ -257,11 +257,6 @@ anymatch@~3.1.1:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
append-field@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
|
||||
integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=
|
||||
|
||||
aproba@^1.0.3:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
@ -370,22 +365,6 @@ bintrees@1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524"
|
||||
integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=
|
||||
|
||||
body-parser@^1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
content-type "~1.0.4"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
http-errors "1.7.2"
|
||||
iconv-lite "0.4.24"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.7.0"
|
||||
raw-body "2.4.0"
|
||||
type-is "~1.6.17"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
@ -427,14 +406,6 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
busboy@^0.2.11:
|
||||
version "0.2.14"
|
||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
|
||||
integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
|
||||
dependencies:
|
||||
dicer "0.2.5"
|
||||
readable-stream "1.1.x"
|
||||
|
||||
busboy@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b"
|
||||
@ -449,11 +420,6 @@ bytebuffer@~5:
|
||||
dependencies:
|
||||
long "~3"
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
||||
|
||||
cache-base@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
|
||||
@ -622,26 +588,11 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
concat-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
|
||||
|
||||
content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
|
||||
copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
@ -678,7 +629,7 @@ dateformat@~1.0.4-1.2.3:
|
||||
get-stdin "^4.0.1"
|
||||
meow "^3.3.0"
|
||||
|
||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
||||
debug@^2.2.0, debug@^2.3.3:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
@ -753,24 +704,11 @@ delegates@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
detect-libc@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
||||
|
||||
dicer@0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
|
||||
integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
|
||||
dependencies:
|
||||
readable-stream "1.1.x"
|
||||
streamsearch "0.1.2"
|
||||
|
||||
dicer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872"
|
||||
@ -804,11 +742,6 @@ ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
|
||||
@ -1255,28 +1188,12 @@ hosted-git-info@^2.1.4:
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
http-errors@1.7.2:
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.1"
|
||||
statuses ">= 1.5.0 < 2"
|
||||
toidentifier "1.0.0"
|
||||
|
||||
http-status-codes@*:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.1.4.tgz#453d99b4bd9424254c4f6a9a3a03715923052798"
|
||||
integrity sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg==
|
||||
|
||||
http-status-codes@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477"
|
||||
integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
||||
iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||
@ -1323,16 +1240,11 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
inherits@2, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@~1.3.0:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
|
||||
@ -1507,11 +1419,6 @@ is-windows@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
isarray@1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
@ -1534,11 +1441,6 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
iterall@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
|
||||
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||
|
||||
jasmine-core@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20"
|
||||
@ -1743,11 +1645,6 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
object-visit "^1.0.0"
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
meow@^3.3.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
|
||||
@ -1788,18 +1685,6 @@ micromatch@^3.1.10:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.2"
|
||||
|
||||
mime-db@1.44.0:
|
||||
version "1.44.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
|
||||
integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
|
||||
|
||||
mime-types@~2.1.24:
|
||||
version "2.1.27"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
|
||||
integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
|
||||
dependencies:
|
||||
mime-db "1.44.0"
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@ -1862,20 +1747,6 @@ ms@2.1.2, ms@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
multer@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
|
||||
integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
|
||||
dependencies:
|
||||
append-field "^1.0.0"
|
||||
busboy "^0.2.11"
|
||||
concat-stream "^1.5.2"
|
||||
mkdirp "^0.5.1"
|
||||
object-assign "^4.1.1"
|
||||
on-finished "^2.3.0"
|
||||
type-is "^1.6.4"
|
||||
xtend "^4.0.0"
|
||||
|
||||
mute-stream@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
@ -1997,7 +1868,7 @@ number-is-nan@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
object-assign@^4.0.1, object-assign@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
@ -2025,13 +1896,6 @@ object.pick@^1.3.0:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
on-finished@^2.3.0, on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
@ -2207,11 +2071,6 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
|
||||
|
||||
query-string@^6.13.3:
|
||||
version "6.13.4"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.4.tgz#b35a9a3bd4955bce55f94feb0e819b3d0be6f66f"
|
||||
@ -2221,16 +2080,6 @@ query-string@^6.13.3:
|
||||
split-on-first "^1.0.0"
|
||||
strict-uri-encode "^2.0.0"
|
||||
|
||||
raw-body@2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
||||
dependencies:
|
||||
bytes "3.1.0"
|
||||
http-errors "1.7.2"
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
rc@^1.2.7:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
@ -2258,17 +2107,7 @@ read-pkg@^1.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@1.1.x:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^2.0.6, readable-stream@^2.2.2:
|
||||
readable-stream@^2.0.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
@ -2444,11 +2283,6 @@ set-value@^2.0.0, set-value@^2.0.1:
|
||||
is-plain-object "^2.0.3"
|
||||
split-string "^3.0.1"
|
||||
|
||||
setprototypeof@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
@ -2590,11 +2424,6 @@ static-extend@^0.1.1:
|
||||
define-property "^0.2.5"
|
||||
object-copy "^0.1.0"
|
||||
|
||||
"statuses@>= 1.5.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
@ -2640,11 +2469,6 @@ string-width@^4.1.0:
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
@ -2807,11 +2631,6 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
||||
regex-not "^1.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
tree-kill@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
@ -2889,19 +2708,6 @@ type-fest@^0.8.1:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
|
||||
type-is@^1.6.4, type-is@~1.6.17:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||
dependencies:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^3.8.3:
|
||||
version "3.9.7"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa"
|
||||
@ -2921,11 +2727,6 @@ union-value@^1.0.0:
|
||||
is-extendable "^0.1.1"
|
||||
set-value "^2.0.1"
|
||||
|
||||
unpipe@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||
|
||||
unset-value@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
|
||||
|
12
benchmark/package-lock.json
generated
@ -209,9 +209,9 @@
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
@ -688,9 +688,9 @@
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
|
||||
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
|
@ -24,7 +24,7 @@
|
||||
"@types/ws": "^7.2.6",
|
||||
"ts-node-dev": "^1.0.0-pre.62",
|
||||
"typescript": "^4.0.2",
|
||||
"ws": "^7.3.1"
|
||||
"ws": "^7.4.6"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
@ -148,8 +148,8 @@ get-stdin@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
|
||||
glob-parent@~5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
@ -515,9 +515,9 @@ wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
ws@^7.3.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||
ws@^7.4.6:
|
||||
version "7.4.6"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.2"
|
||||
|
@ -11,8 +11,7 @@
|
||||
"back1": {
|
||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||
"host": {
|
||||
"url": "api1."+url,
|
||||
"https": "enable",
|
||||
"url": "api1-"+url,
|
||||
"containerPort": 8080
|
||||
},
|
||||
"ports": [8080, 50051],
|
||||
@ -30,8 +29,7 @@
|
||||
"back2": {
|
||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||
"host": {
|
||||
"url": "api2."+url,
|
||||
"https": "enable",
|
||||
"url": "api2-"+url,
|
||||
"containerPort": 8080
|
||||
},
|
||||
"ports": [8080, 50051],
|
||||
@ -50,8 +48,7 @@
|
||||
"replicas": 2,
|
||||
"image": "thecodingmachine/workadventure-pusher:"+tag,
|
||||
"host": {
|
||||
"url": "pusher."+url,
|
||||
"https": "enable"
|
||||
"url": "pusher-"+url,
|
||||
},
|
||||
"ports": [8080],
|
||||
"env": {
|
||||
@ -68,27 +65,25 @@
|
||||
"front": {
|
||||
"image": "thecodingmachine/workadventure-front:"+tag,
|
||||
"host": {
|
||||
"url": "play."+url,
|
||||
"https": "enable"
|
||||
"url": "play-"+url,
|
||||
},
|
||||
"ports": [80],
|
||||
"env": {
|
||||
"PUSHER_URL": "//pusher."+url,
|
||||
"UPLOADER_URL": "//uploader."+url,
|
||||
"PUSHER_URL": "//pusher-"+url,
|
||||
"UPLOADER_URL": "//uploader-"+url,
|
||||
"ADMIN_URL": "//"+url,
|
||||
"JITSI_URL": env.JITSI_URL,
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
|
||||
"START_ROOM_URL": "/_/global/maps-"+url+"/Floor0/floor0.json"
|
||||
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||
}
|
||||
},
|
||||
"uploader": {
|
||||
"image": "thecodingmachine/workadventure-uploader:"+tag,
|
||||
"host": {
|
||||
"url": "uploader."+url,
|
||||
"https": "enable",
|
||||
"url": "uploader-"+url,
|
||||
"containerPort": 8080
|
||||
},
|
||||
"ports": [8080],
|
||||
@ -98,16 +93,12 @@
|
||||
"maps": {
|
||||
"image": "thecodingmachine/workadventure-maps:"+tag,
|
||||
"host": {
|
||||
"url": "maps."+url,
|
||||
"https": "enable"
|
||||
"url": "maps-"+url
|
||||
},
|
||||
"ports": [80]
|
||||
},
|
||||
},
|
||||
"config": {
|
||||
"https": {
|
||||
"mail": "d.negrier@thecodingmachine.com"
|
||||
},
|
||||
k8sextension(k8sConf)::
|
||||
k8sConf + {
|
||||
back1+: {
|
||||
@ -122,6 +113,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["api1-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
back2+: {
|
||||
@ -136,6 +135,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["api2-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
pusher+: {
|
||||
@ -150,8 +157,46 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["pusher-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
front+: {
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["play-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
uploader+: {
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["uploader-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
maps+: {
|
||||
ingress+: {
|
||||
spec+: {
|
||||
tls+: [{
|
||||
hosts: ["maps-"+url],
|
||||
secretName: "certificate-tls"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
33
docs/maps/animations.md
Normal file
@ -0,0 +1,33 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# Animating WorkAdventure maps
|
||||
|
||||
A tile can run an animation in loops, for example to render water or blinking lights. Each animation frame is a single
|
||||
32x32 tile. To create an animation, edit the tileset in Tiled and click on the tile to animate (or pick a free tile to
|
||||
not overwrite existing ones) and click on the animation editor:
|
||||
|
||||
|
||||
<div class="px-5 card rounded d-inline-block">
|
||||
<img class="document-img" src="https://workadventu.re/img/docs/anims/camera.png" alt="" />
|
||||
</div>
|
||||
|
||||
You can now add all tiles that should be part of the animation via drag and drop to the "playlist" and adjust the frame duration:
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img class="figure-img img-fluid rounded" src="https://workadventu.re/img/docs/anims/animation_editor.png" alt="" />
|
||||
<figcaption class="figure-caption">The tile animation editor</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
You can preview animations directly in Tiled, using the "Show tile animations" option:
|
||||
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img class="figure-img img-fluid rounded" src="https://workadventu.re/img/docs/anims/settings_show_animations.png" alt="" />
|
||||
<figcaption class="figure-caption">The Show Tile Animations option</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
{.alert.alert-info}
|
||||
**Tip:** The engine does tile-updates every 100ms, animations with a shorter frame duration will most likely not look that good or may even do not work.
|
37
docs/maps/api-chat.md
Normal file
@ -0,0 +1,37 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Chat functions reference
|
||||
|
||||
### Sending a message in the chat
|
||||
|
||||
```
|
||||
WA.chat.sendChatMessage(message: string, author: string): void
|
||||
```
|
||||
|
||||
Sends a message in the chat. The message is only visible in the browser of the current user.
|
||||
|
||||
* **message**: the message to be displayed in the chat
|
||||
* **author**: the name displayed for the author of the message. It does not have to be a real user.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.chat.sendChatMessage('Hello world', 'Mr Robot');
|
||||
```
|
||||
|
||||
### Listening to messages from the chat
|
||||
|
||||
```javascript
|
||||
WA.chat.onChatMessage(callback: (message: string) => void): void
|
||||
```
|
||||
|
||||
Listens to messages typed by the current user and calls the callback. Messages from other users in the chat cannot be listened to.
|
||||
|
||||
* **callback**: the function that will be called when a message is received. It contains the message typed by the user.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.chat.onChatMessage((message => {
|
||||
console.log('The user typed a message', message);
|
||||
}));
|
||||
```
|
29
docs/maps/api-controls.md
Normal file
@ -0,0 +1,29 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Controls functions Reference
|
||||
|
||||
### Disabling / restoring controls
|
||||
|
||||
```
|
||||
WA.controls.disablePlayerControls(): void
|
||||
WA.controls.restorePlayerControls(): void
|
||||
```
|
||||
|
||||
These 2 methods can be used to completely disable player controls and to enable them again.
|
||||
|
||||
When controls are disabled, the user cannot move anymore using keyboard input. This can be useful in a "First Time User Experience" part, to display an important message to a user before letting him/her move again.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.room.onEnterZone('myZone', () => {
|
||||
WA.controls.disablePlayerControls();
|
||||
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
|
||||
label: "Got it!",
|
||||
className: "primary",
|
||||
callback: (popup) => {
|
||||
WA.controls.restorePlayerControls();
|
||||
popup.close();
|
||||
}
|
||||
}]);
|
||||
});
|
||||
```
|
20
docs/maps/api-deprecated.md
Normal file
@ -0,0 +1,20 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Reference - Deprecated functions
|
||||
|
||||
The list of functions below is **deprecated**. You should not use those but. use the replacement functions.
|
||||
|
||||
- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`.
|
||||
- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`.
|
||||
- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`.
|
||||
- Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`.
|
||||
- Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`.
|
||||
- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`.
|
||||
- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`.
|
||||
- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`.
|
||||
- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`.
|
||||
- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`.
|
||||
- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`.
|
||||
- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`.
|
||||
- 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.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
|
68
docs/maps/api-nav.md
Normal file
@ -0,0 +1,68 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Navigation functions reference
|
||||
|
||||
### Opening a web page in a new tab
|
||||
|
||||
```
|
||||
WA.nav.openTab(url: string): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in your browser, in a new tab.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.nav.openTab('https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Opening a web page in the current tab
|
||||
|
||||
```
|
||||
WA.nav.goToPage(url: string): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdventure will be completely unloaded.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.nav.goToPage('https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Going to a different map from the script
|
||||
|
||||
```
|
||||
|
||||
WA.nav.goToRoom(url: string): void
|
||||
```
|
||||
|
||||
Load the map at url without unloading workadventure
|
||||
|
||||
relative urls: "../subFolder/map.json[#start-layer-name]"
|
||||
global urls: "/_/global/domain/path/map.json[#start-layer-name]"
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls
|
||||
WA.nav.goToRoom('../otherMap/map.json');
|
||||
WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
|
||||
```
|
||||
|
||||
### Opening/closing a web page in an iFrame
|
||||
|
||||
```
|
||||
WA.nav.openCoWebSite(url: string): void
|
||||
WA.nav.closeCoWebSite(): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.nav.openCoWebSite('https://www.wikipedia.org/');
|
||||
// ...
|
||||
WA.nav.closeCoWebSite();
|
||||
```
|
||||
|
21
docs/maps/api-player.md
Normal file
@ -0,0 +1,21 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Player functions Reference
|
||||
|
||||
### Listen to player movement
|
||||
```
|
||||
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
||||
```
|
||||
Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction.
|
||||
|
||||
The event has the following attributes :
|
||||
* **moving (boolean):** **true** when the current player is moving, **false** otherwise.
|
||||
* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
|
||||
* **x (number):** coordinate X of the current player.
|
||||
* **y (number):** coordinate Y of the current player.
|
||||
|
||||
**callback:** the function that will be called when the current player is moving. It contains the event.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.player.onPlayerMove(console.log);
|
||||
```
|
@ -1,352 +1,12 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Reference
|
||||
|
||||
### Sending a message in the chat
|
||||
|
||||
```
|
||||
sendChatMessage(message: string, author: string): void
|
||||
```
|
||||
|
||||
Sends a message in the chat. The message is only visible in the browser of the current user.
|
||||
|
||||
* **message**: the message to be displayed in the chat
|
||||
* **author**: the name displayed for the author of the message. It does not have to be a real user.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.sendChatMessage('Hello world', 'Mr Robot');
|
||||
```
|
||||
|
||||
### Listening to messages from the chat
|
||||
|
||||
```javascript
|
||||
onChatMessage(callback: (message: string) => void): void
|
||||
```
|
||||
|
||||
Listens to messages typed by the current user and calls the callback. Messages from other users in the chat cannot be listened to.
|
||||
|
||||
* **callback**: the function that will be called when a message is received. It contains the message typed by the user.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.onChatMessage((message => {
|
||||
console.log('The user typed a message', message);
|
||||
}));
|
||||
```
|
||||
|
||||
### Detecting when the user enters/leaves a zone
|
||||
|
||||
```
|
||||
onEnterZone(name: string, callback: () => void): void
|
||||
onLeaveZone(name: string, callback: () => void): void
|
||||
```
|
||||
|
||||
Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
* **name**: the name of the zone, as defined in the `zone` property.
|
||||
* **callback**: the function that will be called when a user enters or leaves the zone.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.onEnterZone('myZone', () => {
|
||||
WA.sendChatMessage("Hello!", 'Mr Robot');
|
||||
})
|
||||
|
||||
WA.onLeaveZone('myZone', () => {
|
||||
WA.sendChatMessage("Goodbye!", 'Mr Robot');
|
||||
})
|
||||
```
|
||||
|
||||
### Opening a popup
|
||||
|
||||
In order to open a popup window, you must first define the position of the popup on your map.
|
||||
|
||||
You can position this popup by using a "rectangle" object in Tiled that you will place on an "object" layer.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup
|
||||
```
|
||||
|
||||
* **targetObject**: the name of the rectangle object defined in Tiled.
|
||||
* **message**: the message to display in the popup.
|
||||
* **buttons**: an array of action buttons defined underneath the popup.
|
||||
|
||||
Action buttons are `ButtonDescriptor` objects containing these properties.
|
||||
|
||||
* **label (_string_)**: The label of the button.
|
||||
* **className (_string_)**: The visual type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled".
|
||||
* **callback (_(popup: Popup)=>void_)**: Callback called when the button is pressed.
|
||||
|
||||
Please note that `openPopup` returns an object of the `Popup` class. Also, the callback called when a button is clicked is passed a `Popup` object.
|
||||
|
||||
The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called.
|
||||
|
||||
```javascript
|
||||
class Popup {
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
close() {};
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
let helloWorldPopup;
|
||||
|
||||
// Open the popup when we enter a given zone
|
||||
helloWorldPopup = WA.onEnterZone('myZone', () => {
|
||||
WA.openPopup("popupRectangle", 'Hello world!', [{
|
||||
label: "Close",
|
||||
className: "primary",
|
||||
callback: (popup) => {
|
||||
// Close the popup when the "Close" button is pressed.
|
||||
popup.close();
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
// Close the popup when we leave the zone.
|
||||
WA.onLeaveZone('myZone', () => {
|
||||
helloWorldPopup.close();
|
||||
});
|
||||
```
|
||||
|
||||
### Disabling / restoring controls
|
||||
|
||||
```
|
||||
disablePlayerControls(): void
|
||||
restorePlayerControls(): void
|
||||
```
|
||||
|
||||
These 2 methods can be used to completely disable player controls and to enable them again.
|
||||
|
||||
When controls are disabled, the user cannot move anymore using keyboard input. This can be useful in a "First Time User Experience" part, to display an important message to a user before letting him/her move again.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.onEnterZone('myZone', () => {
|
||||
WA.disablePlayerControls();
|
||||
WA.openPopup("popupRectangle", 'This is an imporant message!', [{
|
||||
label: "Got it!",
|
||||
className: "primary",
|
||||
callback: (popup) => {
|
||||
WA.restorePlayerControls();
|
||||
popup.close();
|
||||
}
|
||||
}]);
|
||||
});
|
||||
```
|
||||
|
||||
### Opening a web page in a new tab
|
||||
|
||||
```
|
||||
openTab(url: string): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in your browser, in a new tab.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.openTab('https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Opening a web page in the current tab
|
||||
|
||||
```
|
||||
goToPage(url: string): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdventure will be completely unloaded.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.goToPage('https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Opening/closing a web page in an iFrame
|
||||
|
||||
```
|
||||
openCoWebSite(url: string): void
|
||||
closeCoWebSite(): void
|
||||
```
|
||||
|
||||
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.openCoWebSite('https://www.wikipedia.org/');
|
||||
// ...
|
||||
WA.closeCoWebSite();
|
||||
```
|
||||
|
||||
### Load a sound from an url
|
||||
|
||||
```
|
||||
loadSound(url: string): Sound
|
||||
```
|
||||
|
||||
Load a sound from an url
|
||||
|
||||
Please note that `loadSound` returns an object of the `Sound` class
|
||||
|
||||
The `Sound` class that represents a loaded sound contains two methods: `play(soundConfig : SoundConfig|undefined)` and `stop()`
|
||||
|
||||
The parameter soundConfig is optional, if you call play without a Sound config the sound will be played with the basic configuration.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
var mySound = WA.loadSound("Sound.ogg");
|
||||
var config = {
|
||||
volume : 0.5,
|
||||
loop : false,
|
||||
rate : 1,
|
||||
detune : 1,
|
||||
delay : 0,
|
||||
seek : 0,
|
||||
mute : false
|
||||
}
|
||||
mySound.play(config);
|
||||
// ...
|
||||
mySound.stop();
|
||||
```
|
||||
|
||||
### Show / Hide a layer
|
||||
```
|
||||
WA.showLayer(layerName : string): void
|
||||
WA.hideLayer(layerName : string) : void
|
||||
```
|
||||
These 2 methods can be used to show and hide a layer.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.showLayer('bottom');
|
||||
//...
|
||||
WA.hideLayer('bottom');
|
||||
```
|
||||
|
||||
### Set/Create properties in a layer
|
||||
|
||||
```
|
||||
WA.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void;
|
||||
```
|
||||
|
||||
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Listen to player movement
|
||||
|
||||
```
|
||||
onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
||||
```
|
||||
Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction.
|
||||
|
||||
The event has the following attributes :
|
||||
* **moving (boolean):** **true** when the current player is moving, **false** otherwise.
|
||||
* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
|
||||
* **x (number):** coordinate X of the current player.
|
||||
* **y (number):** coordinate Y of the current player.
|
||||
|
||||
**callback:** the function that will be called when the current player is moving. It contains the event.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.onPlayerMove(console.log);
|
||||
```
|
||||
|
||||
### Getting informations on the current user
|
||||
```
|
||||
getCurrentUser(): Promise<User>
|
||||
```
|
||||
Return a promise that resolves to a `User` object with the following attributes :
|
||||
* **id (string) :** ID of the current user
|
||||
* **nickName (string) :** name displayed above the current user
|
||||
* **tags (string[]) :** list of all the tags of the current user
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.getCurrentUser().then((user) => {
|
||||
if (user.nickName === 'ABC') {
|
||||
console.log(user.tags);
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Getting informations on the current room
|
||||
```
|
||||
getCurrentRoom(): Promise<Room>
|
||||
```
|
||||
Return a promise that resolves to a `Room` object with the following attributes :
|
||||
* **id (string) :** ID of the current room
|
||||
* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called.
|
||||
* **mapUrl (string) :** Url of the JSON map file
|
||||
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.getCurrentRoom((room) => {
|
||||
if (room.id === '42') {
|
||||
console.log(room.map);
|
||||
window.open(room.mapUrl, '_blank');
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Add a custom menu
|
||||
```
|
||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void)
|
||||
```
|
||||
Add a custom menu item containing the text `commandDescriptor`. A click on the menu will trigger the `callback`.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.registerMenuCommand('About', () => {
|
||||
console.log("The About menu was clicked");
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### Working with group layers
|
||||
If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together.
|
||||
|
||||
Example :
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The name of the layers of this map are :
|
||||
* `entries/start`
|
||||
* `bottom/ground/under`
|
||||
* `bottom/build/carpet`
|
||||
* `wall`
|
||||
- [Navigation functions](api-nav.md)
|
||||
- [Chat functions](api-chat.md)
|
||||
- [Room functions](api-room.md)
|
||||
- [Player functions](api-player.md)
|
||||
- [UI functions](api-ui.md)
|
||||
- [Sound functions](api-sound.md)
|
||||
- [Controls functions](api-controls.md)
|
||||
|
||||
- [List of deprecated functions](api-deprecated.md)
|
114
docs/maps/api-room.md
Normal file
@ -0,0 +1,114 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Room functions Reference
|
||||
|
||||
### Working with group layers
|
||||
If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together.
|
||||
|
||||
Example :
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The name of the layers of this map are :
|
||||
* `entries/start`
|
||||
* `bottom/ground/under`
|
||||
* `bottom/build/carpet`
|
||||
* `wall`
|
||||
|
||||
### Detecting when the user enters/leaves a zone
|
||||
|
||||
```
|
||||
WA.room.onEnterZone(name: string, callback: () => void): void
|
||||
WA.room.onLeaveZone(name: string, callback: () => void): void
|
||||
```
|
||||
|
||||
Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
* **name**: the name of the zone, as defined in the `zone` property.
|
||||
* **callback**: the function that will be called when a user enters or leaves the zone.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
WA.room.onEnterZone('myZone', () => {
|
||||
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
|
||||
})
|
||||
|
||||
WA.room.onLeaveZone('myZone', () => {
|
||||
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
|
||||
})
|
||||
```
|
||||
|
||||
### Show / Hide a layer
|
||||
```
|
||||
WA.room.showLayer(layerName : string): void
|
||||
WA.room.hideLayer(layerName : string) : void
|
||||
```
|
||||
These 2 methods can be used to show and hide a layer.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.room.showLayer('bottom');
|
||||
//...
|
||||
WA.room.hideLayer('bottom');
|
||||
```
|
||||
|
||||
### Set/Create properties in a layer
|
||||
|
||||
```
|
||||
WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void;
|
||||
```
|
||||
|
||||
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/');
|
||||
```
|
||||
|
||||
### Getting information on the current room
|
||||
```
|
||||
WA.room.getCurrentRoom(): Promise<Room>
|
||||
```
|
||||
Return a promise that resolves to a `Room` object with the following attributes :
|
||||
* **id (string) :** ID of the current room
|
||||
* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called.
|
||||
* **mapUrl (string) :** Url of the JSON map file
|
||||
* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.room.getCurrentRoom((room) => {
|
||||
if (room.id === '42') {
|
||||
console.log(room.map);
|
||||
window.open(room.mapUrl, '_blank');
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Getting information on the current user
|
||||
```
|
||||
WA.player.getCurrentUser(): Promise<User>
|
||||
```
|
||||
Return a promise that resolves to a `User` object with the following attributes :
|
||||
* **id (string) :** ID of the current user
|
||||
* **nickName (string) :** name displayed above the current user
|
||||
* **tags (string[]) :** list of all the tags of the current user
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.room.getCurrentUser().then((user) => {
|
||||
if (user.nickName === 'ABC') {
|
||||
console.log(user.tags);
|
||||
}
|
||||
})
|
||||
```
|
34
docs/maps/api-sound.md
Normal file
@ -0,0 +1,34 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Sound functions Reference
|
||||
|
||||
### Load a sound from an url
|
||||
|
||||
```
|
||||
WA.sound.loadSound(url: string): Sound
|
||||
```
|
||||
|
||||
Load a sound from an url
|
||||
|
||||
Please note that `loadSound` returns an object of the `Sound` class
|
||||
|
||||
The `Sound` class that represents a loaded sound contains two methods: `play(soundConfig : SoundConfig|undefined)` and `stop()`
|
||||
|
||||
The parameter soundConfig is optional, if you call play without a Sound config the sound will be played with the basic configuration.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
var mySound = WA.sound.loadSound("Sound.ogg");
|
||||
var config = {
|
||||
volume : 0.5,
|
||||
loop : false,
|
||||
rate : 1,
|
||||
detune : 1,
|
||||
delay : 0,
|
||||
seek : 0,
|
||||
mute : false
|
||||
}
|
||||
mySound.play(config);
|
||||
// ...
|
||||
mySound.stop();
|
||||
```
|
89
docs/maps/api-ui.md
Normal file
@ -0,0 +1,89 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API UI functions Reference
|
||||
|
||||
### Opening a popup
|
||||
|
||||
In order to open a popup window, you must first define the position of the popup on your map.
|
||||
|
||||
You can position this popup by using a "rectangle" object in Tiled that you will place on an "object" layer.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
WA.ui.openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup
|
||||
```
|
||||
|
||||
* **targetObject**: the name of the rectangle object defined in Tiled.
|
||||
* **message**: the message to display in the popup.
|
||||
* **buttons**: an array of action buttons defined underneath the popup.
|
||||
|
||||
Action buttons are `ButtonDescriptor` objects containing these properties.
|
||||
|
||||
* **label (_string_)**: The label of the button.
|
||||
* **className (_string_)**: The visual type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled".
|
||||
* **callback (_(popup: Popup)=>void_)**: Callback called when the button is pressed.
|
||||
|
||||
Please note that `openPopup` returns an object of the `Popup` class. Also, the callback called when a button is clicked is passed a `Popup` object.
|
||||
|
||||
The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called.
|
||||
|
||||
```javascript
|
||||
class Popup {
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
close() {};
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
let helloWorldPopup;
|
||||
|
||||
// Open the popup when we enter a given zone
|
||||
helloWorldPopup = WA.room.onEnterZone('myZone', () => {
|
||||
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
|
||||
label: "Close",
|
||||
className: "primary",
|
||||
callback: (popup) => {
|
||||
// Close the popup when the "Close" button is pressed.
|
||||
popup.close();
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
// Close the popup when we leave the zone.
|
||||
WA.room.onLeaveZone('myZone', () => {
|
||||
helloWorldPopup.close();
|
||||
});
|
||||
```
|
||||
|
||||
### 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`.
|
||||
Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
|
||||
WA.ui.registerMenuCommand("test", () => {
|
||||
WA.chat.sendChatMessage("test clicked", "menu cmd")
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
<div class="col">
|
||||
<img src="./assets/menu-command.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
BIN
docs/maps/assets/menu-command.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
@ -101,7 +101,7 @@ You can now start by testing this with a simple message sent to the chat.
|
||||
```html
|
||||
...
|
||||
<script>
|
||||
WA.sendChatMessage('Hello world', 'Mr Robot');
|
||||
WA.chat.sendChatMessage('Hello world', 'Mr Robot');
|
||||
</script>
|
||||
...
|
||||
```
|
||||
|
90
docs/maps/wa-maps.md
Normal file
@ -0,0 +1,90 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# About WorkAdventure maps
|
||||
|
||||
A WorkAdventure map is a map in "JSON" format generated by [Tiled](https://www.mapeditor.org/).
|
||||
|
||||
## Tiles
|
||||
|
||||
A map is made of "tiles" (we can also call them "sprites"). In WorkAdventure, the tiles are small images of 32x32 pixels.
|
||||
|
||||
Tiles may have transparent parts. Many tiles can be stored in a single PNG file. We call this file a "tileset".
|
||||
|
||||
There are many tilesets available on the internet. Some examples of websites offering awesome tiles:
|
||||
|
||||
* [itch.io](https://itch.io/)
|
||||
* [opengameart.org](https://opengameart.org/)
|
||||
* [deviantart.com](https://www.deviantart.com/)
|
||||
|
||||
Keep in mind the size of tiles and do not forget to check the license of the tileset you are using!
|
||||
|
||||
|
||||
## How to design "pixel" tiles
|
||||
|
||||
You can design your own tiles as well as change existing tiles, this is usually referred to as "pixeling". You can start drawing your own tiles with [Piskel](https://www.piskelapp.com/). It is easy to use and well targeted at "pixeling". If you are getting serious about pixeling, the awesome folks at the Chaos Computer Club recommend the use of the editor [Krita](https://krita.org/). There are plenty of other editors as well.
|
||||
|
||||
If you are using Krita:
|
||||
|
||||
* Please double check that your tiles are 32x32 pixels in size. You can enable a grid under view -> show grid and under settings -> dockers -> grid you can select the grid size.
|
||||
* Use transparency if you have to model transitions between different materials. This is more flexible and saves you time by not modeling every transition.
|
||||
* You can follow the Pixel-Art Workshop by blinry: [media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop](https://media.ccc.de/v/34C3-jugend-hackt-1016-pixel_art_workshop)
|
||||
|
||||
## WorkAdventure Map Rules
|
||||
|
||||
In order to design a map that will be readable by WorkAdventure, you will have to respect some constraints.
|
||||
|
||||
In particular, you will need to:
|
||||
|
||||
* set a start position for the players
|
||||
* configure the "floor layer" (so that WorkAdventure can correctly display characters above the floor, but under the ceiling)
|
||||
* eventually, you can place exits that link to other maps
|
||||
|
||||
A few things to notice:
|
||||
|
||||
* your map can have as many layers as you want
|
||||
* your map MUST contain a layer named "floorLayer" of type "objectgroup" that represents the layer on which characters will be drawn. Every layer above the "floorLayer" will be displayed on top of the characters.
|
||||
* the tilesets in your map MUST be embedded. You cannot refer to an external typeset in a TSX file. Click the "embed tileset" button in the tileset tab to embed tileset data.
|
||||
* your map MUST be exported in JSON format. You need to use a recent version of Tiled to get JSON format export (1.3+)
|
||||
* WorkAdventure doesn't support object layers and will ignore them
|
||||
* If you are starting from a blank map, your map MUST be orthogonal and tiles size should be 32x32.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="https://workadventu.re/img/docs/tiled_screenshot_1.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
|
||||
<figcaption class="figure-caption">"floorLayer" is compulsory</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
## Building walls and "collidable" areas
|
||||
|
||||
By default, the characters can traverse any tiles. If you want to prevent your characeter from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
|
||||
|
||||
To make a tile "collidable", you should:
|
||||
|
||||
1. select the relevant tileset and switch to "edit" mode:
|
||||
{.document-img}
|
||||
![](https://workadventu.re/img/docs/collides-1.png)
|
||||
2. right click on a tile of the tileset to select it:
|
||||
{.document-img}
|
||||
![](https://workadventu.re/img/docs/collides-2.png)
|
||||
3. on the left pane in the custom properties section, right click and select "Add properties":
|
||||
{.document-img}
|
||||
![](https://workadventu.re/img/docs/collides-3.png)
|
||||
|
||||
Please add a `collides` property. The type of the property must be **bool**.
|
||||
|
||||
4. finally, check the checkbox for the `collides` property:
|
||||
{.document-img}
|
||||
![](https://workadventu.re/img/docs/collides-4.png)
|
||||
|
||||
Repeat for every tile that should be "collidable".
|
||||
|
||||
## Adding behaviour with properties
|
||||
|
||||
In the next sections, you will see how you can add behaviour on your map by adding "properties".
|
||||
You can add properties for a variety of features: putting exits, opening websites, meeting rooms, silent zones, etc...
|
||||
|
||||
You can add properties either on individual tiles of a tileset OR on a complete layer.
|
||||
|
||||
If you put a property on a layer, it will be triggered if your Woka walks on any tile of the layer.
|
||||
|
||||
The exception is the "collides" property that can only be set on tiles, but not on a complete layer.
|
40
front/dist/index.tmpl.html
vendored
@ -29,7 +29,6 @@
|
||||
|
||||
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
@ -47,32 +46,6 @@
|
||||
</aside>
|
||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
||||
</div>
|
||||
<div id="activeCam" class="activeCam">
|
||||
<div id="div-myCamVideo" class="video-container">
|
||||
<video id="myCamVideo" autoplay muted></video>
|
||||
<div id="mySoundMeter" class="sound-progress">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div id="btn-monitor" class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
</div>
|
||||
<div id="btn-video" class="btn-video">
|
||||
<img id="cinema" src="resources/logos/cinema.svg">
|
||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||
</div>
|
||||
<div id="btn-micro" class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cowebsite" class="cowebsite hidden">
|
||||
@ -114,23 +87,10 @@
|
||||
<div id="audioplayer" style="visibility: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-playing">
|
||||
<img src="/resources/logos/megaphone.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||
</div>
|
||||
<div id="webRtcSetup" class="webrtcsetup">
|
||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</div>
|
||||
<audio id="audio-webrtc-in">
|
||||
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="audio-webrtc-out">
|
||||
<source src="/resources/objects/webrtc-out.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="report-message">
|
||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
|
5
front/dist/resources/fonts/fonts.css
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/*This file is a workaround to allow phaser to load directly this font */
|
||||
@font-face {
|
||||
font-family: "Press Start 2P";
|
||||
src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
|
||||
}
|
160
front/dist/resources/html/CustomCharacterScene.html
vendored
@ -1,160 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#customizeScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
width: 42vw;
|
||||
height: 48vh;
|
||||
/*max-width: 300px;
|
||||
max-height: 48vh;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
#customizeScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#customizeScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#customizeScene section.action {
|
||||
text-align: center;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
top: 100%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 55%;
|
||||
}
|
||||
#customizeScene button {
|
||||
margin: 2px 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#customizeScene button#customizeSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#customizeScene section h6,
|
||||
#customizeScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#customizeScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#customizeScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#customizeScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#customizeScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section input#customizeSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton{
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: -8vh;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
|
||||
left: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
|
||||
right: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
|
||||
left: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: -20px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
|
||||
right: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
#customizeScene section.action img{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
#customizeScene section.action a#customizeSceneFormBack img{
|
||||
margin-top: -2px;
|
||||
}
|
||||
#customizeScene section.action button#customizeSceneFormSubmit img{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#customizeScene {
|
||||
max-width: 160px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 400px) {
|
||||
#customizeScene section.action {
|
||||
top: 92%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="customizeScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Custom your WOKA</h3>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
|
||||
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
|
||||
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
|
||||
</section>
|
||||
</form>
|
129
front/dist/resources/html/EnableCameraScene.html
vendored
@ -1,129 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#enableCameraScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
width: 42vw;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#enableCameraScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#enableCameraScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section.title {
|
||||
position: absolute;
|
||||
top: 1vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 40vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#enableCameraScene button#enableCameraSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#enableCameraScene section h6,
|
||||
#enableCameraScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#enableCameraScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#enableCameraScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#enableCameraScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#enableCameraScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section input#enableCameraSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#enableCameraScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
/*@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#enableCameraScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
|
||||
<form id="enableCameraScene" hidden>
|
||||
<!-- FIX me -->
|
||||
<section class="title text-center">
|
||||
<h5>Turn on your camera and microphone</h5>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your camera</h3>
|
||||
<select id="camera">
|
||||
</select>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your michrophone</h3>
|
||||
<select id="michrophone">
|
||||
</select>
|
||||
</section>-->
|
||||
<section class="action">
|
||||
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
|
||||
</section>
|
||||
</form>
|
134
front/dist/resources/html/SelectCompanionScene.html
vendored
@ -1,134 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCompanionScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCompanionScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCompanionScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCompanionScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 20vh;
|
||||
}
|
||||
#selectCompanionScene button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCompanionScene button#selectCompanionSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCompanionScene section h6,
|
||||
#selectCompanionScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCompanionScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCompanionScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCompanionScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCompanionScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section input#selectCompanionSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCompanionScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCompanionScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCompanionScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
|
||||
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
5
front/dist/resources/html/gameMenu.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameMenu main{
|
||||
margin-top: 15px;
|
||||
}
|
||||
@ -67,7 +64,7 @@
|
||||
<button id="adminConsoleButton">Admin console</button>
|
||||
</section>
|
||||
<section id="socialLinks" hidden>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventure.WA" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
||||
</section>
|
||||
</main>
|
||||
|
3
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameQuality {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
3
front/dist/resources/html/gameReport.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameReport {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
3
front/dist/resources/html/gameShare.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameShare {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
109
front/dist/resources/html/helpCameraSettings.html
vendored
@ -1,109 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 25px auto 0;
|
||||
width: 400px;
|
||||
max-height: calc(48vh - 50px);
|
||||
max-width: 48vw;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
#helpCameraSettings section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
font-size: 12px;
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
#helpCameraSettings section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#helpCameraSettings section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#helpCameraSettings section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p a{
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
#helpCameraSettings section ul{
|
||||
margin: 6px;
|
||||
}
|
||||
#helpCameraSettings section li{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section img {
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#helpCameraSettings{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Camera/Microphone access needed</h5>
|
||||
<p class="err" id="permissionError">Permission denied</p>
|
||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||
<ul>
|
||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||
</ul>
|
||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
|
||||
</section>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
120
front/dist/resources/html/loginScene.html
vendored
@ -1,120 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#loginScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
max-width: 200px;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loginScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#loginScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#loginScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#loginScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#loginScene button#loginSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#loginScene section h6,
|
||||
#loginScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#loginScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#loginScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#loginScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#loginScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section input#loginSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#loginScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#loginScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="loginScene" hidden>
|
||||
<section class="text-center">
|
||||
<img src="resources/logos/logo.png">
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Enter your name</h5>
|
||||
<p class="info">9 chars maximum</p>
|
||||
<p class="err" id="errorLoginScene"></p>
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="email" id="loginSceneName">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
142
front/dist/resources/html/selectCharacterScene.html
vendored
@ -1,142 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCharacterScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCharacterScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCharacterScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCharacterScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 28vh;
|
||||
}
|
||||
#selectCharacterScene button {
|
||||
margin: 10px 0px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCharacterScene button#selectCharacterSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCharacterScene section h6,
|
||||
#selectCharacterScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCharacterScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCharacterScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCharacterScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCharacterScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section input#selectCharacterSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCharacterScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: none;
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: none;
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCharacterScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: block;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCharacterScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
|
||||
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
|
||||
</section>
|
||||
</form>
|
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
|
BIN
front/dist/resources/objects/arrow_down.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
front/dist/resources/objects/arrow_up.png
vendored
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 4.9 KiB |
BIN
front/dist/resources/objects/arrow_up_black.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
@ -20,9 +20,11 @@
|
||||
"jasmine": "^3.5.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-check": "^2.1.0",
|
||||
"svelte-loader": "^3.1.1",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"ts-loader": "^9.1.2",
|
||||
@ -34,24 +36,32 @@
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/press-start-2p": "^4.3.0",
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"axios": "^0.21.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"phaser": "^3.54.0",
|
||||
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
|
||||
"phaser3-rex-plugins": "^1.1.42",
|
||||
"queue-typescript": "^1.0.1",
|
||||
"quill": "1.3.6",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0"
|
||||
"socket.io-client": "^2.3.0",
|
||||
"standardized-audio-context": "^25.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"start": "run-p templater serve svelte-check-watch",
|
||||
"templater": "cross-env ./templater.sh",
|
||||
"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",
|
||||
"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",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch",
|
||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\""
|
||||
}
|
||||
}
|
||||
|
@ -1,397 +0,0 @@
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
||||
export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
|
||||
export const INPUT_TYPE_CONSOLE = 'input-type';
|
||||
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
|
||||
|
||||
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||
|
||||
interface EventTargetFiles extends EventTarget {
|
||||
files: Array<File>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class ConsoleGlobalMessageManager {
|
||||
|
||||
private readonly divMainConsole: HTMLDivElement;
|
||||
private readonly divMessageConsole: HTMLDivElement;
|
||||
//private readonly divSettingConsole: HTMLDivElement;
|
||||
private readonly buttonMainConsole: HTMLDivElement;
|
||||
private readonly buttonSendMainConsole: HTMLImageElement;
|
||||
//private readonly buttonAdminMainConsole: HTMLImageElement;
|
||||
//private readonly buttonSettingsMainConsole: HTMLImageElement;
|
||||
private activeConsole: boolean = false;
|
||||
private activeMessage: boolean = false;
|
||||
private activeSetting: boolean = false;
|
||||
private userInputManager!: UserInputManager;
|
||||
private static cssLoaded: boolean = false;
|
||||
|
||||
constructor(private Connection: RoomConnection, userInputManager : UserInputManager, private isAdmin: Boolean) {
|
||||
this.buttonMainConsole = document.createElement('div');
|
||||
this.buttonMainConsole.classList.add('console');
|
||||
this.buttonMainConsole.hidden = true;
|
||||
this.divMainConsole = document.createElement('div');
|
||||
this.divMainConsole.className = CLASS_CONSOLE_MESSAGE;
|
||||
this.divMessageConsole = document.createElement('div');
|
||||
this.divMessageConsole.className = 'message';
|
||||
//this.divSettingConsole = document.createElement('div');
|
||||
//this.divSettingConsole.className = 'setting';
|
||||
this.buttonSendMainConsole = document.createElement('img');
|
||||
this.buttonSendMainConsole.id = 'btn-send-message';
|
||||
//this.buttonSettingsMainConsole = document.createElement('img');
|
||||
//this.buttonAdminMainConsole = document.createElement('img');
|
||||
this.userInputManager = userInputManager;
|
||||
this.initialise();
|
||||
|
||||
}
|
||||
|
||||
initialise() {
|
||||
for (const elem of document.getElementsByClassName(CLASS_CONSOLE_MESSAGE)) {
|
||||
elem.remove();
|
||||
}
|
||||
|
||||
const typeConsole = document.createElement('input');
|
||||
typeConsole.id = INPUT_TYPE_CONSOLE;
|
||||
typeConsole.value = MESSAGE_TYPE;
|
||||
typeConsole.type = 'hidden';
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.classList.add('menu')
|
||||
const textMessage = document.createElement('span');
|
||||
textMessage.innerText = "Message";
|
||||
textMessage.classList.add('active');
|
||||
textMessage.addEventListener('click', () => {
|
||||
textMessage.classList.add('active');
|
||||
const messageSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(INPUT_CONSOLE_MESSAGE));
|
||||
messageSection.classList.add('active');
|
||||
|
||||
textAudio.classList.remove('active');
|
||||
const audioSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(UPLOAD_CONSOLE_MESSAGE));
|
||||
audioSection.classList.remove('active');
|
||||
|
||||
typeConsole.value = MESSAGE_TYPE;
|
||||
});
|
||||
menu.appendChild(textMessage);
|
||||
const textAudio = document.createElement('span');
|
||||
textAudio.innerText = "Audio";
|
||||
textAudio.addEventListener('click', () => {
|
||||
textAudio.classList.add('active');
|
||||
const audioSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(UPLOAD_CONSOLE_MESSAGE));
|
||||
audioSection.classList.add('active');
|
||||
|
||||
textMessage.classList.remove('active');
|
||||
const messageSection = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(this.getSectionId(INPUT_CONSOLE_MESSAGE));
|
||||
messageSection.classList.remove('active');
|
||||
|
||||
typeConsole.value = AUDIO_TYPE;
|
||||
});
|
||||
menu.appendChild(textMessage);
|
||||
menu.appendChild(textAudio);
|
||||
this.divMessageConsole.appendChild(menu);
|
||||
|
||||
this.buttonSendMainConsole.src = 'resources/logos/send-yellow.svg';
|
||||
this.buttonSendMainConsole.addEventListener('click', () => {
|
||||
if(this.activeMessage){
|
||||
this.disabledMessageConsole();
|
||||
}else{
|
||||
this.activeMessageConsole();
|
||||
}
|
||||
});
|
||||
|
||||
/*this.buttonAdminMainConsole.src = 'resources/logos/setting-yellow.svg';
|
||||
this.buttonAdminMainConsole.addEventListener('click', () => {
|
||||
window.open(ADMIN_URL, '_blank');
|
||||
});*/
|
||||
|
||||
/*this.buttonSettingsMainConsole.src = 'resources/logos/monitor-yellow.svg';
|
||||
this.buttonSettingsMainConsole.addEventListener('click', () => {
|
||||
if(this.activeSetting){
|
||||
this.disabledSettingConsole();
|
||||
}else{
|
||||
this.activeSettingConsole();
|
||||
}
|
||||
});*/
|
||||
|
||||
this.divMessageConsole.appendChild(typeConsole);
|
||||
|
||||
/*if(this.isAdmin) {
|
||||
this.buttonMainConsole.appendChild(this.buttonSendMainConsole);
|
||||
//this.buttonMainConsole.appendChild(this.buttonAdminMainConsole);
|
||||
}*/
|
||||
this.createTextMessagePart();
|
||||
this.createUploadAudioPart();
|
||||
//this.buttonMainConsole.appendChild(this.buttonSettingsMainConsole);
|
||||
|
||||
this.divMainConsole.appendChild(this.buttonMainConsole);
|
||||
this.divMainConsole.appendChild(this.divMessageConsole);
|
||||
//this.divMainConsole.appendChild(this.divSettingConsole);
|
||||
|
||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
mainSectionDiv.appendChild(this.divMainConsole);
|
||||
}
|
||||
|
||||
createTextMessagePart(){
|
||||
const div = document.createElement('div');
|
||||
div.id = INPUT_CONSOLE_MESSAGE
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
this.disabledMessageConsole();
|
||||
});
|
||||
const buttonDiv = document.createElement('div');
|
||||
buttonDiv.classList.add('btn-action');
|
||||
buttonDiv.appendChild(buttonSend)
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.id = this.getSectionId(INPUT_CONSOLE_MESSAGE);
|
||||
section.classList.add('active');
|
||||
section.appendChild(div);
|
||||
section.appendChild(buttonDiv);
|
||||
this.divMessageConsole.appendChild(section);
|
||||
|
||||
(async () => {
|
||||
try{
|
||||
// Start loading CSS
|
||||
const cssPromise = ConsoleGlobalMessageManager.loadCss();
|
||||
// Import quill
|
||||
const {default: Quill}:any = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
// Wait for CSS to be loaded
|
||||
await cssPromise;
|
||||
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
|
||||
['clean'],
|
||||
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
|
||||
new Quill(`#${INPUT_CONSOLE_MESSAGE}`, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
createUploadAudioPart(){
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('upload');
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.setAttribute('for', UPLOAD_CONSOLE_MESSAGE);
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.setAttribute('for', UPLOAD_CONSOLE_MESSAGE);
|
||||
img.src = 'resources/logos/music-file.svg';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.id = UPLOAD_CONSOLE_MESSAGE
|
||||
input.addEventListener('input', (e: Event) => {
|
||||
if(!e.target){
|
||||
return;
|
||||
}
|
||||
const eventTarget : EventTargetFiles = (e.target as EventTargetFiles);
|
||||
if(!eventTarget || !eventTarget.files || eventTarget.files.length === 0){
|
||||
return;
|
||||
}
|
||||
const file : File = eventTarget.files[0];
|
||||
|
||||
if(!file){
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
HtmlUtils.removeElementByIdOrFail('audi-message-filename');
|
||||
}catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
const p = document.createElement('p');
|
||||
p.id = 'audi-message-filename';
|
||||
p.innerText = `${file.name} : ${this.getFileSize(file.size)}`;
|
||||
label.appendChild(p);
|
||||
});
|
||||
|
||||
label.appendChild(img);
|
||||
div.appendChild(label);
|
||||
div.appendChild(input);
|
||||
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
this.disabledMessageConsole();
|
||||
});
|
||||
const buttonDiv = document.createElement('div');
|
||||
buttonDiv.classList.add('btn-action');
|
||||
buttonDiv.appendChild(buttonSend)
|
||||
|
||||
const section = document.createElement('section');
|
||||
section.id = this.getSectionId(UPLOAD_CONSOLE_MESSAGE);
|
||||
section.appendChild(div);
|
||||
section.appendChild(buttonDiv);
|
||||
this.divMessageConsole.appendChild(section);
|
||||
}
|
||||
|
||||
private static loadCss(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (ConsoleGlobalMessageManager.cssLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const fileref = document.createElement("link")
|
||||
fileref.setAttribute("rel", "stylesheet")
|
||||
fileref.setAttribute("type", "text/css")
|
||||
fileref.setAttribute("href", "https://cdn.quilljs.com/1.3.7/quill.snow.css");
|
||||
document.getElementsByTagName("head")[0].appendChild(fileref);
|
||||
ConsoleGlobalMessageManager.cssLoaded = true;
|
||||
fileref.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
fileref.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendMessage(){
|
||||
const inputType = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(INPUT_TYPE_CONSOLE);
|
||||
if(AUDIO_TYPE !== inputType.value && MESSAGE_TYPE !== inputType.value){
|
||||
throw "Error event type";
|
||||
}
|
||||
if(AUDIO_TYPE === inputType.value){
|
||||
return this.sendAudioMessage();
|
||||
}
|
||||
return this.sendTextMessage();
|
||||
}
|
||||
|
||||
private sendTextMessage(){
|
||||
const elements = document.getElementsByClassName('ql-editor');
|
||||
const quillEditor = elements.item(0);
|
||||
if(!quillEditor){
|
||||
throw "Error get quill node";
|
||||
}
|
||||
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||
id: "1", // FIXME: use another ID?
|
||||
message: quillEditor.innerHTML,
|
||||
type: MESSAGE_TYPE
|
||||
};
|
||||
quillEditor.innerHTML = '';
|
||||
this.Connection.emitGlobalMessage(GlobalMessage);
|
||||
}
|
||||
|
||||
private async sendAudioMessage(){
|
||||
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(UPLOAD_CONSOLE_MESSAGE);
|
||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||
if(!selectedFile){
|
||||
throw 'no file selected';
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('file', selectedFile);
|
||||
const res = await this.Connection.uploadAudio(fd);
|
||||
|
||||
const GlobalMessage : PlayGlobalMessageInterface = {
|
||||
id: (res as {id: string}).id,
|
||||
message: (res as {path: string}).path,
|
||||
type: AUDIO_TYPE
|
||||
};
|
||||
inputAudio.value = '';
|
||||
try {
|
||||
HtmlUtils.removeElementByIdOrFail('audi-message-filename');
|
||||
}catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
this.Connection.emitGlobalMessage(GlobalMessage);
|
||||
}
|
||||
|
||||
active(){
|
||||
this.userInputManager.disableControls();
|
||||
this.divMainConsole.style.top = '0';
|
||||
this.activeConsole = true;
|
||||
}
|
||||
|
||||
disabled(){
|
||||
this.userInputManager.initKeyBoardEvent();
|
||||
this.activeConsole = false;
|
||||
this.divMainConsole.style.top = '-80%';
|
||||
}
|
||||
|
||||
activeMessageConsole(){
|
||||
if(!this.isAdmin){
|
||||
throw "User is not admin";
|
||||
}
|
||||
if(this.activeMessage){
|
||||
this.disabledMessageConsole();
|
||||
return;
|
||||
}
|
||||
this.activeMessage = true;
|
||||
this.active();
|
||||
this.divMessageConsole.classList.add('active');
|
||||
this.buttonMainConsole.hidden = false;
|
||||
this.buttonSendMainConsole.classList.add('active');
|
||||
//if button not
|
||||
try{
|
||||
HtmlUtils.getElementByIdOrFail('btn-send-message');
|
||||
}catch (e) {
|
||||
this.buttonMainConsole.appendChild(this.buttonSendMainConsole);
|
||||
}
|
||||
}
|
||||
|
||||
disabledMessageConsole(){
|
||||
this.activeMessage = false;
|
||||
this.disabled();
|
||||
this.buttonMainConsole.hidden = true;
|
||||
this.divMessageConsole.classList.remove('active');
|
||||
this.buttonSendMainConsole.classList.remove('active');
|
||||
}
|
||||
|
||||
private getSectionId(id: string) : string {
|
||||
return `section-${id}`;
|
||||
}
|
||||
|
||||
private getFileSize(number: number) :string {
|
||||
if (number < 1024) {
|
||||
return number + 'bytes';
|
||||
} else if (number >= 1024 && number < 1048576) {
|
||||
return (number / 1024).toFixed(1) + 'KB';
|
||||
} else if (number >= 1048576) {
|
||||
return (number / 1048576).toFixed(1) + 'MB';
|
||||
}else{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import {soundManager} from "../Phaser/Game/SoundManager";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export class GlobalMessageManager {
|
||||
|
||||
@ -34,54 +36,17 @@ export class GlobalMessageManager {
|
||||
previousMessage.remove();
|
||||
}
|
||||
|
||||
if(AUDIO_TYPE === message.type){
|
||||
if(AdminMessageEventTypes.audio === message.type){
|
||||
this.playAudioMessage(message.id, message.message);
|
||||
}
|
||||
|
||||
if(MESSAGE_TYPE === message.type){
|
||||
if(AdminMessageEventTypes.admin === message.type){
|
||||
this.playTextMessage(message.id, message.message);
|
||||
}
|
||||
}
|
||||
|
||||
private playAudioMessage(messageId : string, urlMessage: string){
|
||||
//delete previous elements
|
||||
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
||||
for(let i = 0; i < previousDivAudio.length; i++){
|
||||
previousDivAudio[i].remove();
|
||||
}
|
||||
|
||||
//create new element
|
||||
const divAudio : HTMLDivElement = document.createElement('div');
|
||||
divAudio.id = `audio-playing-${messageId}`;
|
||||
divAudio.classList.add('audio-playing');
|
||||
const imgAudio : HTMLImageElement = document.createElement('img');
|
||||
imgAudio.src = '/resources/logos/megaphone.svg';
|
||||
const pAudio : HTMLParagraphElement = document.createElement('p');
|
||||
pAudio.textContent = 'Message audio'
|
||||
divAudio.appendChild(imgAudio);
|
||||
divAudio.appendChild(pAudio);
|
||||
|
||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
mainSectionDiv.appendChild(divAudio);
|
||||
|
||||
const messageAudio : HTMLAudioElement = document.createElement('audio');
|
||||
messageAudio.id = this.getHtmlMessageId(messageId);
|
||||
messageAudio.autoplay = true;
|
||||
messageAudio.style.display = 'none';
|
||||
messageAudio.onended = () => {
|
||||
divAudio.classList.remove('active');
|
||||
messageAudio.remove();
|
||||
setTimeout(() => {
|
||||
divAudio.remove();
|
||||
}, 1000);
|
||||
}
|
||||
messageAudio.onplay = () => {
|
||||
divAudio.classList.add('active');
|
||||
}
|
||||
const messageAudioSource : HTMLSourceElement = document.createElement('source');
|
||||
messageAudioSource.src = `${UPLOADER_URL}${urlMessage}`;
|
||||
messageAudio.appendChild(messageAudioSource);
|
||||
mainSectionDiv.appendChild(messageAudio);
|
||||
private playAudioMessage(messageId : string, urlMessage: string) {
|
||||
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
|
||||
}
|
||||
|
||||
private playTextMessage(messageId : string, htmlMessage: string){
|
||||
|
@ -44,7 +44,13 @@ export class TypeMessageExt implements TypeMessageInterface{
|
||||
mainSectionDiv.appendChild(div);
|
||||
|
||||
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
||||
reportMessageAudio.play();
|
||||
// FIXME: this will fail on iOS
|
||||
// We should move the sound playing into the GameScene and listen to the event of a report using a store
|
||||
try {
|
||||
reportMessageAudio.play();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
this.nbSecond = this.maxNbSecond;
|
||||
setTimeout((c) => {
|
||||
|
@ -5,9 +5,7 @@ import type { ChatEvent } from './ChatEvent';
|
||||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||
import type { GoToPageEvent } from './GoToPageEvent';
|
||||
import type { MenuItemClickedEvent } from './MenuItemClickedEvent';
|
||||
import type { MenuItemRegisterEvent } from './MenuItemRegisterEvent';
|
||||
import type { HasPlayerMovedEvent } from './HasPlayerMovedEvent';
|
||||
import type { LoadPageEvent } from './LoadPageEvent';
|
||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||
import type { OpenTabEvent } from './OpenTabEvent';
|
||||
@ -17,16 +15,18 @@ import type { LayerEvent } from './LayerEvent';
|
||||
import type { SetPropertyEvent } from "./setPropertyEvent";
|
||||
import type { LoadSoundEvent } from "./LoadSoundEvent";
|
||||
import type { PlaySoundEvent } from "./PlaySoundEvent";
|
||||
import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||
import type { MenuItemRegisterEvent } from './ui/MenuItemRegisterEvent';
|
||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||
import type { ChangeTileEvent } from "./ChangeTileEvent";
|
||||
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type IframeEventMap = {
|
||||
getState: GameStateEvent,
|
||||
registerMenuCommand: MenuItemRegisterEvent
|
||||
//getState: GameStateEvent,
|
||||
loadPage: LoadPageEvent
|
||||
chat: ChatEvent,
|
||||
openPopup: OpenPopupEvent
|
||||
closePopup: ClosePopupEvent
|
||||
@ -43,11 +43,12 @@ export type IframeEventMap = {
|
||||
hideLayer: LayerEvent
|
||||
setProperty: SetPropertyEvent
|
||||
getDataLayer: undefined
|
||||
//tilsetEvent: TilesetEvent
|
||||
loadSound: LoadSoundEvent
|
||||
playSound: PlaySoundEvent
|
||||
stopSound: null
|
||||
changeTile: ChangeTileEvent
|
||||
getState: undefined,
|
||||
registerMenuCommand: MenuItemRegisterEvent
|
||||
}
|
||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||
type: T;
|
||||
|
13
front/src/Api/Events/LoadPageEvent.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
||||
|
||||
export const isLoadPageEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
url: tg.isString,
|
||||
}).get();
|
||||
|
||||
/**
|
||||
* A message sent from the iFrame to the game to add a message in the chat.
|
||||
*/
|
||||
export type LoadPageEvent = tg.GuardedType<typeof isLoadPageEvent>;
|
@ -1,10 +0,0 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
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>;
|
@ -8,3 +8,5 @@ export const isMenuItemClickedEvent =
|
||||
* A message sent from the game to the iFrame when a menu item is clicked.
|
||||
*/
|
||||
export type MenuItemClickedEvent = tg.GuardedType<typeof isMenuItemClickedEvent>;
|
||||
|
||||
|
25
front/src/Api/Events/ui/MenuItemRegisterEvent.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
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();
|
||||
|
||||
|
||||
const _registerMenuCommandStream: Subject<string> = new Subject();
|
||||
export const registerMenuCommandStream = _registerMenuCommandStream.asObservable();
|
||||
|
||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
||||
_registerMenuCommandStream.next(event.menutItem)
|
||||
}
|
@ -1,30 +1,36 @@
|
||||
import {Subject} from "rxjs";
|
||||
import {ChatEvent, isChatEvent} from "./Events/ChatEvent";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import type {EnterLeaveEvent} from "./Events/EnterLeaveEvent";
|
||||
import {isOpenPopupEvent, OpenPopupEvent} from "./Events/OpenPopupEvent";
|
||||
import {isOpenTabEvent, OpenTabEvent} from "./Events/OpenTabEvent";
|
||||
import type {ButtonClickedEvent} from "./Events/ButtonClickedEvent";
|
||||
import {ClosePopupEvent, isClosePopupEvent} from "./Events/ClosePopupEvent";
|
||||
import {scriptUtils} from "./ScriptUtils";
|
||||
import {GoToPageEvent, isGoToPageEvent} from "./Events/GoToPageEvent";
|
||||
import {isOpenCoWebsite, OpenCoWebSiteEvent} from "./Events/OpenCoWebSiteEvent";
|
||||
import {
|
||||
IframeEvent,
|
||||
IframeEventMap,
|
||||
IframeResponseEvent,
|
||||
IframeResponseEventMap,
|
||||
isIframeEventWrapper,
|
||||
TypedMessageEvent
|
||||
} from "./Events/IframeEvent";
|
||||
import type {UserInputChatEvent} from "./Events/UserInputChatEvent";
|
||||
import {isPlaySoundEvent, PlaySoundEvent} from "./Events/PlaySoundEvent";
|
||||
import {isStopSoundEvent, StopSoundEvent} from "./Events/StopSoundEvent";
|
||||
import {isLoadSoundEvent, LoadSoundEvent} from "./Events/LoadSoundEvent";
|
||||
import {isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
|
||||
import {isLayerEvent, LayerEvent} from "./Events/LayerEvent";
|
||||
import {isMenuItemRegisterEvent,} from "./Events/ui/MenuItemRegisterEvent";
|
||||
import type {DataLayerEvent} from "./Events/DataLayerEvent";
|
||||
import type {GameStateEvent} from "./Events/GameStateEvent";
|
||||
import type {HasPlayerMovedEvent} from "./Events/HasPlayerMovedEvent";
|
||||
import {isLoadPageEvent} from "./Events/LoadPageEvent";
|
||||
import {handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent} from "./Events/ui/MenuItemRegisterEvent";
|
||||
import {ChangeTileEvent, isChangeTileEvent} from "./Events/ChangeTileEvent";
|
||||
|
||||
import { Subject } from "rxjs";
|
||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||
import { scriptUtils } from "./ScriptUtils";
|
||||
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
|
||||
import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
|
||||
import type { GameStateEvent } from './Events/GameStateEvent';
|
||||
import type { HasPlayerMovedEvent } from './Events/HasPlayerMovedEvent';
|
||||
import { Math } from 'phaser';
|
||||
import type { DataLayerEvent } from "./Events/DataLayerEvent";
|
||||
import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent';
|
||||
import type { MenuItemClickedEvent } from './Events/MenuItemClickedEvent';
|
||||
//import { isTilesetEvent, TilesetEvent } from "./Events/TilesetEvent";
|
||||
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
|
||||
import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent";
|
||||
import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent";
|
||||
import { isChangeTileEvent, ChangeTileEvent } from './Events/ChangeTileEvent';
|
||||
/**
|
||||
* Listens to messages from iframes and turn those messages into easy to use observables.
|
||||
* Also allows to send messages to those iframes.
|
||||
@ -43,6 +49,9 @@ class IframeListener {
|
||||
private readonly _goToPageStream: Subject<GoToPageEvent> = new Subject();
|
||||
public readonly goToPageStream = this._goToPageStream.asObservable();
|
||||
|
||||
private readonly _loadPageStream: Subject<string> = new Subject();
|
||||
public readonly loadPageStream = this._loadPageStream.asObservable();
|
||||
|
||||
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
|
||||
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
|
||||
|
||||
@ -82,8 +91,8 @@ class IframeListener {
|
||||
private readonly _registerMenuCommandStream: Subject<string> = new Subject();
|
||||
public readonly registerMenuCommandStream = this._registerMenuCommandStream.asObservable();
|
||||
|
||||
/* private readonly _tilesetLoaderStream: Subject<TilesetEvent> = new Subject();
|
||||
public readonly tilesetLoaderStream = this._tilesetLoaderStream.asObservable();*/
|
||||
private readonly _unregisterMenuCommandStream: Subject<string> = new Subject();
|
||||
public readonly unregisterMenuCommandStream = this._unregisterMenuCommandStream.asObservable();
|
||||
|
||||
private readonly _playSoundStream: Subject<PlaySoundEvent> = new Subject();
|
||||
public readonly playSoundStream = this._playSoundStream.asObservable();
|
||||
@ -98,6 +107,7 @@ class IframeListener {
|
||||
public readonly changeTileStream = this._changeTileStream.asObservable();
|
||||
|
||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||
private sendPlayerMove: boolean = false;
|
||||
|
||||
@ -106,14 +116,17 @@ class IframeListener {
|
||||
// Do we trust the sender of this message?
|
||||
// Let's only accept messages from the iframe that are allowed.
|
||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||
let foundSrc: string | null = null;
|
||||
for (const iframe of this.iframes) {
|
||||
let foundSrc: string | undefined;
|
||||
|
||||
let iframe: HTMLIFrameElement;
|
||||
for (iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
foundSrc = iframe.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundSrc) {
|
||||
|
||||
if (foundSrc === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -138,6 +151,9 @@ class IframeListener {
|
||||
else if (payload.type === 'goToPage' && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
}
|
||||
else if (payload.type === 'loadPage' && isLoadPageEvent(payload.data)) {
|
||||
this._loadPageStream.next(payload.data.url);
|
||||
}
|
||||
else if (payload.type === 'playSound' && isPlaySoundEvent(payload.data)) {
|
||||
this._playSoundStream.next(payload.data);
|
||||
}
|
||||
@ -148,11 +164,7 @@ class IframeListener {
|
||||
this._loadSoundStream.next(payload.data);
|
||||
}
|
||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||
return this.scripts.get(key)?.contentWindow == message.source
|
||||
})
|
||||
|
||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
|
||||
}
|
||||
|
||||
else if (payload.type === 'closeCoWebSite') {
|
||||
@ -174,6 +186,13 @@ class IframeListener {
|
||||
this.sendPlayerMove = true
|
||||
} else if (payload.type == "getDataLayer") {
|
||||
this._dataLayerChangeStream.next();
|
||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||
const data = payload.data.menutItem;
|
||||
// @ts-ignore
|
||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||
this._unregisterMenuCommandStream.next(data);
|
||||
})
|
||||
handleMenuItemRegistrationEvent(payload.data)
|
||||
} else if (payload.type == "registerMenuCommand" && isMenuItemRegisterEvent(payload.data)) {
|
||||
this._registerMenuCommandStream.next(payload.data.menutItem)
|
||||
} else if (payload.type == "changeTile" && isChangeTileEvent(payload.data)) {
|
||||
@ -192,7 +211,7 @@ class IframeListener {
|
||||
}
|
||||
|
||||
|
||||
sendFrozenGameStateEvent(gameStateEvent: GameStateEvent) {
|
||||
sendGameStateEvent(gameStateEvent: GameStateEvent) {
|
||||
this.postMessage({
|
||||
'type': 'gameState',
|
||||
'data': gameStateEvent
|
||||
@ -204,9 +223,13 @@ class IframeListener {
|
||||
*/
|
||||
registerIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframes.add(iframe);
|
||||
this.iframeCloseCallbacks.set(iframe, []);
|
||||
}
|
||||
|
||||
unregisterIframe(iframe: HTMLIFrameElement): void {
|
||||
this.iframeCloseCallbacks.get(iframe)?.forEach(callback => {
|
||||
callback();
|
||||
});
|
||||
this.iframes.delete(iframe);
|
||||
}
|
||||
|
||||
@ -216,7 +239,7 @@ class IframeListener {
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||
// Using external iframe mode (
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '/iframe.html?script=' + encodeURIComponent(scriptUrl);
|
||||
|
||||
@ -231,25 +254,24 @@ class IframeListener {
|
||||
} else {
|
||||
// production code
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = this.getIFrameId(scriptUrl);
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = 'none';
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add('allow-scripts');
|
||||
iframe.sandbox.add('allow-top-navigation-by-user-activation');
|
||||
|
||||
const html = '<!doctype html>\n' +
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc = '<!doctype html>\n' +
|
||||
'\n' +
|
||||
'<html lang="en">\n' +
|
||||
'<head>\n' +
|
||||
'<script src="' + window.location.protocol + '//' + window.location.host + '/iframe_api.js" ></script>\n' +
|
||||
'<script src="' + scriptUrl + '" ></script>\n' +
|
||||
'<title></title>\n' +
|
||||
'</head>\n' +
|
||||
'</html>\n';
|
||||
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc = html;
|
||||
|
||||
document.body.prepend(iframe);
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
@ -259,12 +281,12 @@ class IframeListener {
|
||||
|
||||
}
|
||||
|
||||
private getIFrameId(scriptUrl: string): string {
|
||||
private static getIFrameId(scriptUrl: string): string {
|
||||
return 'script' + btoa(scriptUrl);
|
||||
}
|
||||
|
||||
unregisterScript(scriptUrl: string): void {
|
||||
const iFrameId = this.getIFrameId(scriptUrl);
|
||||
const iFrameId = IframeListener.getIFrameId(scriptUrl);
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLIFrameElement>(iFrameId);
|
||||
if (!iframe) {
|
||||
throw new Error('Unknown iframe for script "' + scriptUrl + '"');
|
||||
@ -275,15 +297,6 @@ class IframeListener {
|
||||
this.scripts.delete(scriptUrl);
|
||||
}
|
||||
|
||||
sendMenuClickedEvent(menuItem: string) {
|
||||
this.postMessage({
|
||||
'type': 'menuItemClicked',
|
||||
'data': {
|
||||
menuItem: menuItem,
|
||||
} as MenuItemClickedEvent
|
||||
});
|
||||
}
|
||||
|
||||
sendUserInputChat(message: string) {
|
||||
this.postMessage({
|
||||
'type': 'userInputChat',
|
||||
@ -333,7 +346,7 @@ class IframeListener {
|
||||
/**
|
||||
* Sends the message... to all allowed iframes.
|
||||
*/
|
||||
private postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||
public postMessage(message: IframeResponseEvent<keyof IframeResponseEventMap>) {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.contentWindow?.postMessage(message, '*');
|
||||
}
|
||||
|
31
front/src/Api/iframe/IframeApiContribution.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import type * as tg from "generic-type-guard";
|
||||
import type { IframeEvent, IframeEventMap, IframeResponseEventMap } from '../Events/IframeEvent';
|
||||
|
||||
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
|
||||
window.parent.postMessage(content, "*")
|
||||
}
|
||||
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never
|
||||
|
||||
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> {
|
||||
|
||||
typeChecker: Guard,
|
||||
callback: (payloadData: T) => void
|
||||
}
|
||||
|
||||
export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> {
|
||||
|
||||
type: Key
|
||||
}
|
||||
|
||||
/**
|
||||
* !! be aware that the implemented attributes (addMethodsAtRoot and subObjectIdentifier) must be readonly
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
export abstract class IframeApiContribution<T extends {
|
||||
callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>,
|
||||
}> {
|
||||
|
||||
abstract callbacks: T["callbacks"]
|
||||
}
|
39
front/src/Api/iframe/Sound/Sound.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {sendToWorkadventure} from "../IframeApiContribution";
|
||||
import type {LoadSoundEvent} from "../../Events/LoadSoundEvent";
|
||||
import type {PlaySoundEvent} from "../../Events/PlaySoundEvent";
|
||||
import type {StopSoundEvent} from "../../Events/StopSoundEvent";
|
||||
import SoundConfig = Phaser.Types.Sound.SoundConfig;
|
||||
|
||||
export class Sound {
|
||||
constructor(private url: string) {
|
||||
sendToWorkadventure({
|
||||
"type": 'loadSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
} as LoadSoundEvent
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public play(config: SoundConfig) {
|
||||
sendToWorkadventure({
|
||||
"type": 'playSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
config
|
||||
} as PlaySoundEvent
|
||||
|
||||
});
|
||||
return this.url;
|
||||
}
|
||||
public stop() {
|
||||
sendToWorkadventure({
|
||||
"type": 'stopSound',
|
||||
"data": {
|
||||
url: this.url,
|
||||
} as StopSoundEvent
|
||||
|
||||
});
|
||||
return this.url;
|
||||
}
|
||||
}
|
18
front/src/Api/iframe/Ui/ButtonDescriptor.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import type {Popup} from "./Popup";
|
||||
|
||||
export type ButtonClickedCallback = (popup: Popup) => void;
|
||||
|
||||
export interface ButtonDescriptor {
|
||||
/**
|
||||
* The label of the button
|
||||
*/
|
||||
label: string,
|
||||
/**
|
||||
* The type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled"
|
||||
*/
|
||||
className?: "normal" | "primary" | "success" | "warning" | "error" | "disabled",
|
||||
/**
|
||||
* Callback called if the button is pressed
|
||||
*/
|
||||
callback: ButtonClickedCallback,
|
||||
}
|
11
front/src/Api/iframe/Ui/MenuItem.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { MenuItemClickedEvent } from '../../Events/ui/MenuItemClickedEvent';
|
||||
import { iframeListener } from '../../IframeListener';
|
||||
|
||||
export function sendMenuClickedEvent(menuItem: string) {
|
||||
iframeListener.postMessage({
|
||||
'type': 'menuItemClicked',
|
||||
'data': {
|
||||
menuItem: menuItem,
|
||||
} as MenuItemClickedEvent
|
||||
});
|
||||
}
|
19
front/src/Api/iframe/Ui/Popup.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {sendToWorkadventure} from "../IframeApiContribution";
|
||||
import type {ClosePopupEvent} from "../../Events/ClosePopupEvent";
|
||||
|
||||
export class Popup {
|
||||
constructor(private id: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the popup
|
||||
*/
|
||||
public close(): void {
|
||||
sendToWorkadventure({
|
||||
'type': 'closePopup',
|
||||
'data': {
|
||||
'popupId': this.id,
|
||||
} as ClosePopupEvent
|
||||
});
|
||||
}
|
||||
}
|
38
front/src/Api/iframe/chat.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { ChatEvent } from '../Events/ChatEvent'
|
||||
import { isUserInputChatEvent, UserInputChatEvent } from '../Events/UserInputChatEvent'
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution'
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
const chatStream = new Subject<string>();
|
||||
|
||||
class WorkadventureChatCommands extends IframeApiContribution<WorkadventureChatCommands> {
|
||||
|
||||
callbacks = [apiCallback({
|
||||
callback: (event: UserInputChatEvent) => {
|
||||
chatStream.next(event.message);
|
||||
},
|
||||
type: "userInputChat",
|
||||
typeChecker: isUserInputChatEvent
|
||||
})]
|
||||
|
||||
|
||||
sendChatMessage(message: string, author: string) {
|
||||
sendToWorkadventure({
|
||||
type: 'chat',
|
||||
data: {
|
||||
'message': message,
|
||||
'author': author
|
||||
} as ChatEvent
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to messages sent by the local user, in the chat.
|
||||
*/
|
||||
onChatMessage(callback: (message: string) => void) {
|
||||
chatStream.subscribe(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventureChatCommands()
|
16
front/src/Api/iframe/controls.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
|
||||
class WorkadventureControlsCommands extends IframeApiContribution<WorkadventureControlsCommands> {
|
||||
callbacks = []
|
||||
|
||||
disablePlayerControls(): void {
|
||||
sendToWorkadventure({ 'type': 'disablePlayerControls', data: null });
|
||||
}
|
||||
|
||||
restorePlayerControls(): void {
|
||||
sendToWorkadventure({ 'type': 'restorePlayerControls', data: null });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureControlsCommands();
|
57
front/src/Api/iframe/nav.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import type { GoToPageEvent } from '../Events/GoToPageEvent';
|
||||
import type { OpenTabEvent } from '../Events/OpenTabEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import type {OpenCoWebSiteEvent} from "../Events/OpenCoWebSiteEvent";
|
||||
import type {LoadPageEvent} from "../Events/LoadPageEvent";
|
||||
|
||||
|
||||
class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
|
||||
callbacks = []
|
||||
|
||||
|
||||
openTab(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'openTab',
|
||||
"data": {
|
||||
url
|
||||
} as OpenTabEvent
|
||||
});
|
||||
}
|
||||
|
||||
goToPage(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'goToPage',
|
||||
"data": {
|
||||
url
|
||||
} as GoToPageEvent
|
||||
});
|
||||
}
|
||||
|
||||
goToRoom(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'loadPage',
|
||||
"data": {
|
||||
url
|
||||
} as LoadPageEvent
|
||||
});
|
||||
}
|
||||
|
||||
openCoWebSite(url: string): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'openCoWebSite',
|
||||
"data": {
|
||||
url
|
||||
} as OpenCoWebSiteEvent
|
||||
});
|
||||
}
|
||||
|
||||
closeCoWebSite(): void {
|
||||
sendToWorkadventure({
|
||||
"type": 'closeCoWebSite',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureNavigationCommands();
|
29
front/src/Api/iframe/player.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution";
|
||||
import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent";
|
||||
import {Subject} from "rxjs";
|
||||
import {apiCallback} from "./registeredCallbacks";
|
||||
import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent";
|
||||
|
||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||
|
||||
class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: 'hasPlayerMoved',
|
||||
typeChecker: isHasPlayerMovedEvent,
|
||||
callback: (payloadData) => {
|
||||
moveStream.next(payloadData);
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
|
||||
moveStream.subscribe(callback);
|
||||
sendToWorkadventure({
|
||||
type: 'onPlayerMove',
|
||||
data: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventurePlayerCommands();
|
16
front/src/Api/iframe/registeredCallbacks.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type {IframeResponseEventMap} from "../../Api/Events/IframeEvent";
|
||||
import type {IframeCallback} from "../../Api/iframe/IframeApiContribution";
|
||||
import type {IframeCallbackContribution} from "../../Api/iframe/IframeApiContribution";
|
||||
|
||||
export const registeredCallbacks: { [K in keyof IframeResponseEventMap]?: IframeCallback<K> } = {}
|
||||
|
||||
export function apiCallback<T extends keyof IframeResponseEventMap>(callbackData: IframeCallbackContribution<T>): IframeCallbackContribution<keyof IframeResponseEventMap> {
|
||||
const iframeCallback = {
|
||||
typeChecker: callbackData.typeChecker,
|
||||
callback: callbackData.callback
|
||||
} as IframeCallback<T>;
|
||||
|
||||
const newCallback = { [callbackData.type]: iframeCallback };
|
||||
Object.assign(registeredCallbacks, newCallback)
|
||||
return callbackData as unknown as IframeCallbackContribution<keyof IframeResponseEventMap>;
|
||||
}
|
148
front/src/Api/iframe/room.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { Subject } from "rxjs";
|
||||
import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent';
|
||||
import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution';
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import type {LayerEvent} from "../Events/LayerEvent";
|
||||
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
|
||||
import type {GameStateEvent} from "../Events/GameStateEvent";
|
||||
import type {ITiledMap} from "../../Phaser/Map/ITiledMap";
|
||||
import type {DataLayerEvent} from "../Events/DataLayerEvent";
|
||||
import {isGameStateEvent} from "../Events/GameStateEvent";
|
||||
import {isDataLayerEvent} from "../Events/DataLayerEvent";
|
||||
|
||||
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||
const stateResolvers = new Subject<GameStateEvent>();
|
||||
|
||||
let immutableData: GameStateEvent;
|
||||
|
||||
interface Room {
|
||||
id: string,
|
||||
mapUrl: string,
|
||||
map: ITiledMap,
|
||||
startLayer: string | null
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string | undefined,
|
||||
nickName: string | null,
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
interface TileDescriptor {
|
||||
x: number
|
||||
y: number
|
||||
tile: number | string
|
||||
layer: string
|
||||
}
|
||||
|
||||
|
||||
function getGameState(): Promise<GameStateEvent> {
|
||||
if (immutableData) {
|
||||
return Promise.resolve(immutableData);
|
||||
}
|
||||
else {
|
||||
return new Promise<GameStateEvent>((resolver, thrower) => {
|
||||
stateResolvers.subscribe(resolver);
|
||||
sendToWorkadventure({type: "getState", data: null});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDataLayer(): Promise<DataLayerEvent> {
|
||||
return new Promise<DataLayerEvent>((resolver, thrower) => {
|
||||
dataLayerResolver.subscribe(resolver);
|
||||
sendToWorkadventure({type: "getDataLayer", data: null})
|
||||
})
|
||||
}
|
||||
|
||||
class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
callback: (payloadData: EnterLeaveEvent) => {
|
||||
enterStreams.get(payloadData.name)?.next();
|
||||
},
|
||||
type: "enterEvent",
|
||||
typeChecker: isEnterLeaveEvent
|
||||
}),
|
||||
apiCallback({
|
||||
type: "leaveEvent",
|
||||
typeChecker: isEnterLeaveEvent,
|
||||
callback: (payloadData) => {
|
||||
leaveStreams.get(payloadData.name)?.next();
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "gameState",
|
||||
typeChecker: isGameStateEvent,
|
||||
callback: (payloadData) => {
|
||||
stateResolvers.next(payloadData);
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "dataLayer",
|
||||
typeChecker: isDataLayerEvent,
|
||||
callback: (payloadData) => {
|
||||
dataLayerResolver.next(payloadData);
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
onEnterZone(name: string, callback: () => void): void {
|
||||
let subject = enterStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
enterStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
|
||||
}
|
||||
onLeaveZone(name: string, callback: () => void): void {
|
||||
let subject = leaveStreams.get(name);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<EnterLeaveEvent>();
|
||||
leaveStreams.set(name, subject);
|
||||
}
|
||||
subject.subscribe(callback);
|
||||
}
|
||||
showLayer(layerName: string): void {
|
||||
sendToWorkadventure({type: 'showLayer', data: {'name': layerName} as LayerEvent});
|
||||
}
|
||||
hideLayer(layerName: string): void {
|
||||
sendToWorkadventure({type: 'hideLayer', data: {'name': layerName} as LayerEvent});
|
||||
}
|
||||
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
|
||||
sendToWorkadventure({
|
||||
type: 'setProperty',
|
||||
data: {
|
||||
'layerName': layerName,
|
||||
'propertyName': propertyName,
|
||||
'propertyValue': propertyValue,
|
||||
} as SetPropertyEvent
|
||||
})
|
||||
}
|
||||
getCurrentRoom(): Promise<Room> {
|
||||
return getGameState().then((gameState) => {
|
||||
return getDataLayer().then((mapJson) => {
|
||||
return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName};
|
||||
})
|
||||
})
|
||||
}
|
||||
getCurrentUser(): Promise<User> {
|
||||
return getGameState().then((gameState) => {
|
||||
return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags};
|
||||
})
|
||||
}
|
||||
changeTile(tiles: TileDescriptor[]) {
|
||||
sendToWorkadventure({
|
||||
type: 'changeTile',
|
||||
data: tiles
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureRoomCommands();
|
17
front/src/Api/iframe/sound.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { LoadSoundEvent } from '../Events/LoadSoundEvent';
|
||||
import type { PlaySoundEvent } from '../Events/PlaySoundEvent';
|
||||
import type { StopSoundEvent } from '../Events/StopSoundEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import {Sound} from "./Sound/Sound";
|
||||
|
||||
class WorkadventureSoundCommands extends IframeApiContribution<WorkadventureSoundCommands> {
|
||||
callbacks = []
|
||||
|
||||
loadSound(url: string): Sound {
|
||||
return new Sound(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default new WorkadventureSoundCommands();
|
106
front/src/Api/iframe/ui.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { isButtonClickedEvent } from '../Events/ButtonClickedEvent';
|
||||
import { isMenuItemClickedEvent } from '../Events/ui/MenuItemClickedEvent';
|
||||
import type { MenuItemRegisterEvent } from '../Events/ui/MenuItemRegisterEvent';
|
||||
import { IframeApiContribution, sendToWorkadventure } from './IframeApiContribution';
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
|
||||
import { Popup } from "./Ui/Popup";
|
||||
|
||||
let popupId = 0;
|
||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||
const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<number, Map<number, ButtonClickedCallback>>();
|
||||
|
||||
const menuCallbacks: Map<string, (command: string) => void> = new Map()
|
||||
|
||||
interface ZonedPopupOptions {
|
||||
zone: string
|
||||
objectLayerName?: string,
|
||||
popupText: string,
|
||||
delay?: number
|
||||
popupOptions: Array<ButtonDescriptor>
|
||||
}
|
||||
|
||||
|
||||
class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
||||
|
||||
callbacks = [apiCallback({
|
||||
type: "buttonClickedEvent",
|
||||
typeChecker: isButtonClickedEvent,
|
||||
callback: (payloadData) => {
|
||||
const callback = popupCallbacks.get(payloadData.popupId)?.get(payloadData.buttonId);
|
||||
const popup = popups.get(payloadData.popupId);
|
||||
if (popup === undefined) {
|
||||
throw new Error('Could not find popup with ID "' + payloadData.popupId + '"');
|
||||
}
|
||||
if (callback) {
|
||||
callback(popup);
|
||||
}
|
||||
}
|
||||
}),
|
||||
apiCallback({
|
||||
type: "menuItemClicked",
|
||||
typeChecker: isMenuItemClickedEvent,
|
||||
callback: event => {
|
||||
const callback = menuCallbacks.get(event.menuItem);
|
||||
if (callback) {
|
||||
callback(event.menuItem)
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||
popupId++;
|
||||
const popup = new Popup(popupId);
|
||||
const btnMap = new Map<number, () => void>();
|
||||
popupCallbacks.set(popupId, btnMap);
|
||||
let id = 0;
|
||||
for (const button of buttons) {
|
||||
const callback = button.callback;
|
||||
if (callback) {
|
||||
btnMap.set(id, () => {
|
||||
callback(popup);
|
||||
});
|
||||
}
|
||||
id++;
|
||||
}
|
||||
|
||||
sendToWorkadventure({
|
||||
'type': 'openPopup',
|
||||
'data': {
|
||||
popupId,
|
||||
targetObject,
|
||||
message,
|
||||
buttons: buttons.map((button) => {
|
||||
return {
|
||||
label: button.label,
|
||||
className: button.className
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
popups.set(popupId, popup)
|
||||
return popup;
|
||||
}
|
||||
|
||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void) {
|
||||
menuCallbacks.set(commandDescriptor, callback);
|
||||
sendToWorkadventure({
|
||||
'type': 'registerMenuCommand',
|
||||
'data': {
|
||||
menutItem: commandDescriptor
|
||||
} as MenuItemRegisterEvent
|
||||
});
|
||||
}
|
||||
|
||||
displayBubble(): void {
|
||||
sendToWorkadventure({ 'type': 'displayBubble', data: null });
|
||||
}
|
||||
|
||||
removeBubble(): void {
|
||||
sendToWorkadventure({ 'type': 'removeBubble', data: null });
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkAdventureUiCommands();
|
@ -1,11 +1,93 @@
|
||||
<script lang="typescript">
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import {menuIconVisible} from "../Stores/MenuStore";
|
||||
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import MyCamera from "./MyCamera.svelte";
|
||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||
import {selectCompanionSceneVisibleStore} from "../Stores/SelectCompanionStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
|
||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
|
||||
import {errorStore} from "../Stores/ErrorStore";
|
||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||
import LoginScene from "./Login/LoginScene.svelte";
|
||||
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||
|
||||
import type {Game} from "../Phaser/Game/Game";
|
||||
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||
import {consoleGlobalMessageManagerVisibleStore} from "../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import ConsoleGlobalMessageManager from "./ConsoleGlobalMessageManager/ConsoleGlobalMessageManager.svelte";
|
||||
|
||||
export let game: Game;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<!-- {#if $menuIconVisible}
|
||||
<MenuIcon />
|
||||
{/if} -->
|
||||
{#if $loginSceneVisibleStore}
|
||||
<div class="scrollable">
|
||||
<LoginScene game={game}></LoginScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCharacterScene game={ game }></SelectCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $customCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<CustomCharacterScene game={ game }></CustomCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCompanionSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCompanionScene game={ game }></SelectCompanionScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $enableCameraSceneVisibilityStore}
|
||||
<div class="scrollable">
|
||||
<EnableCameraScene game={game}></EnableCameraScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $soundPlayingStore}
|
||||
<div>
|
||||
<AudioPlaying url={$soundPlayingStore} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
{#if $menuIconVisible}
|
||||
<div>
|
||||
<MenuIcon />
|
||||
</div>
|
||||
{/if}
|
||||
-->
|
||||
{#if $gameOverlayVisibilityStore}
|
||||
<div>
|
||||
<MyCamera></MyCamera>
|
||||
<CameraControls></CameraControls>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $consoleGlobalMessageManagerVisibleStore}
|
||||
<div>
|
||||
<ConsoleGlobalMessageManager game={game}></ConsoleGlobalMessageManager>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $helpCameraSettingsVisibleStore}
|
||||
<div>
|
||||
<HelpCameraSettingsPopup></HelpCameraSettingsPopup>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore}></VisitCard>
|
||||
{/if}
|
||||
{#if $errorStore.length > 0}
|
||||
<div>
|
||||
<ErrorDialog></ErrorDialog>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
61
front/src/Components/CameraControls.svelte
Normal file
@ -0,0 +1,61 @@
|
||||
<script lang="typescript">
|
||||
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
import monitorCloseImg from "./images/monitor-close.svg";
|
||||
import cinemaImg from "./images/cinema.svg";
|
||||
import cinemaCloseImg from "./images/cinema-close.svg";
|
||||
import microphoneImg from "./images/microphone.svg";
|
||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if ($requestedScreenSharingState === true) {
|
||||
requestedScreenSharingState.disableScreenSharing();
|
||||
} else {
|
||||
requestedScreenSharingState.enableScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
function cameraClick(): void {
|
||||
if ($requestedCameraState === true) {
|
||||
requestedCameraState.disableWebcam();
|
||||
} else {
|
||||
requestedCameraState.enableWebcam();
|
||||
}
|
||||
}
|
||||
|
||||
function microphoneClick(): void {
|
||||
if ($requestedMicrophoneState === true) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
} else {
|
||||
requestedMicrophoneState.enableMicrophone();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||
{#if $requestedCameraState}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||
{#if $requestedMicrophoneState}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,44 @@
|
||||
<script lang="typescript">
|
||||
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
|
||||
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
|
||||
export let game: Game;
|
||||
let inputSendTextActive = true;
|
||||
let uploadMusicActive = false;
|
||||
|
||||
function inputSendTextActivate() {
|
||||
inputSendTextActive = true;
|
||||
uploadMusicActive = false;
|
||||
}
|
||||
|
||||
function inputUploadMusicActivate() {
|
||||
uploadMusicActive = true;
|
||||
inputSendTextActive = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="main-console nes-container is-rounded">
|
||||
<!-- <div class="console nes-container is-rounded">
|
||||
<img class="btn-close" src="resources/logos/send-yellow.svg" alt="Close">
|
||||
</div>-->
|
||||
<div class="main-global-message">
|
||||
<h2> Global Message </h2>
|
||||
<div class="global-message">
|
||||
<div class="menu">
|
||||
<button class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
|
||||
<button class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
|
||||
</div>
|
||||
<div class="main-input">
|
||||
{#if inputSendTextActive}
|
||||
<InputTextGlobalMessage game={game} gameManager={gameManager}></InputTextGlobalMessage>
|
||||
{/if}
|
||||
{#if uploadMusicActive}
|
||||
<UploadAudioGlobalMessage game={game} gameManager={gameManager}></UploadAudioGlobalMessage>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import {onMount} from "svelte";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
||||
import type {Quill} from "quill";
|
||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
|
||||
//toolbar
|
||||
export const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // toggled buttons
|
||||
['blockquote', 'code-block'],
|
||||
|
||||
[{'header': 1}, {'header': 2}], // custom button values
|
||||
[{'list': 'ordered'}, {'list': 'bullet'}],
|
||||
[{'script': 'sub'}, {'script': 'super'}], // superscript/subscript
|
||||
[{'indent': '-1'}, {'indent': '+1'}], // outdent/indent
|
||||
[{'direction': 'rtl'}], // text direction
|
||||
|
||||
[{'size': ['small', false, 'large', 'huge']}], // custom dropdown
|
||||
[{'header': [1, 2, 3, 4, 5, 6, false]}],
|
||||
|
||||
[{'color': []}, {'background': []}], // dropdown with defaults from theme
|
||||
[{'font': []}],
|
||||
[{'align': []}],
|
||||
|
||||
['clean'],
|
||||
|
||||
['link', 'image', 'video']
|
||||
// remove formatting button
|
||||
];
|
||||
|
||||
export let game: Game;
|
||||
export let gameManager: GameManager;
|
||||
|
||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
||||
let quill: Quill;
|
||||
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
|
||||
|
||||
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||
|
||||
//Quill
|
||||
onMount(async () => {
|
||||
|
||||
// Import quill
|
||||
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
toolbar: toolbarOptions
|
||||
},
|
||||
});
|
||||
|
||||
quill.on('selection-change', function (range, oldRange) {
|
||||
if (range === null && oldRange !== null) {
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
} else if (range !== null && oldRange === null)
|
||||
consoleGlobalMessageManagerFocusStore.set(true);
|
||||
});
|
||||
});
|
||||
|
||||
function disableConsole() {
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
}
|
||||
|
||||
function SendTextMessage() {
|
||||
if (gameScene == undefined) {
|
||||
return;
|
||||
}
|
||||
const text = quill.getText(0, quill.getLength());
|
||||
|
||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
||||
id: "1", // FIXME: use another ID?
|
||||
message: text,
|
||||
type: MESSAGE_TYPE
|
||||
};
|
||||
|
||||
quill.deleteText(0, quill.getLength());
|
||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||
disableConsole();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section class="section-input-send-text">
|
||||
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
|
||||
<div class="btn-action">
|
||||
<button class="nes-btn is-primary" on:click|preventDefault={SendTextMessage}>Send</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
@import 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
|
||||
</style>
|
@ -0,0 +1,130 @@
|
||||
<script lang="ts">
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import type {GameManager} from "../../Phaser/Game/GameManager";
|
||||
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
|
||||
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
|
||||
import uploadFile from "../images/music-file.svg";
|
||||
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
|
||||
interface EventTargetFiles extends EventTarget {
|
||||
files: Array<File>;
|
||||
}
|
||||
|
||||
export let game: Game;
|
||||
export let gameManager: GameManager;
|
||||
|
||||
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
|
||||
let fileinput: HTMLInputElement;
|
||||
let filename: string;
|
||||
let filesize: string;
|
||||
let errorfile: boolean;
|
||||
|
||||
const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||
|
||||
|
||||
async function SendAudioMessage() {
|
||||
if (gameScene == undefined) {
|
||||
return;
|
||||
}
|
||||
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
|
||||
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
|
||||
if (!selectedFile) {
|
||||
errorfile = true;
|
||||
throw 'no file selected';
|
||||
}
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('file', selectedFile);
|
||||
const res = await gameScene.connection?.uploadAudio(fd);
|
||||
|
||||
const GlobalMessage: PlayGlobalMessageInterface = {
|
||||
id: (res as { id: string }).id,
|
||||
message: (res as { path: string }).path,
|
||||
type: AUDIO_TYPE
|
||||
}
|
||||
inputAudio.value = '';
|
||||
gameScene.connection?.emitGlobalMessage(GlobalMessage);
|
||||
disableConsole();
|
||||
}
|
||||
|
||||
function inputAudioFile(event: Event) {
|
||||
const eventTarget : EventTargetFiles = (event.target as EventTargetFiles);
|
||||
if(!eventTarget || !eventTarget.files || eventTarget.files.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
const file = eventTarget.files[0];
|
||||
if(!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
filename = file.name;
|
||||
filesize = getFileSize(file.size);
|
||||
errorfile = false;
|
||||
}
|
||||
|
||||
function getFileSize(number: number) {
|
||||
if (number < 1024) {
|
||||
return number + 'bytes';
|
||||
} else if (number >= 1024 && number < 1048576) {
|
||||
return (number / 1024).toFixed(1) + 'KB';
|
||||
} else if (number >= 1048576) {
|
||||
return (number / 1048576).toFixed(1) + 'MB';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function disableConsole() {
|
||||
consoleGlobalMessageManagerVisibleStore.set(false);
|
||||
consoleGlobalMessageManagerFocusStore.set(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<section class="section-input-send-audio">
|
||||
<div class="input-send-audio">
|
||||
<img src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileinput.click();}}>
|
||||
{#if filename != undefined}
|
||||
<label for="input-send-audio">{filename} : {filesize}</label>
|
||||
{/if}
|
||||
{#if errorfile}
|
||||
<p class="err">No file selected. You need to upload a file before sending it.</p>
|
||||
{/if}
|
||||
<input type="file" id="input-send-audio" bind:this={fileinput} on:change={(e) => {inputAudioFile(e)}}>
|
||||
</div>
|
||||
<div class="btn-action">
|
||||
<button class="nes-btn is-primary" on:click|preventDefault={SendAudioMessage}>Send</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
//UploadAudioGlobalMessage
|
||||
.section-input-send-audio {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.section-input-send-audio .input-send-audio {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-input-send-audio #input-send-audio{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio label{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio p.err {
|
||||
color: #ce372b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-input-send-audio div.input-send-audio img{
|
||||
height: 150px;
|
||||
cursor: url('../../../style/images/cursor_pointer.png'), pointer;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,119 @@
|
||||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
||||
let activeRow = customCharacterScene.activeRow;
|
||||
|
||||
function selectLeft() {
|
||||
customCharacterScene.moveCursorHorizontally(-1);
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
customCharacterScene.moveCursorHorizontally(1);
|
||||
}
|
||||
|
||||
function selectUp() {
|
||||
customCharacterScene.moveCursorVertically(-1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function selectDown() {
|
||||
customCharacterScene.moveCursorVertically(1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function previousScene() {
|
||||
customCharacterScene.backToPreviousScene();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
customCharacterScene.nextSceneToCamera();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="customCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Customize your WOKA</h2>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
{#if activeRow === 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||
{/if}
|
||||
{#if activeRow !== 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||
{/if}
|
||||
{#if activeRow === 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||
{/if}
|
||||
{#if activeRow !== 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||
{/if}
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.customCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.customCharacterSceneButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button.customCharacterSceneFormBack {
|
||||
color: #292929;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.customCharacterSceneButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.customCharacterSceneButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.customCharacterScene button.customCharacterSceneButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.customCharacterScene button.customCharacterSceneButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
229
front/src/Components/EnableCamera/EnableCameraScene.svelte
Normal file
@ -0,0 +1,229 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {
|
||||
audioConstraintStore,
|
||||
cameraListStore,
|
||||
localStreamStore,
|
||||
microphoneListStore,
|
||||
videoConstraintStore
|
||||
} from "../../Stores/MediaStore";
|
||||
import {onDestroy} from "svelte";
|
||||
import HorizontalSoundMeterWidget from "./HorizontalSoundMeterWidget.svelte";
|
||||
import cinemaCloseImg from "../images/cinema-close.svg";
|
||||
import cinemaImg from "../images/cinema.svg";
|
||||
import microphoneImg from "../images/microphone.svg";
|
||||
|
||||
export let game: Game;
|
||||
let selectedCamera : string|undefined = undefined;
|
||||
let selectedMicrophone : string|undefined = undefined;
|
||||
|
||||
const enableCameraScene = game.scene.getScene(EnableCameraSceneName) as EnableCameraScene;
|
||||
|
||||
function submit() {
|
||||
enableCameraScene.login();
|
||||
}
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream: MediaStream | null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
|
||||
if (stream !== null) {
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
if (videoTracks.length > 0) {
|
||||
selectedCamera = videoTracks[0].getSettings().deviceId;
|
||||
}
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
if (audioTracks.length > 0) {
|
||||
selectedMicrophone = audioTracks[0].getSettings().deviceId;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream = null;
|
||||
selectedCamera = undefined;
|
||||
selectedMicrophone = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
function normalizeDeviceName(label: string): string {
|
||||
// remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)"
|
||||
return label.replace(/(\([[0-9a-f]{4}:[0-9a-f]{4}\))/g, '').trim();
|
||||
}
|
||||
|
||||
function selectCamera() {
|
||||
videoConstraintStore.setDeviceId(selectedCamera);
|
||||
}
|
||||
|
||||
function selectMicrophone() {
|
||||
audioConstraintStore.setDeviceId(selectedMicrophone);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<h2>Turn on your camera and microphone</h2>
|
||||
</section>
|
||||
{#if $localStreamStore.type === 'success' && $localStreamStore.stream}
|
||||
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
{:else }
|
||||
<div class="webrtcsetup">
|
||||
<img class="background-img" src={cinemaCloseImg} alt="">
|
||||
</div>
|
||||
{/if}
|
||||
<HorizontalSoundMeterWidget stream={stream}></HorizontalSoundMeterWidget>
|
||||
|
||||
<section class="selectWebcamForm">
|
||||
|
||||
{#if $cameraListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={cinemaImg} alt="Camera" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedCamera} on:change={selectCamera}>
|
||||
{#each $cameraListStore as camera}
|
||||
<option value={camera.deviceId}>
|
||||
{normalizeDeviceName(camera.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $microphoneListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={microphoneImg} alt="Microphone" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedMicrophone} on:change={selectMicrophone}>
|
||||
{#each $microphoneListStore as microphone}
|
||||
<option value={microphone.deviceId}>
|
||||
{normalizeDeviceName(microphone.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.enableCameraScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
|
||||
section.selectWebcamForm {
|
||||
margin-top: 3vh;
|
||||
margin-bottom: 3vh;
|
||||
min-height: 10vh;
|
||||
width: 50vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
select {
|
||||
font-family: "Press Start 2P";
|
||||
margin-top: 1vh;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
option {
|
||||
font-family: "Press Start 2P";
|
||||
}
|
||||
}
|
||||
|
||||
section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.letsgo {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-height: 60px;
|
||||
margin-top: 10px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.webrtcsetup{
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 28.125vw;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img.background-img {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.myCamVideoSetup {
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 50vh;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.enableCameraScene h2 {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene .control-group .nes-select {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene button.letsgo {
|
||||
font-size: 160%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,82 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream | null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 20;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function color(i: number, volume: number) {
|
||||
const red = 255 * i / NB_BARS;
|
||||
const green = 255 * (1 - i / NB_BARS);
|
||||
|
||||
let alpha = 1;
|
||||
if (i >= volume) {
|
||||
alpha = 0.5;
|
||||
}
|
||||
|
||||
return 'background-color:rgba('+red+', '+green+', 0, '+alpha+')';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="horizontal-sound-meter" class:active={display}>
|
||||
{#each [...Array(NB_BARS).keys()] as i}
|
||||
<div style={color(i, volume)}></div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.horizontal-sound-meter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 50%;
|
||||
height: 30px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.horizontal-sound-meter div {
|
||||
margin-left: 5px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,73 @@
|
||||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||
|
||||
let isAndroid = window.navigator.userAgent.includes('Android');
|
||||
let isFirefox = window.navigator.userAgent.includes('Firefox');
|
||||
let isChrome = window.navigator.userAgent.includes('Chrome');
|
||||
|
||||
function refresh() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function close() {
|
||||
helpCameraSettingsVisibleStore.set(false);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="helpCameraSettings nes-container" on:submit|preventDefault={close} transition:fly="{{ y: -900, duration: 500 }}">
|
||||
<section>
|
||||
<h2>Camera / Microphone access needed</h2>
|
||||
<p class="err">Permission denied</p>
|
||||
<p>You must allow camera and microphone access in your browser.</p>
|
||||
<p>
|
||||
{#if isFirefox }
|
||||
<p class="err">Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.</p>
|
||||
<img src={firefoxImg} alt="" />
|
||||
{:else if isChrome && !isAndroid }
|
||||
<img src={chromeImg} alt="" />
|
||||
{/if}
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
|
||||
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}>Continue without webcam</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.helpCameraSettings {
|
||||
pointer-events: auto;
|
||||
background: #eceeee;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10vh;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-family: 'Press Start 2P';
|
||||
}
|
||||
|
||||
section {
|
||||
p {
|
||||
margin: 15px;
|
||||
font-family: 'Press Start 2P';
|
||||
|
||||
& .err {
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
122
front/src/Components/Login/LoginScene.svelte
Normal file
@ -0,0 +1,122 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
|
||||
import logoImg from "../images/logo.png";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const loginScene = game.scene.getScene(LoginSceneName) as LoginScene;
|
||||
|
||||
let name = gameManager.getPlayerName() || '';
|
||||
let startValidating = false;
|
||||
|
||||
function submit() {
|
||||
startValidating = true;
|
||||
|
||||
let finalName = name.trim();
|
||||
if (finalName !== '') {
|
||||
loginScene.login(finalName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="loginScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<img src={logoImg} alt="WorkAdventure logo" />
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h2>Enter your name</h2>
|
||||
</section>
|
||||
<input type="text" name="loginSceneName" class="nes-input is-dark" autofocus maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
|
||||
<section class="error-section">
|
||||
{#if name.trim() === '' && startValidating }
|
||||
<p class="err">The name is empty</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if DISPLAY_TERMS_OF_USE}
|
||||
<section class="terms-and-conditions">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
{/if}
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.loginScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
color: #ebeeee;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
font-family: "Press Start 2P";
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.terms-and-conditions {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
p.err {
|
||||
color: #ce372b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.error-section {
|
||||
min-height: 2rem;
|
||||
margin: 0;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
46
front/src/Components/MyCamera.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<script lang="typescript">
|
||||
import {localStreamStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream : MediaStream|null;
|
||||
/*$: {
|
||||
if ($localStreamStore.type === 'success') {
|
||||
stream = $localStreamStore.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
}*/
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream }
|
||||
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,87 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCompanionScene = game.scene.getScene(SelectCompanionSceneName) as SelectCompanionScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCompanionScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCompanionScene.moveToRight();
|
||||
}
|
||||
|
||||
function noCompanion() {
|
||||
selectCompanionScene.closeScene();
|
||||
}
|
||||
|
||||
function selectCompanion() {
|
||||
selectCompanionScene.selectCompanion();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="selectCompanionScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your companion</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}>No companion</button>
|
||||
<button type="submit" class="selectCompanionSceneFormSubmit nes-btn is-primary" on:click|preventDefault={selectCompanion}>Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCompanionScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
button.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCompanionScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCompanionScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
53
front/src/Components/SoundMeterWidget.svelte
Normal file
@ -0,0 +1,53 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream|null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 5;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div class="sound-progress" class:active={display}>
|
||||
<span class:active={volume > 1}></span>
|
||||
<span class:active={volume > 2}></span>
|
||||
<span class:active={volume > 3}></span>
|
||||
<span class:active={volume > 4}></span>
|
||||
<span class:active={volume > 5}></span>
|
||||
</div>
|
52
front/src/Components/UI/AudioPlaying.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import megaphoneImg from "./images/megaphone.svg";
|
||||
import {soundPlayingStore} from "../../Stores/SoundPlayingStore";
|
||||
import {afterUpdate} from "svelte";
|
||||
|
||||
export let url: string;
|
||||
let audio: HTMLAudioElement;
|
||||
|
||||
function soundEnded() {
|
||||
soundPlayingStore.soundEnded();
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
audio.play();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="audio-playing" transition:fly="{{ x: 210, duration: 500 }}">
|
||||
<img src={megaphoneImg} alt="Audio playing" />
|
||||
<p>Audio message</p>
|
||||
<audio bind:this={audio} src={url} on:ended={soundEnded} >
|
||||
<track kind="captions">
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
/*audio html when audio message playing*/
|
||||
.audio-playing {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 54px;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
transition: all 0.1s ease-out;
|
||||
background-color: black;
|
||||
border-radius: 30px 0 0 30px;
|
||||
display: inline-flex;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
background-color: #ffda01;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: white;
|
||||
margin-left: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
48
front/src/Components/UI/ErrorDialog.svelte
Normal file
@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import {errorStore} from "../../Stores/ErrorStore";
|
||||
|
||||
function close(): boolean {
|
||||
errorStore.clearMessages();
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="error-div nes-container is-dark is-rounded" open>
|
||||
<p class="nes-text is-error title">Error</p>
|
||||
<div class="body">
|
||||
{#each $errorStore as error}
|
||||
<p>{error}</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.error-div {
|
||||
pointer-events: auto;
|
||||
margin-top: 10vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: max-content;
|
||||
max-width: 80vw;
|
||||
|
||||
.button-bar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body {
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
18
front/src/Components/UI/images/megaphone.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
|
||||
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
|
||||
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
|
||||
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
|
||||
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
|
||||
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
|
||||
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
|
||||
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
|
||||
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
|
||||
L256.1,445.3z"/>
|
||||
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
|
||||
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
|
||||
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
|
||||
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
84
front/src/Components/VisitCard/VisitCard.svelte
Normal file
@ -0,0 +1,84 @@
|
||||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
export let visitCardUrl: string;
|
||||
let w = '500px';
|
||||
let h = '250px';
|
||||
let hidden = true;
|
||||
let cvIframe: HTMLIFrameElement;
|
||||
|
||||
function closeCard() {
|
||||
requestVisitCardsStore.set(null);
|
||||
}
|
||||
|
||||
function handleIframeMessage(message:any) {
|
||||
if (message.data.type === 'cvIframeSize') {
|
||||
w = (message.data.data.w) + 'px';
|
||||
h = (message.data.data.h) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
cvIframe.onload = () => hidden = false
|
||||
cvIframe.onerror = () => hidden = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin:auto;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.visitCard {
|
||||
pointer-events: all;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 200px;
|
||||
max-width: 80vw;
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
max-width: 80vw;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}" style="width: {w}">
|
||||
{#if hidden}
|
||||
<div class="loader"></div>
|
||||
{/if}
|
||||
<iframe title="visitCard" src={visitCardUrl} allow="clipboard-read; clipboard-write self {visitCardUrl}" style="width: {w}; height: {h}" class:hidden={hidden} bind:this={cvIframe}></iframe>
|
||||
{#if !hidden}
|
||||
<div class="buttonContainer">
|
||||
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
<svelte:window on:message={handleIframeMessage}/>
|
41
front/src/Components/images/cinema-close.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
|
||||
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
|
||||
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
|
||||
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
|
||||
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
|
||||
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
|
||||
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
|
||||
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
|
||||
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
|
||||
C0.8,68,6.8,56.8,10.1,51.6z"/>
|
||||
<g>
|
||||
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
|
||||
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
|
||||
<g>
|
||||
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
|
||||
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
|
||||
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
|
||||
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
|
||||
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
|
||||
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
front/src/Components/images/logo.png
Normal file
After Width: | Height: | Size: 16 KiB |
27
front/src/Components/images/microphone-close.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<rect x="257" y="-47.9" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 643.9641 283.6469)" class="st0" width="20.4" height="628.3"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M333.6,250.3c-52.6-43.9-105.1-87.9-157.7-131.8c0-17.9,0-35.8,0-53.6c6.5-38.6,40.3-67,79.3-66.8
|
||||
c38.6,0.2,71.9,28.5,78.4,66.8C333.6,126.7,333.6,188.5,333.6,250.3z"/>
|
||||
<path class="st0" d="M322.6,279.9c-48.9-53.8-97.8-107.6-146.6-161.4l0,0c52.6,43.9,105.1,87.9,157.7,131.8
|
||||
c-0.2,1.6-0.5,3.3-0.9,5C330.5,265.2,326.6,273.5,322.6,279.9z"/>
|
||||
</g>
|
||||
<path class="st0" d="M292.5,308.1c-2.3,1.2-39.5,20.3-76.7-1c-36.4-20.8-39.4-61.2-39.6-64.1c-0.1-21-0.1-42.1-0.2-63.1
|
||||
C214.8,222.6,253.6,265.3,292.5,308.1z"/>
|
||||
</g>
|
||||
<path class="st0" d="M431.6,238.5c-0.9-8.4-8.5-14.4-16.6-13.5c-7.9,0.9-13.9,8.1-13.2,16.3c-0.1,13.3-2.2,34.6-12.6,57.9
|
||||
c-6.3,14.2-14,25.2-20.6,33.1c6.8,7.5,13.6,14.9,20.3,22.4c9.5-10.9,23.4-29.7,32.8-56.3C430.3,273.9,431.8,252.5,431.6,238.5z"/>
|
||||
<line class="st0" x1="354.5" y1="347.2" x2="374.6" y2="369.4"/>
|
||||
<path class="st0" d="M338.5,359.9c6.8,7.4,13.5,14.9,20.3,22.3c-52.6,37.6-121.5,43.7-179.2,15.8c-60.3-29.1-98.9-90.7-99.3-158.2
|
||||
c0-8.2,6.8-15,15-15s15,6.8,15,15c0.1,13.5,2.4,54.4,32.4,91.6c4.2,5.2,45.1,54.1,113.3,54.1C297,385.6,326.7,367.9,338.5,359.9z"/>
|
||||
<rect x="241" y="409.6" class="st0" width="29.9" height="102.3"/>
|
||||
<path class="st0" d="M304.2,511.9h-97.1c-8-0.4-14.3-7.1-14.3-15c0-8.1,6.7-14.9,15-15c31.7,0,63.4,0.1,95.1,0.1
|
||||
c8.9-0.6,16.3,6.5,16.3,14.9C319.2,504.8,312.6,511.7,304.2,511.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
27
front/src/Components/images/music-file.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 448 448" style="enable-background:new 0 0 448 448;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFDA01;}
|
||||
</style>
|
||||
<path class="st0" d="M348,288c-44.2,0-80,35.8-80,80s35.8,80,80,80s80-35.8,80-80C428,323.8,392.2,288,348,288z M387.6,359.6
|
||||
c-3.1,3.1-8.2,3.1-11.3,0L356,339.3V416c0,4.4-3.6,8-8,8s-8-3.6-8-8v-76.7l-20.3,20.3c-3.1,3-8.1,3-11.2-0.1s-3.1-8.1-0.1-11.2
|
||||
l33.9-33.9c0.7-0.7,1.6-1.3,2.6-1.7c2-0.8,4.2-0.8,6.1,0c1,0.4,1.9,1,2.6,1.7l33.9,33.9C390.7,351.4,390.7,356.5,387.6,359.6z"/>
|
||||
<path class="st0" d="M244,154.6L148,182v15.4l96-27.4V154.6z"/>
|
||||
<path class="st0" d="M244,280c0,8.8-7.2,16-16,16s-16-7.2-16-16s7.2-16,16-16S244,271.2,244,280z"/>
|
||||
<path class="st0" d="M132,312c0,8.8-7.2,16-16,16s-16-7.2-16-16s7.2-16,16-16S132,303.2,132,312z"/>
|
||||
<path class="st0" d="M31.3,80H100V11.3L31.3,80z"/>
|
||||
<path class="st0" d="M20,448h275c-0.1-0.1-0.2-0.1-0.3-0.2c-2.9-2-5.8-4.1-8.5-6.4c-0.7-0.6-1.4-1.3-2.1-1.9
|
||||
c-1.9-1.7-3.8-3.5-5.6-5.4c-0.8-0.9-1.6-1.8-2.4-2.7c-1.6-1.8-3.2-3.8-4.7-5.7c-0.7-0.9-1.4-1.8-2-2.7c-1.8-2.6-3.5-5.2-5-8
|
||||
c-0.2-0.4-0.4-0.7-0.6-1c-1.7-3.1-3.2-6.4-4.6-9.7c-0.4-1-0.7-2-1.1-3c-0.9-2.4-1.7-4.9-2.4-7.4c-0.3-1.2-0.6-2.4-0.9-3.6
|
||||
c-0.6-2.5-1.1-5-1.5-7.6c-0.2-1.1-0.4-2.3-0.5-3.4c-0.9-6.9-1-13.9-0.2-20.8c0.1-1.1,0.3-2.1,0.5-3.1c0.3-2.1,0.5-4.1,0.9-6.2
|
||||
c0.2-1.2,0.6-2.4,0.9-3.7c0.4-1.8,0.8-3.6,1.4-5.3c0.4-1.3,0.9-2.5,1.3-3.8c0.6-1.6,1.1-3.3,1.8-4.9c0.5-1.3,1.1-2.5,1.7-3.7
|
||||
c0.7-1.5,1.4-3,2.2-4.5c0.6-1.2,1.4-2.4,2-3.6c0.8-1.4,1.7-2.8,2.6-4.2c0.8-1.2,1.6-2.3,2.4-3.4c0.9-1.3,1.9-2.6,2.9-3.9
|
||||
c0.9-1.1,1.8-2.1,2.7-3.2c1.1-1.2,2.1-2.4,3.2-3.6c1-1,2-2,3-2.9c1.2-1.1,2.3-2.2,3.6-3.2c1.1-0.9,2.1-1.8,3.2-2.7
|
||||
c1.3-1,2.6-2,3.9-2.9c1.1-0.8,2.3-1.6,3.5-2.4c1.4-0.9,2.8-1.7,4.2-2.5c1.2-0.7,2.4-1.4,3.6-2c1.5-0.8,2.9-1.5,4.4-2.1
|
||||
c1.3-0.6,2.5-1.2,3.8-1.7c1.6-0.6,3.1-1.2,4.7-1.7c1.3-0.4,2.6-0.9,3.9-1.3c1.6-0.5,3.3-0.9,5-1.3c1.3-0.3,2.6-0.7,4-0.9
|
||||
c1.8-0.3,3.5-0.6,5.3-0.8c1.3-0.2,2.6-0.4,4-0.5c0.3,0,0.6-0.1,1-0.1V0H116v88c0,4.4-3.6,8-8,8H20V448z M116,280
|
||||
c5.6,0,11.2,1.6,16,4.4V176c0-3.6,2.4-6.7,5.8-7.7l112-32c2.4-0.7,5-0.2,7,1.3s3.2,3.9,3.2,6.4v136c0,17.7-14.3,32-32,32
|
||||
s-32-14.3-32-32s14.3-32,32-32c5.6,0,11.2,1.6,16,4.4v-65.8L148,214v98c0,17.7-14.3,32-32,32s-32-14.3-32-32S98.3,280,116,280z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,92 @@
|
||||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName) as SelectCharacterScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCharacterScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCharacterScene.moveToRight();
|
||||
}
|
||||
|
||||
function cameraScene() {
|
||||
selectCharacterScene.nextSceneToCameraScene();
|
||||
}
|
||||
|
||||
function customizeScene() {
|
||||
selectCharacterScene.nextSceneToCustomizeScene();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="selectCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your WOKA</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ cameraScene }>Continue</button>
|
||||
<button type="submit" class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn" on:click|preventDefault={ customizeScene }>Customize your WOKA</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCharacterScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCharacterScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection";
|
||||
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
||||
import {localUserStore} from "./LocalUserStore";
|
||||
import {LocalUser} from "./LocalUser";
|
||||
import {CharacterTexture, LocalUser} from "./LocalUser";
|
||||
import {Room} from "./Room";
|
||||
|
||||
|
||||
@ -46,8 +46,8 @@ class ConnectionManager {
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
return Promise.resolve(room);
|
||||
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
|
||||
let localUser = localUserStore.getLocalUser();
|
||||
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
|
||||
this.localUser = localUser;
|
||||
try {
|
||||
@ -57,16 +57,42 @@ class ConnectionManager {
|
||||
console.error('JWT token invalid. Did it expire? Login anonymously instead.');
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
} else {
|
||||
}else{
|
||||
await this.anonymousLogin();
|
||||
}
|
||||
let roomId: string
|
||||
|
||||
localUser = localUserStore.getLocalUser();
|
||||
if(!localUser){
|
||||
throw "Error to store local user data";
|
||||
}
|
||||
|
||||
let roomId: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomId = START_ROOM_URL;
|
||||
} else {
|
||||
roomId = window.location.pathname + window.location.search + window.location.hash;
|
||||
}
|
||||
return Promise.resolve(new Room(roomId));
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
const room = new Room(roomId);
|
||||
const mapDetail = await room.getMapDetail();
|
||||
if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if(localUser.textures.length === 0){
|
||||
localUser.textures = mapDetail.textures;
|
||||
}else{
|
||||
mapDetail.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
|
||||
if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
|
||||
return;
|
||||
}
|
||||
localUser?.textures.push(newTexture)
|
||||
});
|
||||
}
|
||||
this.localUser = localUser;
|
||||
localUserStore.saveUser(localUser);
|
||||
}
|
||||
return Promise.resolve(room);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Invalid URL'));
|
||||
|
@ -1,5 +1,3 @@
|
||||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import type {SignalData} from "simple-peer";
|
||||
import type {RoomConnection} from "./RoomConnection";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
@ -47,6 +45,7 @@ export interface MessageUserPositionInterface {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string|null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
@ -60,6 +59,7 @@ export interface MessageUserJoined {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string | null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
||||
@ -85,11 +85,6 @@ export interface WebRtcSignalReceivedMessageInterface {
|
||||
webRtcPassword: string | undefined
|
||||
}
|
||||
|
||||
export interface StartMapInterface {
|
||||
mapUrlStart: string,
|
||||
startInstance: string
|
||||
}
|
||||
|
||||
export interface ViewportInterface {
|
||||
left: number,
|
||||
top: number,
|
||||
|
@ -9,9 +9,8 @@ export interface CharacterTexture {
|
||||
|
||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||
|
||||
export function isUserNameValid(value: string): boolean {
|
||||
const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$');
|
||||
return regexp.test(value);
|
||||
export function isUserNameValid(value: unknown): boolean {
|
||||
return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && /\S/.test(value);
|
||||
}
|
||||
|
||||
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
@ -25,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
}
|
||||
|
||||
export class LocalUser {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
|
||||
constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
import Axios from "axios";
|
||||
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {CharacterTexture} from "./LocalUser";
|
||||
|
||||
export class MapDetail{
|
||||
constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private mapUrl: string|undefined;
|
||||
private textures: CharacterTexture[]|undefined;
|
||||
private instance: string|undefined;
|
||||
private _search: URLSearchParams;
|
||||
|
||||
@ -50,10 +57,10 @@ export class Room {
|
||||
return {roomId, hash}
|
||||
}
|
||||
|
||||
public async getMapUrl(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (this.mapUrl !== undefined) {
|
||||
resolve(this.mapUrl);
|
||||
public async getMapDetail(): Promise<MapDetail> {
|
||||
return new Promise<MapDetail>((resolve, reject) => {
|
||||
if (this.mapUrl !== undefined && this.textures != undefined) {
|
||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -61,7 +68,7 @@ export class Room {
|
||||
const match = /_\/[^/]+\/(.+)/.exec(this.id);
|
||||
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
|
||||
this.mapUrl = window.location.protocol+'//'+match[1];
|
||||
resolve(this.mapUrl);
|
||||
resolve(new MapDetail(this.mapUrl, this.textures));
|
||||
return;
|
||||
} else {
|
||||
// We have a private ID, we need to query the map URL from the server.
|
||||
@ -71,7 +78,7 @@ export class Room {
|
||||
params: urlParts
|
||||
}).then(({data}) => {
|
||||
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
|
||||
resolve(data.mapUrl);
|
||||
resolve(data);
|
||||
return;
|
||||
}).catch((reason) => {
|
||||
reject(reason);
|
||||
|
@ -30,7 +30,7 @@ import {
|
||||
EmoteEventMessage,
|
||||
EmotePromptMessage,
|
||||
SendUserMessage,
|
||||
BanUserMessage
|
||||
BanUserMessage,
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
|
||||
@ -177,9 +177,9 @@ export class RoomConnection implements RoomConnection {
|
||||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
this.closed = true;
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||
@ -343,6 +343,7 @@ export class RoomConnection implements RoomConnection {
|
||||
userId: message.getUserid(),
|
||||
name: message.getName(),
|
||||
characterLayers,
|
||||
visitCardUrl: message.getVisitcardurl(),
|
||||
position: ProtobufClientUtils.toPointInterface(position),
|
||||
companion: companion ? companion.getName() : null
|
||||
}
|
||||
@ -617,7 +618,7 @@ export class RoomConnection implements RoomConnection {
|
||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||
}
|
||||
|
||||
public getAllTag() : string[] {
|
||||
public getAllTags() : string[] {
|
||||
return this.tags;
|
||||
}
|
||||
}
|
||||
|