Merge pull request #1812 from thecodingmachine/develop
Deploy 2022-02-01
This commit is contained in:
commit
95b471f809
5
.github/workflows/continuous_integration.yml
vendored
5
.github/workflows/continuous_integration.yml
vendored
@ -10,7 +10,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
continuous-integration-front:
|
continuous-integration-front:
|
||||||
name: "Continuous Integration Front"
|
name: "Continuous Integration Front"
|
||||||
|
|
||||||
@ -46,6 +45,10 @@ jobs:
|
|||||||
run: ./templater.sh
|
run: ./templater.sh
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
|
- name: "Generate i18n files"
|
||||||
|
run: yarn run typesafe-i18n
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build
|
run: yarn run build
|
||||||
env:
|
env:
|
||||||
|
2
.github/workflows/end_to_end_tests.yml
vendored
2
.github/workflows/end_to_end_tests.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
mode: start
|
mode: start
|
||||||
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
||||||
ec2-image-id: ami-094dbcc53250a2480
|
ec2-image-id: ami-094dbcc53250a2480
|
||||||
ec2-instance-type: t3.xlarge
|
ec2-instance-type: m5.2xlarge
|
||||||
subnet-id: subnet-0ac40025f559df1bc
|
subnet-id: subnet-0ac40025f559df1bc
|
||||||
security-group-id: sg-0e36e96e3b8ed2d64
|
security-group-id: sg-0e36e96e3b8ed2d64
|
||||||
#iam-role-name: my-role-name # optional, requires additional permissions
|
#iam-role-name: my-role-name # optional, requires additional permissions
|
||||||
|
4
.github/workflows/push-to-npm.yml
vendored
4
.github/workflows/push-to-npm.yml
vendored
@ -43,6 +43,10 @@ jobs:
|
|||||||
run: ./templater.sh
|
run: ./templater.sh
|
||||||
working-directory: "front"
|
working-directory: "front"
|
||||||
|
|
||||||
|
- name: "Generate i18n files"
|
||||||
|
run: yarn run typesafe-i18n
|
||||||
|
working-directory: "front"
|
||||||
|
|
||||||
- name: "Build"
|
- name: "Build"
|
||||||
run: yarn run build-typings
|
run: yarn run build-typings
|
||||||
env:
|
env:
|
||||||
|
@ -107,3 +107,7 @@ $ LIVE_RELOAD=0 docker-compose up -d
|
|||||||
# Wait 2-3 minutes for the environment to start, then:
|
# Wait 2-3 minutes for the environment to start, then:
|
||||||
$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up
|
$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### A bad wording or a missing language
|
||||||
|
|
||||||
|
If you notice a translation error or missing language you can help us by following the [how to translate](docs/dev/how-to-translate.md) documentation.
|
||||||
|
@ -15,6 +15,9 @@ export class DebugController {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
|
if (ADMIN_API_TOKEN === "") {
|
||||||
|
return res.writeStatus("401 Unauthorized").end("No token configured!");
|
||||||
|
}
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIM
|
|||||||
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
||||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "";
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || "";
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import "jasmine";
|
import "jasmine";
|
||||||
import {PositionNotifier} from "../src/Model/PositionNotifier";
|
import { PositionNotifier } from "../src/Model/PositionNotifier";
|
||||||
import {User, UserSocket} from "../src/Model/User";
|
import { User, UserSocket } from "../src/Model/User";
|
||||||
import {Zone} from "_Model/Zone";
|
import { Zone } from "_Model/Zone";
|
||||||
import {Movable} from "_Model/Movable";
|
import { Movable } from "_Model/Movable";
|
||||||
import {PositionInterface} from "_Model/PositionInterface";
|
import { PositionInterface } from "_Model/PositionInterface";
|
||||||
import {ZoneSocket} from "../src/RoomManager";
|
import { ZoneSocket } from "../src/RoomManager";
|
||||||
|
|
||||||
|
|
||||||
describe("PositionNotifier", () => {
|
describe("PositionNotifier", () => {
|
||||||
it("should receive notifications when player moves", () => {
|
it("should receive notifications when player moves", () => {
|
||||||
@ -13,28 +12,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
|
300,
|
||||||
|
300,
|
||||||
|
(thing: Movable) => {
|
||||||
enterTriggered = true;
|
enterTriggered = true;
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
},
|
||||||
|
(thing: Movable, position: PositionInterface) => {
|
||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
}, () => {},
|
},
|
||||||
() => {});
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
|
1,
|
||||||
|
"test",
|
||||||
|
"10.0.0.2",
|
||||||
|
{
|
||||||
x: 500,
|
x: 500,
|
||||||
y: 500,
|
y: 500,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: "down",
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
|
2,
|
||||||
|
"test",
|
||||||
|
"10.0.0.2",
|
||||||
|
{
|
||||||
x: -9999,
|
x: -9999,
|
||||||
y: -9999,
|
y: -9999,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: "down",
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
||||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
||||||
@ -47,21 +77,21 @@ describe("PositionNotifier", () => {
|
|||||||
bottom: 500
|
bottom: 500
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
// Move inside the zone
|
// Move inside the zone
|
||||||
user2.setPosition({x:501, y:500, direction: 'down', moving: false});
|
user2.setPosition({ x: 501, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(true);
|
expect(moveTriggered).toBe(true);
|
||||||
moveTriggered = false;
|
moveTriggered = false;
|
||||||
|
|
||||||
// Move out of the zone in a zone that we don't track
|
// Move out of the zone in a zone that we don't track
|
||||||
user2.setPosition({x: 901, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 901, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(false);
|
expect(enterTriggered).toBe(false);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -69,7 +99,7 @@ describe("PositionNotifier", () => {
|
|||||||
leaveTriggered = false;
|
leaveTriggered = false;
|
||||||
|
|
||||||
// Move back in
|
// Move back in
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
expect(leaveTriggered).toBe(false);
|
expect(leaveTriggered).toBe(false);
|
||||||
@ -89,28 +119,59 @@ describe("PositionNotifier", () => {
|
|||||||
let moveTriggered = false;
|
let moveTriggered = false;
|
||||||
let leaveTriggered = false;
|
let leaveTriggered = false;
|
||||||
|
|
||||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable, fromZone: Zone|null ) => {
|
const positionNotifier = new PositionNotifier(
|
||||||
|
300,
|
||||||
|
300,
|
||||||
|
(thing: Movable, fromZone: Zone | null) => {
|
||||||
enterTriggered = true;
|
enterTriggered = true;
|
||||||
}, (thing: Movable, position: PositionInterface) => {
|
},
|
||||||
|
(thing: Movable, position: PositionInterface) => {
|
||||||
moveTriggered = true;
|
moveTriggered = true;
|
||||||
}, (thing: Movable) => {
|
},
|
||||||
|
(thing: Movable) => {
|
||||||
leaveTriggered = true;
|
leaveTriggered = true;
|
||||||
}, () => {},
|
},
|
||||||
() => {});
|
() => {},
|
||||||
|
() => {}
|
||||||
|
);
|
||||||
|
|
||||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
const user1 = new User(
|
||||||
|
1,
|
||||||
|
"test",
|
||||||
|
"10.0.0.2",
|
||||||
|
{
|
||||||
x: 500,
|
x: 500,
|
||||||
y: 500,
|
y: 500,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: "down",
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
const user2 = new User(
|
||||||
|
2,
|
||||||
|
"test",
|
||||||
|
"10.0.0.2",
|
||||||
|
{
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
moving: false,
|
moving: false,
|
||||||
direction: 'down'
|
direction: "down",
|
||||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
},
|
||||||
|
false,
|
||||||
|
positionNotifier,
|
||||||
|
{} as UserSocket,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
"foo",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const listener = {} as ZoneSocket;
|
const listener = {} as ZoneSocket;
|
||||||
positionNotifier.addZoneListener(listener, 0, 0);
|
positionNotifier.addZoneListener(listener, 0, 0);
|
||||||
@ -126,14 +187,12 @@ describe("PositionNotifier", () => {
|
|||||||
positionNotifier.enter(user1);
|
positionNotifier.enter(user1);
|
||||||
positionNotifier.enter(user2);
|
positionNotifier.enter(user2);
|
||||||
|
|
||||||
|
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
|
|
||||||
|
|
||||||
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
||||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||||
|
|
||||||
expect(enterTriggered).toBe(true);
|
expect(enterTriggered).toBe(true);
|
||||||
expect(moveTriggered).toBe(false);
|
expect(moveTriggered).toBe(false);
|
||||||
@ -184,4 +243,4 @@ describe("PositionNotifier", () => {
|
|||||||
enterTriggered = false;
|
enterTriggered = false;
|
||||||
//expect(newUsers.length).toBe(2);
|
//expect(newUsers.length).toBe(2);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
@ -935,9 +935,9 @@ flatted@^3.1.0:
|
|||||||
integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==
|
integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==
|
||||||
|
|
||||||
follow-redirects@^1.14.0:
|
follow-redirects@^1.14.0:
|
||||||
version "1.14.6"
|
version "1.14.7"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
|
||||||
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
|
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
|
||||||
|
|
||||||
fs-minipass@^2.0.0:
|
fs-minipass@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
@ -1504,9 +1504,9 @@ natural-compare@^1.4.0:
|
|||||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||||
|
|
||||||
node-fetch@^2.6.5:
|
node-fetch@^2.6.5:
|
||||||
version "2.6.6"
|
version "2.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||||
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==
|
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
whatwg-url "^5.0.0"
|
whatwg-url "^5.0.0"
|
||||||
|
|
||||||
|
@ -83,7 +83,8 @@
|
|||||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||||
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||||
"START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json"
|
"START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json",
|
||||||
|
"ICON_URL": "//icon-"+url,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uploader": {
|
"uploader": {
|
||||||
@ -109,7 +110,15 @@
|
|||||||
"redis": {
|
"redis": {
|
||||||
"image": "redis:6",
|
"image": "redis:6",
|
||||||
"ports": [6379]
|
"ports": [6379]
|
||||||
}
|
},
|
||||||
|
"iconserver": {
|
||||||
|
"image": "matthiasluedtke/iconserver:v3.13.0",
|
||||||
|
"host": {
|
||||||
|
"url": "icon-"+url,
|
||||||
|
"containerPort": 8080,
|
||||||
|
},
|
||||||
|
"ports": [8080]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
k8sextension(k8sConf)::
|
k8sextension(k8sConf)::
|
||||||
@ -210,6 +219,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
iconserver+: {
|
||||||
|
ingress+: {
|
||||||
|
spec+: {
|
||||||
|
tls+: [{
|
||||||
|
hosts: ["icon-"+url],
|
||||||
|
secretName: "certificate-tls"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ services:
|
|||||||
DEBUG: "socket:*"
|
DEBUG: "socket:*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
# wait for files generated by "messages" container to exists
|
# wait for files generated by "messages" container to exists
|
||||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
STARTUP_COMMAND_2: sleep 5; while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||||
@ -132,7 +132,7 @@ services:
|
|||||||
DEBUG: "*"
|
DEBUG: "*"
|
||||||
STARTUP_COMMAND_1: yarn install
|
STARTUP_COMMAND_1: yarn install
|
||||||
# wait for files generated by "messages" container to exists
|
# wait for files generated by "messages" container to exists
|
||||||
STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
STARTUP_COMMAND_2: sleep 5; while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
|
||||||
SECRET_KEY: yourSecretKey
|
SECRET_KEY: yourSecretKey
|
||||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||||
ALLOW_ARTILLERY: "true"
|
ALLOW_ARTILLERY: "true"
|
||||||
|
76
docs/dev/how-to-translate.md
Normal file
76
docs/dev/how-to-translate.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# How to translate WorkAdventure
|
||||||
|
|
||||||
|
We use the [typesafe-i18n](https://github.com/ivanhofer/typesafe-i18n) package to handle the translation.
|
||||||
|
|
||||||
|
## Add a new language
|
||||||
|
|
||||||
|
It is very easy to add a new language!
|
||||||
|
|
||||||
|
First, in the `front/src/i18n` folder create a new folder with the language code as name (the language code according to [RFC 5646](https://datatracker.ietf.org/doc/html/rfc5646)).
|
||||||
|
|
||||||
|
In the previously created folder, add a file named index.ts with the following content containing your language information (french from France in this example):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { Translation } from "../i18n-types";
|
||||||
|
|
||||||
|
const fr_FR: Translation = {
|
||||||
|
...en_US,
|
||||||
|
language: "Français",
|
||||||
|
country: "France",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default fr_FR;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add a new key
|
||||||
|
|
||||||
|
### Add a simple key
|
||||||
|
|
||||||
|
The keys are searched by a path through the properties of the sub-objects and it is therefore advisable to write your translation as a JavaScript object.
|
||||||
|
|
||||||
|
Please use kamelcase to name your keys!
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
messages: {
|
||||||
|
coffeMachine: {
|
||||||
|
start: "Coffe machine has been started!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the code you can translate using `$LL`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
|
console.log($LL.messages.coffeMachine.start());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a key with parameters
|
||||||
|
|
||||||
|
You can also use parameters to make the translation dynamic.
|
||||||
|
Use the tag { [parameter name] } to apply your parameters in the translations
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
messages: {
|
||||||
|
coffeMachine: {
|
||||||
|
playerStart: "{ playerName } started the coffee machine!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the code you can use it like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
$LL.messages.coffeMachine.playerStart.start({
|
||||||
|
playerName: "John",
|
||||||
|
});
|
||||||
|
```
|
@ -1,6 +1,32 @@
|
|||||||
{.section-title.accent.text-primary}
|
{.section-title.accent.text-primary}
|
||||||
|
|
||||||
# API Camera functions Reference
|
# API Camera functions Reference
|
||||||
|
|
||||||
|
### Start following player
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.camera.followPlayer(smooth: boolean): void
|
||||||
|
```
|
||||||
|
Set camera to follow the player. Set `smooth` to true for smooth transition.
|
||||||
|
|
||||||
|
### Set spot for camera to look at
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
WA.camera.set(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
lock: boolean = false,
|
||||||
|
smooth: boolean = false,
|
||||||
|
): void
|
||||||
|
```
|
||||||
|
|
||||||
|
Set camera to look at given spot.
|
||||||
|
Setting `width` and `height` will adjust zoom.
|
||||||
|
Set `lock` to true to lock camera in this position.
|
||||||
|
Set `smooth` to true for smooth transition.
|
||||||
|
|
||||||
### Listen to camera updates
|
### Listen to camera updates
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -52,17 +52,17 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
|
|||||||
### Opening/closing web page in Co-Websites
|
### Opening/closing web page in Co-Websites
|
||||||
|
|
||||||
```
|
```
|
||||||
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): Promise<CoWebsite>
|
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number, closable: boolean, lazy: boolean): Promise<CoWebsite>
|
||||||
```
|
```
|
||||||
|
|
||||||
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open.
|
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy
|
||||||
You can have only 5 co-wbesites open simultaneously.
|
it's to add the cowebsite but don't load it.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/');
|
const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/');
|
||||||
const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1);
|
const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1, true, true);
|
||||||
// ...
|
// ...
|
||||||
coWebsite.close();
|
coWebsite.close();
|
||||||
```
|
```
|
||||||
|
@ -36,6 +36,23 @@ WA.onInit().then(() => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get the player language
|
||||||
|
|
||||||
|
```
|
||||||
|
WA.player.language: string;
|
||||||
|
```
|
||||||
|
|
||||||
|
The current language of player is available from the `WA.player.language` property.
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
You need to wait for the end of the initialization before accessing `WA.player.language`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
WA.onInit().then(() => {
|
||||||
|
console.log('Player language: ', WA.player.language);
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
### Get the tags of the player
|
### Get the tags of the player
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -58,6 +75,27 @@ WA.onInit().then(() => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get the position of the player
|
||||||
|
```
|
||||||
|
WA.player.getPosition(): Promise<Position>
|
||||||
|
```
|
||||||
|
The player's current position is available using the `WA.player.getPosition()` function.
|
||||||
|
|
||||||
|
`Position` has the following attributes :
|
||||||
|
* **x (number) :** The coordinate x of the current player's position.
|
||||||
|
* **y (number) :** The coordinate y of the current player's position.
|
||||||
|
|
||||||
|
|
||||||
|
{.alert.alert-info}
|
||||||
|
You need to wait for the end of the initialization before calling `WA.player.getPosition()`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
WA.onInit().then(() => {
|
||||||
|
console.log('Position: ', WA.player.getPosition());
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Get the user-room token of the player
|
### Get the user-room token of the player
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -152,6 +190,37 @@ Example:
|
|||||||
WA.player.state.toto //will retrieve the variable
|
WA.player.state.toto //will retrieve the variable
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Move player to position
|
||||||
|
```typescript
|
||||||
|
WA.player.moveTo(x: number, y: number, speed?: number): Promise<{ x: number, y: number, cancelled: boolean }>;
|
||||||
|
```
|
||||||
|
Player will try to find shortest path to the destination point and proceed to move there.
|
||||||
|
```typescript
|
||||||
|
// Let's move player to x: 250 y: 250 with speed of 10
|
||||||
|
WA.player.moveTo(250, 250, 10);
|
||||||
|
```
|
||||||
|
You can also chain movement like this:
|
||||||
|
```typescript
|
||||||
|
// Player will move to the next point after reaching first one
|
||||||
|
await WA.player.moveTo(250, 250, 10);
|
||||||
|
await WA.player.moveTo(500, 0, 10);
|
||||||
|
```
|
||||||
|
Or like this:
|
||||||
|
```typescript
|
||||||
|
// Player will move to the next point after reaching first one or stop if the movement was cancelled
|
||||||
|
WA.player.moveTo(250, 250, 10).then((result) => {
|
||||||
|
if (!result.cancelled) {
|
||||||
|
WA.player.moveTo(500, 0, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
It is possible to get the information about current player's position on stop and if the movement was interrupted
|
||||||
|
```typescript
|
||||||
|
// Result will store x and y of Player at the moment of movement's end and information if the movement was interrupted
|
||||||
|
const result = await WA.player.moveTo(250, 250, 10);
|
||||||
|
// result: { x: number, y: number, cancelled: boolean }
|
||||||
|
```
|
||||||
|
|
||||||
### Set the outline color of the player
|
### Set the outline color of the player
|
||||||
```
|
```
|
||||||
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;
|
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;
|
||||||
|
@ -8,6 +8,7 @@ If you use group layers in your map, to reference a layer in a group you will ne
|
|||||||
together.
|
together.
|
||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="images/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/groupLayer.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
@ -16,10 +17,10 @@ Example :
|
|||||||
|
|
||||||
The name of the layers of this map are :
|
The name of the layers of this map are :
|
||||||
|
|
||||||
* `entries/start`
|
- `entries/start`
|
||||||
* `bottom/ground/under`
|
- `bottom/ground/under`
|
||||||
* `bottom/build/carpet`
|
- `bottom/build/carpet`
|
||||||
* `wall`
|
- `wall`
|
||||||
|
|
||||||
### Detecting when the user enters/leaves a layer
|
### Detecting when the user enters/leaves a layer
|
||||||
|
|
||||||
@ -30,17 +31,18 @@ WA.room.onLeaveLayer(name: string): Subscription
|
|||||||
|
|
||||||
Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
|
Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
|
||||||
|
|
||||||
* **name**: the name of the layer who as defined in Tiled.
|
- **name**: the name of the layer who as defined in Tiled.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
WA.room.onEnterLayer('myLayer').subscribe(() => {
|
const myLayerSubscriber = WA.room.onEnterLayer("myLayer").subscribe(() => {
|
||||||
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
|
WA.chat.sendChatMessage("Hello!", "Mr Robot");
|
||||||
});
|
});
|
||||||
|
|
||||||
WA.room.onLeaveLayer('myLayer').subscribe(() => {
|
WA.room.onLeaveLayer("myLayer").subscribe(() => {
|
||||||
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
|
WA.chat.sendChatMessage("Goodbye!", "Mr Robot");
|
||||||
|
myLayerSubscriber.unsubscribe();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -56,10 +58,10 @@ layer in that group layer.
|
|||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
WA.room.showLayer('bottom');
|
WA.room.showLayer("bottom");
|
||||||
//...
|
//...
|
||||||
WA.room.hideLayer('bottom');
|
WA.room.hideLayer("bottom");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set/Create properties in a layer
|
### Set/Create properties in a layer
|
||||||
@ -76,8 +78,8 @@ To unset a property from a layer, use `setProperty` with `propertyValue` set to
|
|||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/');
|
WA.room.setProperty("wikiLayer", "openWebsite", "https://www.wikipedia.org/");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get the room id
|
### Get the room id
|
||||||
@ -92,9 +94,9 @@ The ID of the current room is available from the `WA.room.id` property.
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
WA.onInit().then(() => {
|
WA.onInit().then(() => {
|
||||||
console.log('Room id: ', WA.room.id);
|
console.log("Room id: ", WA.room.id);
|
||||||
// Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json"
|
// Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json"
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Get the map URL
|
### Get the map URL
|
||||||
@ -109,9 +111,9 @@ The URL of the map is available from the `WA.room.mapURL` property.
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
WA.onInit().then(() => {
|
WA.onInit().then(() => {
|
||||||
console.log('Map URL: ', WA.room.mapURL);
|
console.log("Map URL: ", WA.room.mapURL);
|
||||||
// Will output something like: 'https://mymap.org/map.json"
|
// Will output something like: 'https://mymap.org/map.json"
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Getting map data
|
### Getting map data
|
||||||
@ -122,7 +124,7 @@ WA.room.getTiledMap(): Promise<ITiledMap>
|
|||||||
|
|
||||||
Returns a promise that resolves to the JSON map file.
|
Returns a promise that resolves to the JSON map file.
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
const map = await WA.room.getTiledMap();
|
const map = await WA.room.getTiledMap();
|
||||||
console.log("Map generated with Tiled version ", map.tiledversion);
|
console.log("Map generated with Tiled version ", map.tiledversion);
|
||||||
```
|
```
|
||||||
@ -140,6 +142,7 @@ WA.room.setTiles(tiles: TileDescriptor[]): void
|
|||||||
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`.
|
||||||
|
|
||||||
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
If `tile` is a string, it's not the id of the tile but the value of the property `name`.
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<img src="images/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
<img src="images/nameIndexProperty.png" class="figure-img img-fluid rounded" alt="" />
|
||||||
@ -148,10 +151,10 @@ If `tile` is a string, it's not the id of the tile but the value of the property
|
|||||||
|
|
||||||
`TileDescriptor` has the following attributes :
|
`TileDescriptor` has the following attributes :
|
||||||
|
|
||||||
* **x (number) :** The coordinate x of the tile that you want to replace.
|
- **x (number) :** The coordinate x of the tile that you want to replace.
|
||||||
* **y (number) :** The coordinate y of the tile that you want to replace.
|
- **y (number) :** The coordinate y of the tile that you want to replace.
|
||||||
* **tile (number | string) :** The id of the tile that will be placed in the map.
|
- **tile (number | string) :** The id of the tile that will be placed in the map.
|
||||||
* **layer (string) :** The name of the layer where the tile will be placed.
|
- **layer (string) :** The name of the layer where the tile will be placed.
|
||||||
|
|
||||||
**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want
|
**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want
|
||||||
to the id of the tile in Tiled Editor.
|
to the id of the tile in Tiled Editor.
|
||||||
@ -160,12 +163,12 @@ Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
|
|||||||
|
|
||||||
Example :
|
Example :
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
WA.room.setTiles([
|
WA.room.setTiles([
|
||||||
{ x: 6, y: 4, tile: 'blue', layer: 'setTiles' },
|
{ x: 6, y: 4, tile: "blue", layer: "setTiles" },
|
||||||
{ x: 7, y: 4, tile: 109, layer: 'setTiles' },
|
{ x: 7, y: 4, tile: 109, layer: "setTiles" },
|
||||||
{ x: 8, y: 4, tile: 109, layer: 'setTiles' },
|
{ x: 8, y: 4, tile: 109, layer: "setTiles" },
|
||||||
{ x: 9, y: 4, tile: 'blue', layer: 'setTiles' }
|
{ x: 9, y: 4, tile: "blue", layer: "setTiles" },
|
||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -179,10 +182,10 @@ Load a tileset in JSON format from an url and return the id of the first tile of
|
|||||||
|
|
||||||
You can create a tileset file in Tile Editor.
|
You can create a tileset file in Tile Editor.
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => {
|
||||||
WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: 'bottom' }]);
|
WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: "bottom" }]);
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Embedding websites in a map
|
## Embedding websites in a map
|
||||||
@ -199,10 +202,10 @@ WA.room.website.get(objectName: string): Promise<EmbeddedWebsite>
|
|||||||
You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of
|
You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of
|
||||||
an `EmbeddedWebsite` instance.
|
an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map)
|
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map)
|
||||||
const website = await WA.room.website.get('my_website');
|
const website = await WA.room.website.get("my_website");
|
||||||
website.url = 'https://example.com';
|
website.url = "https://example.com";
|
||||||
website.visible = true;
|
website.visible = true;
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -231,7 +234,7 @@ interface CreateEmbeddedWebsiteEvent {
|
|||||||
You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns
|
You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns
|
||||||
an `EmbeddedWebsite` instance.
|
an `EmbeddedWebsite` instance.
|
||||||
|
|
||||||
```javascript
|
```ts
|
||||||
// Create a new website object
|
// Create a new website object
|
||||||
const website = WA.room.website.create({
|
const website = WA.room.website.create({
|
||||||
name: "my_website",
|
name: "my_website",
|
||||||
@ -282,4 +285,3 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom
|
|||||||
|
|
||||||
{.alert.alert-warning} The websites you add/edit/delete via the scripting API are only shown locally. If you want them
|
{.alert.alert-warning} The websites you add/edit/delete via the scripting API are only shown locally. If you want them
|
||||||
to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users.
|
to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users.
|
||||||
|
|
||||||
|
@ -65,3 +65,24 @@ How to use entry point :
|
|||||||
|
|
||||||
* To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`".
|
* To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`".
|
||||||
* You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)
|
* You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL)
|
||||||
|
|
||||||
|
## Defining destination point with moveTo parameter
|
||||||
|
|
||||||
|
We are able to direct a Woka to the desired place immediately after spawn. To make users spawn on an entry point and then, walk automatically to a meeting room, simply add `moveTo` as an additional parameter of URL:
|
||||||
|
|
||||||
|
*Use default entry point*
|
||||||
|
```
|
||||||
|
.../my_map.json#&moveTo=exit
|
||||||
|
```
|
||||||
|
*Define entry point and moveTo parameter like this...*
|
||||||
|
```
|
||||||
|
.../my_map.json#start&moveTo=meeting-room
|
||||||
|
```
|
||||||
|
*...or like this*
|
||||||
|
```
|
||||||
|
.../my_map.json#moveTo=meeting-room&start
|
||||||
|
```
|
||||||
|
|
||||||
|
For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected.
|
||||||
|
|
||||||
|
![](images/moveTo-layer-example.png)
|
BIN
docs/maps/images/moveTo-layer-example.png
Normal file
BIN
docs/maps/images/moveTo-layer-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -8,7 +8,7 @@ module.exports = {
|
|||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/eslint-recommended",
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
],
|
],
|
||||||
"globals": {
|
"globals": {
|
||||||
"Atomics": "readonly",
|
"Atomics": "readonly",
|
||||||
@ -23,7 +23,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@typescript-eslint",
|
"@typescript-eslint",
|
||||||
"svelte3"
|
"svelte3",
|
||||||
],
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@ -33,6 +33,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
"eol-last": ["error", "always"],
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"no-throw-literal": "error",
|
"no-throw-literal": "error",
|
||||||
// TODO: remove those ignored rules and write a stronger code!
|
// TODO: remove those ignored rules and write a stronger code!
|
||||||
|
1
front/.gitignore
vendored
1
front/.gitignore
vendored
@ -6,6 +6,7 @@
|
|||||||
/dist/main.*.css.map
|
/dist/main.*.css.map
|
||||||
/dist/tests/
|
/dist/tests/
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
|
/package-lock.json
|
||||||
/dist/webpack.config.js
|
/dist/webpack.config.js
|
||||||
/dist/webpack.config.js.map
|
/dist/webpack.config.js.map
|
||||||
/dist/src
|
/dist/src
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
src/Messages/generated
|
src/Messages/generated
|
||||||
src/Messages/JsonMessages
|
src/Messages/JsonMessages
|
||||||
|
src/i18n/i18n-svelte.ts
|
||||||
|
src/i18n/i18n-types.ts
|
||||||
|
src/i18n/i18n-util.ts
|
||||||
|
5
front/.typesafe-i18n.json
Normal file
5
front/.typesafe-i18n.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://unpkg.com/typesafe-i18n@2.59.0/schema/typesafe-i18n.json",
|
||||||
|
"baseLocale": "en-US",
|
||||||
|
"adapter": "svelte"
|
||||||
|
}
|
@ -1,23 +1,35 @@
|
|||||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder
|
||||||
WORKDIR /usr/src
|
|
||||||
|
WORKDIR /usr/src/messages
|
||||||
COPY messages .
|
COPY messages .
|
||||||
RUN yarn install && yarn ts-proto
|
RUN yarn install && yarn ts-proto
|
||||||
|
|
||||||
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
|
WORKDIR /usr/src/front
|
||||||
FROM thecodingmachine/nodejs:14-apache
|
COPY front .
|
||||||
|
|
||||||
COPY --chown=docker:docker front .
|
# move messages to front
|
||||||
COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated
|
RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated
|
||||||
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts
|
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts
|
||||||
COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages
|
RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages
|
||||||
|
|
||||||
|
RUN yarn install && yarn run typesafe-i18n && yarn build
|
||||||
|
|
||||||
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
# Removing the iframe.html file from the final image as this adds a XSS attack.
|
||||||
# iframe.html is only in dev mode to circumvent a limitation
|
# iframe.html is only in dev mode to circumvent a limitation
|
||||||
RUN rm dist/iframe.html
|
RUN rm dist/iframe.html
|
||||||
|
|
||||||
RUN yarn install
|
FROM thecodingmachine/nodejs:14-apache
|
||||||
|
|
||||||
|
COPY --from=builder --chown=docker:docker /usr/src/front/dist dist
|
||||||
|
COPY front/templater.sh .
|
||||||
|
|
||||||
|
USER root
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
||||||
|
&& apt-get install -y \
|
||||||
|
gettext-base \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
USER docker
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV STARTUP_COMMAND_0="./templater.sh"
|
ENV STARTUP_COMMAND_0="./templater.sh"
|
||||||
ENV STARTUP_COMMAND_1="yarn run build"
|
ENV STARTUP_COMMAND_1="envsubst < dist/env-config.template.js > dist/env-config.js"
|
||||||
ENV APACHE_DOCUMENT_ROOT=dist/
|
ENV APACHE_DOCUMENT_ROOT=dist/
|
||||||
|
4
front/dist/.gitignore
vendored
4
front/dist/.gitignore
vendored
@ -1,4 +1,6 @@
|
|||||||
index.html
|
index.html
|
||||||
index.tmpl.html.tmp
|
|
||||||
/js/
|
/js/
|
||||||
|
/fonts/
|
||||||
style.*.css
|
style.*.css
|
||||||
|
!env-config.template.js
|
||||||
|
*.png
|
||||||
|
27
front/dist/env-config.template.js
vendored
Normal file
27
front/dist/env-config.template.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
window.env = {
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS: '${SKIP_RENDER_OPTIMIZATIONS}',
|
||||||
|
DISABLE_NOTIFICATIONS: '${DISABLE_NOTIFICATIONS}',
|
||||||
|
PUSHER_URL: '${PUSHER_URL}',
|
||||||
|
UPLOADER_URL: '${UPLOADER_URL}',
|
||||||
|
ADMIN_URL: '${ADMIN_URL}',
|
||||||
|
CONTACT_URL: '${CONTACT_URL}',
|
||||||
|
PROFILE_URL: '${PROFILE_URL}',
|
||||||
|
ICON_URL: '${ICON_URL}',
|
||||||
|
DEBUG_MODE: '${DEBUG_MODE}',
|
||||||
|
STUN_SERVER: '${STUN_SERVER}',
|
||||||
|
TURN_SERVER: '${TURN_SERVER}',
|
||||||
|
TURN_USER: '${TURN_USER}',
|
||||||
|
TURN_PASSWORD: '${TURN_PASSWORD}',
|
||||||
|
JITSI_URL: '${JITSI_URL}',
|
||||||
|
JITSI_PRIVATE_MODE: '${JITSI_PRIVATE_MODE}',
|
||||||
|
START_ROOM_URL: '${START_ROOM_URL}',
|
||||||
|
MAX_USERNAME_LENGTH: '${MAX_USERNAME_LENGTH}',
|
||||||
|
MAX_PER_GROUP: '${MAX_PER_GROUP}',
|
||||||
|
DISPLAY_TERMS_OF_USE: '${DISPLAY_TERMS_OF_USE}',
|
||||||
|
POSTHOG_API_KEY: '${POSTHOG_API_KEY}',
|
||||||
|
POSTHOG_URL: '${POSTHOG_URL}',
|
||||||
|
NODE_ENV: '${NODE_ENV}',
|
||||||
|
DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}',
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}',
|
||||||
|
FALLBACK_LOCALE: '${FALLBACK_LOCALE}',
|
||||||
|
};
|
68
front/dist/index.ejs
vendored
Normal file
68
front/dist/index.ejs
vendored
Normal file
File diff suppressed because one or more lines are too long
126
front/dist/index.tmpl.html
vendored
126
front/dist/index.tmpl.html
vendored
@ -1,126 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
|
|
||||||
<!-- TRACK CODE -->
|
|
||||||
<!-- END TRACK CODE -->
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="static/images/favicons/manifest.json">
|
|
||||||
<meta name="msapplication-TileColor" content="#000000">
|
|
||||||
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
|
|
||||||
<meta name="theme-color" content="#000000">
|
|
||||||
|
|
||||||
|
|
||||||
<base href="/">
|
|
||||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
|
||||||
|
|
||||||
<title>WorkAdventure</title>
|
|
||||||
</head>
|
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
|
||||||
<div class="main-container" id="main-container">
|
|
||||||
<!-- Create the editor container -->
|
|
||||||
<div id="game" class="game">
|
|
||||||
<div id="cowebsite-container">
|
|
||||||
<div id="cowebsite-container-main">
|
|
||||||
<div id="cowebsite-slot-1">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-container-sub">
|
|
||||||
<div id="cowebsite-slot-2">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-slot-3">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-slot-4">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="svelte-overlay"></div>
|
|
||||||
<div id="game-overlay" class="game-overlay">
|
|
||||||
<div id="main-section" class="main-section">
|
|
||||||
</div>
|
|
||||||
<aside id="sidebar" class="sidebar">
|
|
||||||
</aside>
|
|
||||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite" class="cowebsite hidden">
|
|
||||||
<aside id="cowebsite-aside" class="noselect">
|
|
||||||
<div id="cowebsite-aside-buttons">
|
|
||||||
<button class="top-right-btn nes-btn is-error" id="cowebsite-close" alt="close all co-websites">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
<button class="top-right-btn nes-btn is-primary" id="cowebsite-fullscreen" alt="fullscreen mode">
|
|
||||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
|
||||||
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-aside-holder">
|
|
||||||
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-sub-icons"></div>
|
|
||||||
</aside>
|
|
||||||
<main id="cowebsite-slot-0"></main>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-buffer"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
|
||||||
</div>
|
|
||||||
<audio id="report-message">
|
|
||||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
|
||||||
</audio>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
1
front/dist/resources/logos/cowebsite-swipe.svg
vendored
Normal file
1
front/dist/resources/logos/cowebsite-swipe.svg
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><defs><image width="12" height="14" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAOAQMAAAAhc2+vAAAAAXNSR0IB2cksfwAAAAZQTFRFAAAA////pdmf3QAAAAJ0Uk5TAP9bkSK1AAAAJUlEQVR4nGNgOMDAoMDw/wMDCDgwMDQwQIAASBgE/j8ACRswAACLjwYPIknTggAAAABJRU5ErkJggg=="/><image width="12" height="12" id="img2" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMAgMAAAArG7R0AAAAAXNSR0IB2cksfwAAAAlQTFRFAAAA/////wAAzV63nAAAAAN0Uk5TAP8BHlUJOgAAACJJREFUeJxjYGAQYGBgYGEIDQ0F0xA+BwMDEi80NASqigEAOD8CVqGVgwsAAAAASUVORK5CYII="/></defs><style></style><use href="#img1" x="2" y="1" /><use href="#img2" x="2" y="2" /></svg>
|
After Width: | Height: | Size: 717 B |
1
front/dist/resources/translations/.gitignore
vendored
Normal file
1
front/dist/resources/translations/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.json
|
@ -15,6 +15,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
|
"css-minimizer-webpack-plugin": "^3.3.1",
|
||||||
"eslint": "^8.4.1",
|
"eslint": "^8.4.1",
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-plugin-svelte3": "^3.2.1",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||||
@ -46,8 +47,10 @@
|
|||||||
"@types/simple-peer": "^9.11.1",
|
"@types/simple-peer": "^9.11.1",
|
||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
|
"cancelable-promise": "^4.2.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"deep-copy-ts": "^0.5.0",
|
"deep-copy-ts": "^0.5.0",
|
||||||
|
"easystarjs": "^0.4.4",
|
||||||
"generic-type-guard": "^3.2.0",
|
"generic-type-guard": "^3.2.0",
|
||||||
"google-protobuf": "^3.13.0",
|
"google-protobuf": "^3.13.0",
|
||||||
"phaser": "^3.54.0",
|
"phaser": "^3.54.0",
|
||||||
@ -63,10 +66,12 @@
|
|||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"standardized-audio-context": "^25.2.4",
|
"standardized-audio-context": "^25.2.4",
|
||||||
"ts-proto": "^1.96.0",
|
"ts-proto": "^1.96.0",
|
||||||
"uuidv4": "^6.2.10"
|
"typesafe-i18n": "^2.59.0",
|
||||||
|
"uuidv4": "^6.2.10",
|
||||||
|
"zod": "^3.11.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "run-p templater serve svelte-check-watch",
|
"start": "run-p templater serve svelte-check-watch typesafe-i18n",
|
||||||
"templater": "cross-env ./templater.sh",
|
"templater": "cross-env ./templater.sh",
|
||||||
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||||
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||||
@ -78,7 +83,8 @@
|
|||||||
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch",
|
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch",
|
||||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"",
|
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"",
|
||||||
"pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'",
|
"pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'",
|
||||||
"pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'"
|
"pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'",
|
||||||
|
"typesafe-i18n": "typesafe-i18n --no-watch"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.svelte": [
|
"*.svelte": [
|
||||||
|
11
front/src/Api/Events/CameraFollowPlayerEvent.ts
Normal file
11
front/src/Api/Events/CameraFollowPlayerEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isCameraFollowPlayerEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
smooth: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to make the camera follow player.
|
||||||
|
*/
|
||||||
|
export type CameraFollowPlayerEvent = tg.GuardedType<typeof isCameraFollowPlayerEvent>;
|
16
front/src/Api/Events/CameraSetEvent.ts
Normal file
16
front/src/Api/Events/CameraSetEvent.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isCameraSetEvent = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
width: tg.isOptional(tg.isNumber),
|
||||||
|
height: tg.isOptional(tg.isNumber),
|
||||||
|
lock: tg.isBoolean,
|
||||||
|
smooth: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
/**
|
||||||
|
* A message sent from the iFrame to the game to change the camera position.
|
||||||
|
*/
|
||||||
|
export type CameraSetEvent = tg.GuardedType<typeof isCameraSetEvent>;
|
@ -5,12 +5,13 @@ export const isGameStateEvent = new tg.IsInterface()
|
|||||||
roomId: tg.isString,
|
roomId: tg.isString,
|
||||||
mapUrl: tg.isString,
|
mapUrl: tg.isString,
|
||||||
nickname: tg.isString,
|
nickname: tg.isString,
|
||||||
|
language: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
uuid: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
startLayerName: tg.isUnion(tg.isString, tg.isNull),
|
||||||
tags: tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
variables: tg.isObject,
|
variables: tg.isObject,
|
||||||
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
|
|
||||||
playerVariables: tg.isObject,
|
playerVariables: tg.isObject,
|
||||||
|
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
/**
|
/**
|
||||||
|
@ -28,10 +28,14 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent";
|
|||||||
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
|
||||||
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
|
||||||
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
||||||
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
|
||||||
import { isColorEvent } from "./ColorEvent";
|
|
||||||
import { isPlayerPosition } from "./PlayerPosition";
|
import { isPlayerPosition } from "./PlayerPosition";
|
||||||
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
||||||
|
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
||||||
|
import type { CameraSetEvent } from "./CameraSetEvent";
|
||||||
|
import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
||||||
|
import { isColorEvent } from "./ColorEvent";
|
||||||
|
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
|
||||||
|
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
|
||||||
|
|
||||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||||
data: T;
|
data: T;
|
||||||
@ -43,6 +47,8 @@ export interface TypedMessageEvent<T> extends MessageEvent {
|
|||||||
export type IframeEventMap = {
|
export type IframeEventMap = {
|
||||||
loadPage: LoadPageEvent;
|
loadPage: LoadPageEvent;
|
||||||
chat: ChatEvent;
|
chat: ChatEvent;
|
||||||
|
cameraFollowPlayer: CameraFollowPlayerEvent;
|
||||||
|
cameraSet: CameraSetEvent;
|
||||||
openPopup: OpenPopupEvent;
|
openPopup: OpenPopupEvent;
|
||||||
closePopup: ClosePopupEvent;
|
closePopup: ClosePopupEvent;
|
||||||
openTab: OpenTabEvent;
|
openTab: OpenTabEvent;
|
||||||
@ -169,6 +175,10 @@ export const iframeQueryMapTypeGuards = {
|
|||||||
query: tg.isUndefined,
|
query: tg.isUndefined,
|
||||||
answer: isPlayerPosition,
|
answer: isPlayerPosition,
|
||||||
},
|
},
|
||||||
|
movePlayerTo: {
|
||||||
|
query: isMovePlayerToEventConfig,
|
||||||
|
answer: isMovePlayerToEventAnswer,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||||
|
11
front/src/Api/Events/MovePlayerToEvent.ts
Normal file
11
front/src/Api/Events/MovePlayerToEvent.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isMovePlayerToEventConfig = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
speed: tg.isOptional(tg.isNumber),
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type MovePlayerToEvent = tg.GuardedType<typeof isMovePlayerToEventConfig>;
|
11
front/src/Api/Events/MovePlayerToEventAnswer.ts
Normal file
11
front/src/Api/Events/MovePlayerToEventAnswer.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as tg from "generic-type-guard";
|
||||||
|
|
||||||
|
export const isMovePlayerToEventAnswer = new tg.IsInterface()
|
||||||
|
.withProperties({
|
||||||
|
x: tg.isNumber,
|
||||||
|
y: tg.isNumber,
|
||||||
|
cancelled: tg.isBoolean,
|
||||||
|
})
|
||||||
|
.get();
|
||||||
|
|
||||||
|
export type MovePlayerToEventAnswer = tg.GuardedType<typeof isMovePlayerToEventAnswer>;
|
@ -6,13 +6,14 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface()
|
|||||||
allowApi: tg.isOptional(tg.isBoolean),
|
allowApi: tg.isOptional(tg.isBoolean),
|
||||||
allowPolicy: tg.isOptional(tg.isString),
|
allowPolicy: tg.isOptional(tg.isString),
|
||||||
position: tg.isOptional(tg.isNumber),
|
position: tg.isOptional(tg.isNumber),
|
||||||
|
closable: tg.isOptional(tg.isBoolean),
|
||||||
|
lazy: tg.isOptional(tg.isBoolean),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
export const isCoWebsite = new tg.IsInterface()
|
export const isCoWebsite = new tg.IsInterface()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
id: tg.isString,
|
id: tg.isString,
|
||||||
position: tg.isNumber,
|
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
|||||||
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||||
import { scriptUtils } from "./ScriptUtils";
|
import { scriptUtils } from "./ScriptUtils";
|
||||||
import { isGoToPageEvent } from "./Events/GoToPageEvent";
|
import { isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||||
import { isCloseCoWebsite, CloseCoWebsiteEvent } from "./Events/CloseCoWebsiteEvent";
|
|
||||||
import {
|
import {
|
||||||
IframeErrorAnswerEvent,
|
IframeErrorAnswerEvent,
|
||||||
IframeQueryMap,
|
IframeQueryMap,
|
||||||
@ -33,6 +32,8 @@ import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Store
|
|||||||
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
|
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
|
||||||
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
||||||
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
||||||
|
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent";
|
||||||
|
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent";
|
||||||
|
|
||||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||||
query: IframeQueryMap[T]["query"],
|
query: IframeQueryMap[T]["query"],
|
||||||
@ -56,6 +57,12 @@ class IframeListener {
|
|||||||
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
|
||||||
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _cameraSetStream: Subject<CameraSetEvent> = new Subject();
|
||||||
|
public readonly cameraSetStream = this._cameraSetStream.asObservable();
|
||||||
|
|
||||||
|
private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject();
|
||||||
|
public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable();
|
||||||
|
|
||||||
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||||
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||||
|
|
||||||
@ -202,6 +209,10 @@ class IframeListener {
|
|||||||
this._hideLayerStream.next(payload.data);
|
this._hideLayerStream.next(payload.data);
|
||||||
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||||
this._setPropertyStream.next(payload.data);
|
this._setPropertyStream.next(payload.data);
|
||||||
|
} else if (payload.type === "cameraSet" && isCameraSetEvent(payload.data)) {
|
||||||
|
this._cameraSetStream.next(payload.data);
|
||||||
|
} else if (payload.type === "cameraFollowPlayer" && isCameraFollowPlayerEvent(payload.data)) {
|
||||||
|
this._cameraFollowPlayerStream.next(payload.data);
|
||||||
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||||
scriptUtils.sendAnonymousChat(payload.data);
|
scriptUtils.sendAnonymousChat(payload.data);
|
||||||
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { coWebsiteManager, CoWebsite } from "../WebRtc/CoWebsiteManager";
|
|
||||||
import { playersStore } from "../Stores/PlayersStore";
|
import { playersStore } from "../Stores/PlayersStore";
|
||||||
import { chatMessagesStore } from "../Stores/ChatStore";
|
import { chatMessagesStore } from "../Stores/ChatStore";
|
||||||
import type { ChatEvent } from "./Events/ChatEvent";
|
import type { ChatEvent } from "./Events/ChatEvent";
|
||||||
|
@ -17,6 +17,27 @@ export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdven
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public set(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
lock: boolean = false,
|
||||||
|
smooth: boolean = false
|
||||||
|
): void {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "cameraSet",
|
||||||
|
data: { x, y, width, height, lock, smooth },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public followPlayer(smooth: boolean = false): void {
|
||||||
|
sendToWorkadventure({
|
||||||
|
type: "cameraFollowPlayer",
|
||||||
|
data: { smooth },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
||||||
sendToWorkadventure({
|
sendToWorkadventure({
|
||||||
type: "onCameraUpdate",
|
type: "onCameraUpdate",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import type { ChatEvent } from "../Events/ChatEvent";
|
|
||||||
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
import { isUserInputChatEvent, UserInputChatEvent } from "../Events/UserInputChatEvent";
|
||||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||||
import { apiCallback } from "./registeredCallbacks";
|
import { apiCallback } from "./registeredCallbacks";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
|
||||||
|
|
||||||
export class CoWebsite {
|
export class CoWebsite {
|
||||||
constructor(private readonly id: string, public readonly position: number) {}
|
constructor(private readonly id: string) {}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
return queryWorkadventure({
|
return queryWorkadventure({
|
||||||
@ -41,7 +41,14 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openCoWebSite(url: string, allowApi?: boolean, allowPolicy?: string, position?: number): Promise<CoWebsite> {
|
async openCoWebSite(
|
||||||
|
url: string,
|
||||||
|
allowApi?: boolean,
|
||||||
|
allowPolicy?: string,
|
||||||
|
position?: number,
|
||||||
|
closable?: boolean,
|
||||||
|
lazy?: boolean
|
||||||
|
): Promise<CoWebsite> {
|
||||||
const result = await queryWorkadventure({
|
const result = await queryWorkadventure({
|
||||||
type: "openCoWebsite",
|
type: "openCoWebsite",
|
||||||
data: {
|
data: {
|
||||||
@ -49,9 +56,11 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
|||||||
allowApi,
|
allowApi,
|
||||||
allowPolicy,
|
allowPolicy,
|
||||||
position,
|
position,
|
||||||
|
closable,
|
||||||
|
lazy,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return new CoWebsite(result.id, result.position);
|
return new CoWebsite(result.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCoWebSites(): Promise<CoWebsite[]> {
|
async getCoWebSites(): Promise<CoWebsite[]> {
|
||||||
@ -59,7 +68,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
|||||||
type: "getCoWebsites",
|
type: "getCoWebsites",
|
||||||
data: undefined,
|
data: undefined,
|
||||||
});
|
});
|
||||||
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position));
|
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,6 +13,12 @@ export const setPlayerName = (name: string) => {
|
|||||||
playerName = name;
|
playerName = name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let playerLanguage: string | undefined;
|
||||||
|
|
||||||
|
export const setPlayerLanguage = (language: string | undefined) => {
|
||||||
|
playerLanguage = language;
|
||||||
|
};
|
||||||
|
|
||||||
let tags: string[] | undefined;
|
let tags: string[] | undefined;
|
||||||
|
|
||||||
export const setTags = (_tags: string[]) => {
|
export const setTags = (_tags: string[]) => {
|
||||||
@ -61,6 +67,15 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
|||||||
return playerName;
|
return playerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get language(): string {
|
||||||
|
if (playerLanguage === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
"Player language not initialized yet. You should call WA.player.language within a WA.onInit callback."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return playerLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
get tags(): string[] {
|
get tags(): string[] {
|
||||||
if (tags === undefined) {
|
if (tags === undefined) {
|
||||||
throw new Error("Tags not initialized yet. You should call WA.player.tags within a WA.onInit callback.");
|
throw new Error("Tags not initialized yet. You should call WA.player.tags within a WA.onInit callback.");
|
||||||
@ -84,6 +99,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async moveTo(x: number, y: number, speed?: number): Promise<{ x: number; y: number; cancelled: boolean }> {
|
||||||
|
return await queryWorkadventure({
|
||||||
|
type: "movePlayerTo",
|
||||||
|
data: { x, y, speed },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get userRoomToken(): string | undefined {
|
get userRoomToken(): string | undefined {
|
||||||
if (userRoomToken === undefined) {
|
if (userRoomToken === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -1,166 +1,52 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
|
||||||
import { menuIconVisiblilityStore, menuVisiblilityStore } from "../Stores/MenuStore";
|
|
||||||
import { emoteMenuStore } from "../Stores/EmoteStore";
|
|
||||||
import { enableCameraSceneVisibilityStore } 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 Chat from "./Chat/Chat.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 type { Game } from "../Phaser/Game/Game";
|
||||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||||
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
||||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
import { errorStore } from "../Stores/ErrorStore";
|
||||||
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||||
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||||
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
import { selectCompanionSceneVisibleStore } from "../Stores/SelectCompanionStore";
|
||||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
import Chat from "./Chat/Chat.svelte";
|
||||||
|
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||||
|
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||||
|
import LoginScene from "./Login/LoginScene.svelte";
|
||||||
|
import MainLayout from "./MainLayout.svelte";
|
||||||
|
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||||
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
import Menu from "./Menu/Menu.svelte";
|
|
||||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
|
||||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
|
||||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
|
||||||
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
|
||||||
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
|
||||||
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
|
||||||
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
|
||||||
import { warningContainerStore } from "../Stores/MenuStore";
|
|
||||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
|
||||||
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
|
||||||
import LayoutManager from "./LayoutManager/LayoutManager.svelte";
|
|
||||||
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
|
||||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
|
||||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
|
||||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
|
||||||
import { followStateStore } from "../Stores/FollowStore";
|
|
||||||
import { peerStore } from "../Stores/PeerStore";
|
|
||||||
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
{#if $errorStore.length > 0}
|
||||||
{#if $loginSceneVisibleStore}
|
|
||||||
<div class="scrollable">
|
|
||||||
<LoginScene {game} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $selectCharacterSceneVisibleStore}
|
|
||||||
<div>
|
|
||||||
<SelectCharacterScene {game} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $customCharacterSceneVisibleStore}
|
|
||||||
<div>
|
|
||||||
<CustomCharacterScene {game} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $selectCompanionSceneVisibleStore}
|
|
||||||
<div>
|
|
||||||
<SelectCompanionScene {game} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $enableCameraSceneVisibilityStore}
|
|
||||||
<div class="scrollable">
|
|
||||||
<EnableCameraScene {game} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $banMessageStore.length > 0}
|
|
||||||
<div>
|
|
||||||
<BanMessageContainer />
|
|
||||||
</div>
|
|
||||||
{:else if $textMessageStore.length > 0}
|
|
||||||
<div>
|
|
||||||
<TextMessageContainer />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $soundPlayingStore}
|
|
||||||
<div>
|
|
||||||
<AudioPlaying url={$soundPlayingStore} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $audioManagerVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<AudioManager />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $layoutManagerVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<LayoutManager />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showReportScreenStore !== userReportEmpty}
|
|
||||||
<div>
|
|
||||||
<ReportMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
|
||||||
<div>
|
|
||||||
<FollowMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $menuIconVisiblilityStore}
|
|
||||||
<div>
|
|
||||||
<MenuIcon />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $menuVisiblilityStore}
|
|
||||||
<div>
|
|
||||||
<Menu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $emoteMenuStore}
|
|
||||||
<div>
|
|
||||||
<EmoteMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $gameOverlayVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<VideoOverlay />
|
|
||||||
<MyCamera />
|
|
||||||
<CameraControls />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $helpCameraSettingsVisibleStore}
|
|
||||||
<div>
|
|
||||||
<HelpCameraSettingsPopup />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showLimitRoomModalStore}
|
|
||||||
<div>
|
|
||||||
<LimitRoomModal />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showShareLinkMapModalStore}
|
|
||||||
<div>
|
|
||||||
<ShareLinkMapModal />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $requestVisitCardsStore}
|
|
||||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
|
||||||
{/if}
|
|
||||||
{#if $errorStore.length > 0}
|
|
||||||
<div>
|
<div>
|
||||||
<ErrorDialog />
|
<ErrorDialog />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if $loginSceneVisibleStore}
|
||||||
|
<div class="scrollable">
|
||||||
|
<LoginScene {game} />
|
||||||
|
</div>
|
||||||
|
{:else if $selectCharacterSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<SelectCharacterScene {game} />
|
||||||
|
</div>
|
||||||
|
{:else if $customCharacterSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<CustomCharacterScene {game} />
|
||||||
|
</div>
|
||||||
|
{:else if $selectCompanionSceneVisibleStore}
|
||||||
|
<div>
|
||||||
|
<SelectCompanionScene {game} />
|
||||||
|
</div>
|
||||||
|
{:else if $enableCameraSceneVisibilityStore}
|
||||||
|
<div class="scrollable">
|
||||||
|
<EnableCameraScene {game} />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<MainLayout />
|
||||||
|
|
||||||
{#if $chatVisibilityStore}
|
{#if $chatVisibilityStore}
|
||||||
<Chat />
|
<Chat />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $warningContainerStore}
|
{/if}
|
||||||
<WarningContainer />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import type { Unsubscriber } from "svelte/store";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let HTMLAudioPlayer: HTMLAudioElement;
|
let HTMLAudioPlayer: HTMLAudioElement;
|
||||||
let audioPlayerVolumeIcon: HTMLElement;
|
let audioPlayerVolumeIcon: HTMLElement;
|
||||||
@ -144,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="audio-manager-reduce-conversation">
|
<div class="audio-manager-reduce-conversation">
|
||||||
<label>
|
<label>
|
||||||
reduce in conversations
|
{$LL.audio.manager.reduce()}
|
||||||
<input type="checkbox" bind:checked={decreaseWhileTalking} on:change={setDecrease} />
|
<input type="checkbox" bind:checked={decreaseWhileTalking} on:change={setDecrease} />
|
||||||
</label>
|
</label>
|
||||||
<section class="audio-manager-file">
|
<section class="audio-manager-file">
|
||||||
@ -156,13 +157,16 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-audio-manager.nes-container.is-rounded {
|
div.main-audio-manager.nes-container.is-rounded {
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 1%;
|
||||||
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
||||||
width: clamp(200px, 15vw, 15vw);
|
width: clamp(200px, 15vw, 15vw);
|
||||||
padding: 3px 3px;
|
padding: 3px 3px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 550;
|
||||||
|
|
||||||
background-color: rgb(0, 0, 0, 0.5);
|
background-color: rgb(0, 0, 0, 0.5);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -9,10 +9,15 @@
|
|||||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||||
import layoutPresentationImg from "./images/layout-presentation.svg";
|
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||||
import layoutChatImg from "./images/layout-chat.svg";
|
import layoutChatImg from "./images/layout-chat.svg";
|
||||||
import { layoutModeStore } from "../Stores/StreamableCollectionStore";
|
import followImg from "./images/follow.svg";
|
||||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||||
import { peerStore } from "../Stores/PeerStore";
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
|
import { embedScreenLayout } from "../Stores/EmbedScreensStore";
|
||||||
|
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
|
||||||
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
|
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
function screenSharingClick(): void {
|
function screenSharingClick(): void {
|
||||||
if (isSilent) return;
|
if (isSilent) return;
|
||||||
@ -42,10 +47,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function switchLayoutMode() {
|
function switchLayoutMode() {
|
||||||
if ($layoutModeStore === LayoutMode.Presentation) {
|
if ($embedScreenLayout === LayoutMode.Presentation) {
|
||||||
$layoutModeStore = LayoutMode.VideoChat;
|
$embedScreenLayout = LayoutMode.VideoChat;
|
||||||
} else {
|
} else {
|
||||||
$layoutModeStore = LayoutMode.Presentation;
|
$embedScreenLayout = LayoutMode.Presentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function followClick() {
|
||||||
|
switch ($followStateStore) {
|
||||||
|
case "off":
|
||||||
|
gameScene.connection?.emitFollowRequest();
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
followStateStore.set("active");
|
||||||
|
break;
|
||||||
|
case "requesting":
|
||||||
|
case "active":
|
||||||
|
case "ending":
|
||||||
|
gameScene.connection?.emitFollowAbort();
|
||||||
|
followUsersStore.stopFollowing();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,15 +77,24 @@
|
|||||||
onDestroy(unsubscribeIsSilent);
|
onDestroy(unsubscribeIsSilent);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="btn-cam-action">
|
||||||
<div class="btn-cam-action">
|
|
||||||
<div class="btn-layout" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
<div class="btn-layout" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||||
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
<img class="noselect" src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
||||||
{:else}
|
{:else}
|
||||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
<img class="noselect" src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="btn-follow"
|
||||||
|
class:hide={($peerStore.size === 0 && $followStateStore === "off") || isSilent}
|
||||||
|
class:disabled={$followStateStore !== "off"}
|
||||||
|
on:click={followClick}
|
||||||
|
>
|
||||||
|
<img class="noselect" src={followImg} alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="btn-monitor"
|
class="btn-monitor"
|
||||||
on:click={screenSharingClick}
|
on:click={screenSharingClick}
|
||||||
@ -72,24 +102,137 @@
|
|||||||
class:enabled={$requestedScreenSharingState}
|
class:enabled={$requestedScreenSharingState}
|
||||||
>
|
>
|
||||||
{#if $requestedScreenSharingState && !isSilent}
|
{#if $requestedScreenSharingState && !isSilent}
|
||||||
<img src={monitorImg} alt="Start screen sharing" />
|
<img class="noselect" src={monitorImg} alt="Start screen sharing" />
|
||||||
{:else}
|
{:else}
|
||||||
<img src={monitorCloseImg} alt="Stop screen sharing" />
|
<img class="noselect" src={monitorCloseImg} alt="Stop screen sharing" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
|
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
|
||||||
{#if $requestedCameraState && !isSilent}
|
{#if $requestedCameraState && !isSilent}
|
||||||
<img src={cinemaImg} alt="Turn on webcam" />
|
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
|
||||||
{:else}
|
{:else}
|
||||||
<img src={cinemaCloseImg} alt="Turn off webcam" />
|
<img class="noselect" src={cinemaCloseImg} alt="Turn off webcam" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
||||||
{#if $requestedMicrophoneState && !isSilent}
|
{#if $requestedMicrophoneState && !isSilent}
|
||||||
<img src={microphoneImg} alt="Turn on microphone" />
|
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
|
||||||
{:else}
|
{:else}
|
||||||
<img src={microphoneCloseImg} alt="Turn off microphone" />
|
<img class="noselect" src={microphoneCloseImg} alt="Turn off microphone" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.btn-cam-action {
|
||||||
|
pointer-events: all;
|
||||||
|
position: absolute;
|
||||||
|
display: inline-flex;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 15px;
|
||||||
|
width: 360px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
z-index: 251;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
div.hide {
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*btn animation*/
|
||||||
|
.btn-cam-action div {
|
||||||
|
cursor: url("../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: solid 0px black;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: #666;
|
||||||
|
box-shadow: 2px 2px 24px #444;
|
||||||
|
border-radius: 48px;
|
||||||
|
transform: translateY(15px);
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin: 0 4%;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-cam-action div.disabled {
|
||||||
|
background: #d75555;
|
||||||
|
}
|
||||||
|
.btn-cam-action div.enabled {
|
||||||
|
background: #73c973;
|
||||||
|
}
|
||||||
|
.btn-cam-action:hover div {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
.btn-cam-action div:hover {
|
||||||
|
background: #407cf7;
|
||||||
|
box-shadow: 4px 4px 48px #666;
|
||||||
|
transition: 120ms;
|
||||||
|
}
|
||||||
|
.btn-micro {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.btn-video {
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
.btn-monitor {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.btn-layout {
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.btn-cam-action div img {
|
||||||
|
height: 22px;
|
||||||
|
width: 30px;
|
||||||
|
position: relative;
|
||||||
|
cursor: url("../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-follow {
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
/**
|
||||||
|
* If we cannot hover over elements, let's display camera button in full.
|
||||||
|
*/
|
||||||
|
.btn-cam-action {
|
||||||
|
div {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.btn-cam-action {
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 40%;
|
||||||
|
max-height: 40px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: 20%;
|
||||||
|
max-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import ChatElement from "./ChatElement.svelte";
|
import ChatElement from "./ChatElement.svelte";
|
||||||
import { afterUpdate, beforeUpdate, onMount } from "svelte";
|
import { afterUpdate, beforeUpdate, onMount } from "svelte";
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let listDom: HTMLElement;
|
let listDom: HTMLElement;
|
||||||
let chatWindowElement: HTMLElement;
|
let chatWindowElement: HTMLElement;
|
||||||
@ -42,10 +43,10 @@
|
|||||||
<svelte:window on:keydown={onKeyDown} on:click={onClick} />
|
<svelte:window on:keydown={onKeyDown} on:click={onClick} />
|
||||||
|
|
||||||
<aside class="chatWindow" transition:fly={{ x: -1000, duration: 500 }} bind:this={chatWindowElement}>
|
<aside class="chatWindow" transition:fly={{ x: -1000, duration: 500 }} bind:this={chatWindowElement}>
|
||||||
<p class="close-icon" on:click={closeChat}>×</p>
|
<p class="close-icon noselect" on:click={closeChat}>×</p>
|
||||||
<section class="messagesList" bind:this={listDom}>
|
<section class="messagesList" bind:this={listDom}>
|
||||||
<ul>
|
<ul>
|
||||||
<li><p class="system-text">Here is your chat history:</p></li>
|
<li><p class="system-text">{$LL.chat.intro()}</p></li>
|
||||||
{#each $chatMessagesStore as message, i}
|
{#each $chatMessagesStore as message, i}
|
||||||
<li><ChatElement {message} line={i} /></li>
|
<li><ChatElement {message} line={i} /></li>
|
||||||
{/each}
|
{/each}
|
||||||
@ -77,7 +78,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
aside.chatWindow {
|
aside.chatWindow {
|
||||||
z-index: 100;
|
z-index: 1000;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import { chatMessagesStore, chatInputFocusStore } from "../../Stores/ChatStore";
|
import { chatMessagesStore, chatInputFocusStore } from "../../Stores/ChatStore";
|
||||||
|
|
||||||
export const handleForm = {
|
export const handleForm = {
|
||||||
@ -27,7 +28,7 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={newMessageText}
|
bind:value={newMessageText}
|
||||||
placeholder="Enter your message..."
|
placeholder={$LL.chat.enter()}
|
||||||
on:focus={onFocus}
|
on:focus={onFocus}
|
||||||
on:blur={onBlur}
|
on:blur={onBlur}
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import type { PlayerInterface } from "../../Phaser/Game/PlayerInterface";
|
import type { PlayerInterface } from "../../Phaser/Game/PlayerInterface";
|
||||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||||
|
|
||||||
@ -12,8 +13,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ul class="selectMenu" style="border-top: {player.color || 'whitesmoke'} 5px solid">
|
<ul class="selectMenu" style="border-top: {player.color || 'whitesmoke'} 5px solid">
|
||||||
<li><button class="text-btn" disabled={!player.visitCardUrl} on:click={openVisitCard}>Visit card</button></li>
|
<li>
|
||||||
<li><button class="text-btn" disabled>Add friend</button></li>
|
<button class="text-btn" disabled={!player.visitCardUrl} on:click={openVisitCard}
|
||||||
|
>{$LL.chat.menu.visitCard()}</button
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li><button class="text-btn" disabled>{$LL.chat.menu.addFriend}</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import { CustomizeScene, CustomizeSceneName } from "../../Phaser/Login/CustomizeScene";
|
import { CustomizeScene, CustomizeSceneName } from "../../Phaser/Login/CustomizeScene";
|
||||||
import { activeRowStore } from "../../Stores/CustomCharacterStore";
|
import { activeRowStore } from "../../Stores/CustomCharacterStore";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@
|
|||||||
|
|
||||||
<form class="customCharacterScene">
|
<form class="customCharacterScene">
|
||||||
<section class="text-center">
|
<section class="text-center">
|
||||||
<h2>Customize your WOKA</h2>
|
<h2>{$LL.woka.customWoka.title()}</h2>
|
||||||
</section>
|
</section>
|
||||||
<section class="action action-move">
|
<section class="action action-move">
|
||||||
<button
|
<button
|
||||||
@ -53,32 +54,37 @@
|
|||||||
<section class="action">
|
<section class="action">
|
||||||
{#if $activeRowStore === 0}
|
{#if $activeRowStore === 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={previousScene}
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={previousScene}
|
||||||
>Return</button
|
>{$LL.woka.customWoka.navigation.return()}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $activeRowStore !== 0}
|
{#if $activeRowStore !== 0}
|
||||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={selectUp}
|
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={selectUp}
|
||||||
>Back <img src="resources/objects/arrow_up_black.png" alt="" /></button
|
>{$LL.woka.customWoka.navigation.back()}
|
||||||
|
<img src="resources/objects/arrow_up_black.png" alt="" /></button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $activeRowStore === 5}
|
{#if $activeRowStore === 5}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="customCharacterSceneFormSubmit nes-btn is-primary"
|
class="customCharacterSceneFormSubmit nes-btn is-primary"
|
||||||
on:click|preventDefault={finish}>Finish</button
|
on:click|preventDefault={finish}>{$LL.woka.customWoka.navigation.finish()}</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $activeRowStore !== 5}
|
{#if $activeRowStore !== 5}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="customCharacterSceneFormSubmit nes-btn is-primary"
|
class="customCharacterSceneFormSubmit nes-btn is-primary"
|
||||||
on:click|preventDefault={selectDown}>Next <img src="resources/objects/arrow_down.png" alt="" /></button
|
on:click|preventDefault={selectDown}
|
||||||
|
>{$LL.woka.customWoka.navigation.next()}
|
||||||
|
<img src="resources/objects/arrow_down.png" alt="" /></button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.customCharacterScene {
|
form.customCharacterScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@ -125,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.customCharacterScene button.customCharacterSceneButtonLeft {
|
form.customCharacterScene button.customCharacterSceneButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
32
front/src/Components/EmbedScreens/CamerasContainer.svelte
Normal file
32
front/src/Components/EmbedScreens/CamerasContainer.svelte
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
||||||
|
import MediaBox from "../Video/MediaBox.svelte";
|
||||||
|
|
||||||
|
export let highlightedEmbedScreen: EmbedScreen | null;
|
||||||
|
export let full = false;
|
||||||
|
$: clickable = !full;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside class="cameras-container" class:full>
|
||||||
|
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||||
|
{#if !highlightedEmbedScreen || highlightedEmbedScreen.type !== "streamable" || (highlightedEmbedScreen.type === "streamable" && highlightedEmbedScreen.embed !== peer)}
|
||||||
|
<MediaBox streamable={peer} isClickable={clickable} />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cameras-container {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
313
front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte
Normal file
313
front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import { ICON_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
import { coWebsitesNotAsleep, mainCoWebsite } from "../../Stores/CoWebsiteStore";
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
|
||||||
|
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
|
export let index: number;
|
||||||
|
export let coWebsite: CoWebsite;
|
||||||
|
export let vertical: boolean;
|
||||||
|
|
||||||
|
let icon: HTMLImageElement;
|
||||||
|
let iconLoaded = false;
|
||||||
|
let state = coWebsite.state;
|
||||||
|
|
||||||
|
const coWebsiteUrl = coWebsite.iframe.src;
|
||||||
|
const urlObject = new URL(coWebsiteUrl);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
icon.src = `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||||
|
icon.alt = coWebsite.altMessage ?? urlObject.hostname;
|
||||||
|
icon.onload = () => {
|
||||||
|
iconLoaded = true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onClick() {
|
||||||
|
if (vertical) {
|
||||||
|
coWebsiteManager.goToMain(coWebsite);
|
||||||
|
} else if ($mainCoWebsite) {
|
||||||
|
if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) {
|
||||||
|
const coWebsites = $coWebsitesNotAsleep;
|
||||||
|
const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined;
|
||||||
|
if (newMain) {
|
||||||
|
coWebsiteManager.goToMain(coWebsite);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
highlightedEmbedScreen.toggleHighlight({
|
||||||
|
type: "cowebsite",
|
||||||
|
embed: coWebsite,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === "asleep") {
|
||||||
|
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||||
|
}
|
||||||
|
|
||||||
|
coWebsiteManager.resizeAllIframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isHighlight: boolean = false;
|
||||||
|
let isMain: boolean = false;
|
||||||
|
$: {
|
||||||
|
isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe;
|
||||||
|
isHighlight =
|
||||||
|
$highlightedEmbedScreen !== null &&
|
||||||
|
$highlightedEmbedScreen.type === "cowebsite" &&
|
||||||
|
$highlightedEmbedScreen.embed.iframe === coWebsite.iframe;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id={"cowebsite-thumbnail-" + index}
|
||||||
|
class="cowebsite-thumbnail nes-pointer"
|
||||||
|
class:asleep={$state === "asleep"}
|
||||||
|
class:loading={$state === "loading"}
|
||||||
|
class:ready={$state === "ready"}
|
||||||
|
class:displayed={isMain || isHighlight}
|
||||||
|
class:vertical
|
||||||
|
on:click={onClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="cowebsite-icon noselect nes-pointer"
|
||||||
|
class:hide={!iconLoaded}
|
||||||
|
bind:this={icon}
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
class="cowebsite-icon"
|
||||||
|
class:hide={iconLoaded}
|
||||||
|
style="margin: auto; background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%; shape-rendering: auto;"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
>
|
||||||
|
<rect x="19" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="40" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.125s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.25s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="19" y="40" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.875s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="40" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.375s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="19" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.75s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="40" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.625s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.5s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cowebsite-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
margin: 12px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
left: -8px;
|
||||||
|
top: -8px;
|
||||||
|
|
||||||
|
margin: 4px;
|
||||||
|
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 4px;
|
||||||
|
border-image-slice: 3;
|
||||||
|
border-image-width: 3;
|
||||||
|
border-image-repeat: stretch;
|
||||||
|
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(33,37,41)" /></svg>');
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.vertical) {
|
||||||
|
animation: bounce 0.35s ease 6 alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
margin: 7px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cowebsite-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation: shake 0.35s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.displayed {
|
||||||
|
&:not(.vertical) {
|
||||||
|
animation: activeThumbnail 300ms ease-in 0s forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.asleep {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
--webkit-filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
animation: 2500ms ease-in-out 0s infinite alternate backgroundLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ready {
|
||||||
|
&::before {
|
||||||
|
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(38, 74, 110)" /></svg>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backgroundLoading {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: #25598e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes activeThumbnail {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
20% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cowebsite-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
42
front/src/Components/EmbedScreens/CoWebsitesContainer.svelte
Normal file
42
front/src/Components/EmbedScreens/CoWebsitesContainer.svelte
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { coWebsites } from "../../Stores/CoWebsiteStore";
|
||||||
|
import CoWebsiteThumbnail from "./CoWebsiteThumbnailSlot.svelte";
|
||||||
|
|
||||||
|
export let vertical = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $coWebsites.length > 0}
|
||||||
|
<div id="cowebsite-thumbnail-container" class:vertical>
|
||||||
|
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)}
|
||||||
|
<CoWebsiteThumbnail {index} {coWebsite} {vertical} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#cowebsite-thumbnail-container {
|
||||||
|
pointer-events: all;
|
||||||
|
height: 100px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 2%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
height: auto !important;
|
||||||
|
width: auto !important;
|
||||||
|
bottom: auto !important;
|
||||||
|
left: auto !important;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import PresentationLayout from "./Layouts/PresentationLayout.svelte";
|
||||||
|
import MozaicLayout from "./Layouts/MozaicLayout.svelte";
|
||||||
|
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
||||||
|
import { embedScreenLayout } from "../../Stores/EmbedScreensStore";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="embedScreensContainer">
|
||||||
|
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||||
|
<PresentationLayout />
|
||||||
|
{:else}
|
||||||
|
<MozaicLayout />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#embedScreensContainer {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 2%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,61 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||||
|
import { streamableCollectionStore } from "../../../Stores/StreamableCollectionStore";
|
||||||
|
import MediaBox from "../../Video/MediaBox.svelte";
|
||||||
|
|
||||||
|
let layoutDom: HTMLDivElement;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(layoutDom);
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="mozaic-layout" bind:this={layoutDom}>
|
||||||
|
<div
|
||||||
|
class="media-container"
|
||||||
|
class:full-width={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||||
|
class:quarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size === 4}
|
||||||
|
>
|
||||||
|
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||||
|
<MediaBox
|
||||||
|
streamable={peer}
|
||||||
|
mozaicFullWidth={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||||
|
mozaicQuarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size === 4}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#mozaic-layout {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.media-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33.3% 33.3% 33.3%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.quarter {
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,143 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||||
|
import CamerasContainer from "../CamerasContainer.svelte";
|
||||||
|
import MediaBox from "../../Video/MediaBox.svelte";
|
||||||
|
import { coWebsiteManager } from "../../../WebRtc/CoWebsiteManager";
|
||||||
|
import { afterUpdate, onMount } from "svelte";
|
||||||
|
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../../../Utils/BreakpointsUtils";
|
||||||
|
import { peerStore } from "../../../Stores/PeerStore";
|
||||||
|
|
||||||
|
function closeCoWebsite() {
|
||||||
|
if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||||
|
if ($highlightedEmbedScreen.embed.closable) {
|
||||||
|
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed).catch(() => {
|
||||||
|
console.error("Error during co-website highlighted closing");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch(() => {
|
||||||
|
console.error("Error during co-website highlighted unloading");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if ($highlightedEmbedScreen) {
|
||||||
|
coWebsiteManager.resizeAllIframes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let layoutDom: HTMLDivElement;
|
||||||
|
|
||||||
|
let displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||||
|
let displayFullMedias = isMediaBreakpointUp("sm");
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||||
|
displayFullMedias = isMediaBreakpointUp("sm");
|
||||||
|
|
||||||
|
if (!displayCoWebsiteContainer && $highlightedEmbedScreen && $highlightedEmbedScreen.type === "cowebsite") {
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayFullMedias) {
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(layoutDom);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="presentation-layout" bind:this={layoutDom} class:full-medias={displayFullMedias}>
|
||||||
|
{#if displayFullMedias}
|
||||||
|
<div id="full-medias">
|
||||||
|
<CamerasContainer full={true} highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div id="embed-left-block" class:full={$peerStore.size === 0}>
|
||||||
|
<div id="main-embed-screen">
|
||||||
|
{#if $highlightedEmbedScreen}
|
||||||
|
{#if $highlightedEmbedScreen.type === "streamable"}
|
||||||
|
{#key $highlightedEmbedScreen.embed.uniqueId}
|
||||||
|
<MediaBox
|
||||||
|
isHightlighted={true}
|
||||||
|
isClickable={true}
|
||||||
|
streamable={$highlightedEmbedScreen.embed}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
||||||
|
{#key $highlightedEmbedScreen.embed.iframe.id}
|
||||||
|
<div
|
||||||
|
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.iframe.id}
|
||||||
|
class="highlighted-cowebsite nes-container is-rounded"
|
||||||
|
>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="button" class="nes-btn is-error close" on:click={closeCoWebsite}
|
||||||
|
>×</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $peerStore.size > 0}
|
||||||
|
<CamerasContainer highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#presentation-layout {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&.full-medias {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#embed-left-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 0 0 75%;
|
||||||
|
height: 100%;
|
||||||
|
width: 75%;
|
||||||
|
|
||||||
|
&.full {
|
||||||
|
flex: 0 0 98% !important;
|
||||||
|
width: 98% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-embed-screen {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 3%;
|
||||||
|
|
||||||
|
.highlighted-cowebsite {
|
||||||
|
height: 100% !important;
|
||||||
|
width: 96%;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
z-index: 200;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 2%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -3,7 +3,8 @@
|
|||||||
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { EmojiButton } from "@joeattardi/emoji-button";
|
import { EmojiButton } from "@joeattardi/emoji-button";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
let emojiContainer: HTMLElement;
|
let emojiContainer: HTMLElement;
|
||||||
let picker: EmojiButton;
|
let picker: EmojiButton;
|
||||||
@ -15,10 +16,31 @@
|
|||||||
rootElement: emojiContainer,
|
rootElement: emojiContainer,
|
||||||
styleProperties: {
|
styleProperties: {
|
||||||
"--font": "Press Start 2P",
|
"--font": "Press Start 2P",
|
||||||
|
"--text-color": "whitesmoke",
|
||||||
|
"--secondary-text-color": "whitesmoke",
|
||||||
|
"--category-button-color": "whitesmoke",
|
||||||
},
|
},
|
||||||
emojisPerRow: isMobile() ? 6 : 8,
|
emojisPerRow: isMediaBreakpointUp("md") ? 6 : 8,
|
||||||
autoFocusSearch: false,
|
autoFocusSearch: false,
|
||||||
style: "twemoji",
|
style: "twemoji",
|
||||||
|
showPreview: false,
|
||||||
|
i18n: {
|
||||||
|
search: $LL.emoji.search(),
|
||||||
|
categories: {
|
||||||
|
recents: $LL.emoji.categories.recents(),
|
||||||
|
smileys: $LL.emoji.categories.smileys(),
|
||||||
|
people: $LL.emoji.categories.people(),
|
||||||
|
animals: $LL.emoji.categories.animals(),
|
||||||
|
food: $LL.emoji.categories.food(),
|
||||||
|
activities: $LL.emoji.categories.activities(),
|
||||||
|
travel: $LL.emoji.categories.travel(),
|
||||||
|
objects: $LL.emoji.categories.objects(),
|
||||||
|
symbols: $LL.emoji.categories.symbols(),
|
||||||
|
flags: $LL.emoji.categories.flags(),
|
||||||
|
custom: $LL.emoji.categories.custom(),
|
||||||
|
},
|
||||||
|
notFound: $LL.emoji.notFound(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
//the timeout is here to prevent the menu from flashing
|
//the timeout is here to prevent the menu from flashing
|
||||||
setTimeout(() => picker.showPicker(emojiContainer), 100);
|
setTimeout(() => picker.showPicker(emojiContainer), 100);
|
||||||
@ -64,6 +86,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emote-menu {
|
.emote-menu {
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
import cinemaCloseImg from "../images/cinema-close.svg";
|
import cinemaCloseImg from "../images/cinema-close.svg";
|
||||||
import cinemaImg from "../images/cinema.svg";
|
import cinemaImg from "../images/cinema.svg";
|
||||||
import microphoneImg from "../images/microphone.svg";
|
import microphoneImg from "../images/microphone.svg";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
let selectedCamera: string | undefined = undefined;
|
let selectedCamera: string | undefined = undefined;
|
||||||
@ -76,7 +77,7 @@
|
|||||||
|
|
||||||
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
||||||
<section class="text-center">
|
<section class="text-center">
|
||||||
<h2>Turn on your camera and microphone</h2>
|
<h2>{$LL.camera.enable.title()}</h2>
|
||||||
</section>
|
</section>
|
||||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||||
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline />
|
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline />
|
||||||
@ -121,11 +122,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<section class="action">
|
<section class="action">
|
||||||
<button type="submit" class="nes-btn is-primary letsgo">Let's go!</button>
|
<button type="submit" class="nes-btn is-primary letsgo">{$LL.camera.enable.start()}</button>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.enableCameraScene {
|
.enableCameraScene {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
margin: 20px auto 0;
|
margin: 20px auto 0;
|
||||||
@ -213,7 +216,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
.enableCameraScene h2 {
|
.enableCameraScene h2 {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
33
front/src/Components/FollowMenu/FollowButton.svelte
Normal file
33
front/src/Components/FollowMenu/FollowButton.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import followImg from "../images/follow.svg";
|
||||||
|
|
||||||
|
export let hidden: Boolean;
|
||||||
|
|
||||||
|
let cancelButton = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="btn-follow" class:hide={hidden} class:cancel={cancelButton}>
|
||||||
|
<img src={followImg} alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.btn-follow {
|
||||||
|
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: solid 0px black;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: #666;
|
||||||
|
box-shadow: 2px 2px 24px #444;
|
||||||
|
border-radius: 48px;
|
||||||
|
transform: translateY(15px);
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
margin: 0 4%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,20 +1,13 @@
|
|||||||
<!--
|
|
||||||
vim: ft=typescript
|
|
||||||
-->
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import followImg from "../images/follow.svg";
|
|
||||||
|
|
||||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
const gameScene = gameManager.getCurrentGameScene();
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
function name(userId: number): string | undefined {
|
function name(userId: number): string {
|
||||||
return gameScene.MapPlayersByKey.get(userId)?.PlayerValue;
|
const user = gameScene.MapPlayersByKey.get(userId);
|
||||||
}
|
return user ? user.PlayerValue : "";
|
||||||
|
|
||||||
function sendFollowRequest() {
|
|
||||||
gameScene.CurrentPlayer.sendFollowRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptFollowRequest() {
|
function acceptFollowRequest() {
|
||||||
@ -42,11 +35,15 @@ vim: ft=typescript
|
|||||||
{#if $followStateStore === "requesting" && $followRoleStore === "follower"}
|
{#if $followStateStore === "requesting" && $followRoleStore === "follower"}
|
||||||
<div class="interact-menu nes-container is-rounded">
|
<div class="interact-menu nes-container is-rounded">
|
||||||
<section class="interact-menu-title">
|
<section class="interact-menu-title">
|
||||||
<h2>Do you want to follow {name($followUsersStore[0])}?</h2>
|
<h2>{$LL.follow.interactMenu.title.follow({ leader: name($followUsersStore[0]) })}</h2>
|
||||||
</section>
|
</section>
|
||||||
<section class="interact-menu-action">
|
<section class="interact-menu-action">
|
||||||
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}>Yes</button>
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}
|
||||||
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}>No</button>
|
>{$LL.follow.interactMenu.yes()}</button
|
||||||
|
>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}
|
||||||
|
>{$LL.follow.interactMenu.no()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -54,88 +51,77 @@ vim: ft=typescript
|
|||||||
{#if $followStateStore === "ending"}
|
{#if $followStateStore === "ending"}
|
||||||
<div class="interact-menu nes-container is-rounded">
|
<div class="interact-menu nes-container is-rounded">
|
||||||
<section class="interact-menu-title">
|
<section class="interact-menu-title">
|
||||||
<h2>Interaction</h2>
|
<h2>{$LL.follow.interactMenu.title.interact()}</h2>
|
||||||
</section>
|
</section>
|
||||||
{#if $followRoleStore === "follower"}
|
{#if $followRoleStore === "follower"}
|
||||||
<section class="interact-menu-question">
|
<section class="interact-menu-question">
|
||||||
<p>Do you want to stop following {name($followUsersStore[0])}?</p>
|
<p>{$LL.follow.interactMenu.stop.follower({ leader: name($followUsersStore[0]) })}</p>
|
||||||
</section>
|
</section>
|
||||||
{:else if $followRoleStore === "leader"}
|
{:else if $followRoleStore === "leader"}
|
||||||
<section class="interact-menu-question">
|
<section class="interact-menu-question">
|
||||||
<p>Do you want to stop leading the way?</p>
|
<p>{$LL.follow.interactMenu.stop.leader()}</p>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="interact-menu-action">
|
<section class="interact-menu-action">
|
||||||
<button type="button" class="nes-btn is-success" on:click|preventDefault={reset}>Yes</button>
|
<button type="button" class="nes-btn is-success" on:click|preventDefault={reset}
|
||||||
<button type="button" class="nes-btn is-error" on:click|preventDefault={abortEnding}>No</button>
|
>{$LL.follow.interactMenu.yes()}</button
|
||||||
|
>
|
||||||
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={abortEnding}
|
||||||
|
>{$LL.follow.interactMenu.no()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
<div class="interact-status nes-container is-rounded">
|
<div class="interact-status nes-container is-rounded">
|
||||||
<section class="interact-status">
|
<section>
|
||||||
{#if $followRoleStore === "follower"}
|
{#if $followRoleStore === "follower"}
|
||||||
<p>Following {name($followUsersStore[0])}</p>
|
<p>{$LL.follow.interactStatus.following({ leader: name($followUsersStore[0]) })}</p>
|
||||||
{:else if $followUsersStore.length === 0}
|
{:else if $followUsersStore.length === 0}
|
||||||
<p>Waiting for followers' confirmation</p>
|
<p>{$LL.follow.interactStatus.waitingFollowers()}</p>
|
||||||
{:else if $followUsersStore.length === 1}
|
{:else if $followUsersStore.length === 1}
|
||||||
<p>{name($followUsersStore[0])} is following you</p>
|
<p>{$LL.follow.interactStatus.followed.one({ follower: name($followUsersStore[0]) })}</p>
|
||||||
{:else if $followUsersStore.length === 2}
|
{:else if $followUsersStore.length === 2}
|
||||||
<p>{name($followUsersStore[0])} and {name($followUsersStore[1])} are following you</p>
|
<p>
|
||||||
|
{$LL.follow.interactStatus.followed.two({
|
||||||
|
firstFollower: name($followUsersStore[0]),
|
||||||
|
secondFollower: name($followUsersStore[1]),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>
|
||||||
{$followUsersStore.slice(0, -1).map(name).join(", ")} and {name(
|
{$LL.follow.interactStatus.followed.many({
|
||||||
$followUsersStore[$followUsersStore.length - 1]
|
followers: $followUsersStore.slice(0, -1).map(name).join(", "),
|
||||||
)} are following you
|
lastFollower: name($followUsersStore[$followUsersStore.length - 1]),
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $followStateStore === "off"}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-primary follow-menu-button"
|
|
||||||
on:click|preventDefault={sendFollowRequest}
|
|
||||||
title="Ask others to follow"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
|
||||||
{#if $followRoleStore === "follower"}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-error follow-menu-button"
|
|
||||||
on:click|preventDefault={reset}
|
|
||||||
title="Stop following"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-error follow-menu-button"
|
|
||||||
on:click|preventDefault={reset}
|
|
||||||
title="Stop leading the way"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.nes-container {
|
.nes-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-status {
|
.interact-status {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|
||||||
position: relative;
|
position: absolute;
|
||||||
height: 2.7em;
|
max-height: 2.7em;
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
top: 87vh;
|
top: 87vh;
|
||||||
margin: auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-menu {
|
div.interact-menu {
|
||||||
@ -143,10 +129,14 @@ vim: ft=typescript
|
|||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|
||||||
position: relative;
|
position: absolute;
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
top: 60vh;
|
top: 60vh;
|
||||||
margin: auto;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 150;
|
||||||
|
|
||||||
section.interact-menu-title {
|
section.interact-menu-title {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -174,23 +164,16 @@ vim: ft=typescript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow-menu-button {
|
@include media-breakpoint-up(md) {
|
||||||
position: absolute;
|
.interact-status {
|
||||||
bottom: 10px;
|
width: 90vw;
|
||||||
left: 10px;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
div.interact-status {
|
|
||||||
width: 100vw;
|
|
||||||
top: 78vh;
|
top: 78vh;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-menu {
|
div.interact-menu {
|
||||||
height: 21vh;
|
max-height: 21vh;
|
||||||
width: 100vw;
|
width: 90vw;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||||
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||||
import { getNavigatorType, isAndroid as isAndroidFct, NavigatorType } from "../../WebRtc/DeviceUtils";
|
import { getNavigatorType, isAndroid as isAndroidFct, NavigatorType } from "../../WebRtc/DeviceUtils";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let isAndroid = isAndroidFct();
|
let isAndroid = isAndroidFct();
|
||||||
let isFirefox = getNavigatorType() === NavigatorType.firefox;
|
let isFirefox = getNavigatorType() === NavigatorType.firefox;
|
||||||
@ -21,17 +22,16 @@
|
|||||||
<form
|
<form
|
||||||
class="helpCameraSettings nes-container"
|
class="helpCameraSettings nes-container"
|
||||||
on:submit|preventDefault={close}
|
on:submit|preventDefault={close}
|
||||||
transition:fly={{ y: -900, duration: 500 }}
|
transition:fly={{ y: -50, duration: 500 }}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<h2>Camera / Microphone access needed</h2>
|
<h2>{$LL.camera.help.title()}</h2>
|
||||||
<p class="err">Permission denied</p>
|
<p class="err">{$LL.camera.help.permissionDenied()}</p>
|
||||||
<p>You must allow camera and microphone access in your browser.</p>
|
<p>{$LL.camera.help.content()}</p>
|
||||||
<p>
|
<p>
|
||||||
{#if isFirefox}
|
{#if isFirefox}
|
||||||
<p class="err">
|
<p class="err">
|
||||||
Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the
|
{$LL.camera.help.firefoxContent()}
|
||||||
authorization.
|
|
||||||
</p>
|
</p>
|
||||||
<img src={firefoxImg} alt="" />
|
<img src={firefoxImg} alt="" />
|
||||||
{:else if isChrome && !isAndroid}
|
{:else if isChrome && !isAndroid}
|
||||||
@ -40,9 +40,11 @@
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
|
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}
|
||||||
|
>{$LL.camera.help.refresh()}</button
|
||||||
|
>
|
||||||
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}
|
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}
|
||||||
>Continue without webcam</button
|
>{$LL.camera.help.continue()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
@ -53,9 +55,12 @@
|
|||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-top: 10vh;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
z-index: 600;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -21,9 +21,11 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
margin: 0 auto;
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: clamp(200px, 20vw, 20vw);
|
width: clamp(200px, 20vw, 20vw);
|
||||||
|
z-index: 155;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -31,6 +33,10 @@
|
|||||||
animation: moveMessage 0.5s;
|
animation: moveMessage 0.5s;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-bottom: 5%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.nes-container.is-rounded {
|
div.nes-container.is-rounded {
|
@ -4,6 +4,7 @@
|
|||||||
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
|
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
|
||||||
import logoImg from "../images/logo.png";
|
import logoImg from "../images/logo.png";
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -27,7 +28,7 @@
|
|||||||
<img src={logoImg} alt="WorkAdventure logo" />
|
<img src={logoImg} alt="WorkAdventure logo" />
|
||||||
</section>
|
</section>
|
||||||
<section class="text-center">
|
<section class="text-center">
|
||||||
<h2>Enter your name</h2>
|
<h2>{$LL.login.input.name.placeholder()}</h2>
|
||||||
</section>
|
</section>
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
<!-- svelte-ignore a11y-autofocus -->
|
||||||
<input
|
<input
|
||||||
@ -44,22 +45,20 @@
|
|||||||
/>
|
/>
|
||||||
<section class="error-section">
|
<section class="error-section">
|
||||||
{#if name.trim() === "" && startValidating}
|
{#if name.trim() === "" && startValidating}
|
||||||
<p class="err">The name is empty</p>
|
<p class="err">{$LL.login.input.name.empty()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if DISPLAY_TERMS_OF_USE}
|
{#if DISPLAY_TERMS_OF_USE}
|
||||||
<section class="terms-and-conditions">
|
<section class="terms-and-conditions">
|
||||||
|
<a style="display: none;" href="traduction">Need for traduction</a>
|
||||||
<p>
|
<p>
|
||||||
By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank"
|
{$LL.login.terms()}
|
||||||
>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>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="action">
|
<section class="action">
|
||||||
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
|
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">{$LL.login.continue()}</button>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
170
front/src/Components/MainLayout.svelte
Normal file
170
front/src/Components/MainLayout.svelte
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
||||||
|
import { embedScreenLayout, hasEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||||
|
import { emoteMenuStore } from "../Stores/EmoteStore";
|
||||||
|
import { myCameraVisibilityStore } from "../Stores/MyCameraStoreVisibility";
|
||||||
|
import { requestVisitCardsStore } from "../Stores/GameStore";
|
||||||
|
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
||||||
|
import { layoutManagerActionVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||||
|
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
|
||||||
|
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||||
|
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||||
|
import CameraControls from "./CameraControls.svelte";
|
||||||
|
import EmbedScreensContainer from "./EmbedScreens/EmbedScreensContainer.svelte";
|
||||||
|
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||||
|
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||||
|
import LayoutActionManager from "./LayoutActionManager/LayoutActionManager.svelte";
|
||||||
|
import Menu from "./Menu/Menu.svelte";
|
||||||
|
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||||
|
import MyCamera from "./MyCamera.svelte";
|
||||||
|
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||||
|
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||||
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
|
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
|
||||||
|
import CoWebsitesContainer from "./EmbedScreens/CoWebsitesContainer.svelte";
|
||||||
|
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||||
|
import { followStateStore } from "../Stores/FollowStore";
|
||||||
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||||
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||||
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
|
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
||||||
|
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
||||||
|
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
||||||
|
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||||
|
|
||||||
|
let mainLayout: HTMLDivElement;
|
||||||
|
|
||||||
|
let displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||||
|
let displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||||
|
displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(mainLayout);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="main-layout" bind:this={mainLayout}>
|
||||||
|
<aside id="main-layout-left-aside">
|
||||||
|
{#if $menuIconVisiblilityStore}
|
||||||
|
<MenuIcon />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $embedScreenLayout === LayoutMode.VideoChat || displayCoWebsiteContainerMd}
|
||||||
|
<CoWebsitesContainer vertical={true} />
|
||||||
|
{/if}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section id="main-layout-main">
|
||||||
|
{#if $menuVisiblilityStore}
|
||||||
|
<Menu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $banMessageStore.length > 0}
|
||||||
|
<BanMessageContainer />
|
||||||
|
{:else if $textMessageStore.length > 0}
|
||||||
|
<TextMessageContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $soundPlayingStore}
|
||||||
|
<AudioPlaying url={$soundPlayingStore} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $warningContainerStore}
|
||||||
|
<WarningContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showReportScreenStore !== userReportEmpty}
|
||||||
|
<ReportMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $helpCameraSettingsVisibleStore}
|
||||||
|
<HelpCameraSettingsPopup />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $audioManagerVisibilityStore}
|
||||||
|
<AudioManager />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showLimitRoomModalStore}
|
||||||
|
<LimitRoomModal />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showShareLinkMapModalStore}
|
||||||
|
<ShareLinkMapModal />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||||
|
<FollowMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $requestVisitCardsStore}
|
||||||
|
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $emoteMenuStore}
|
||||||
|
<EmoteMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if hasEmbedScreen}
|
||||||
|
<EmbedScreensContainer />
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="main-layout-baseline">
|
||||||
|
{#if displayCoWebsiteContainerLg}
|
||||||
|
<CoWebsitesContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $layoutManagerActionVisibilityStore}
|
||||||
|
<LayoutActionManager />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $myCameraVisibilityStore}
|
||||||
|
<MyCamera />
|
||||||
|
<CameraControls />
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
#main-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px calc(100% - 120px);
|
||||||
|
grid-template-rows: 80% 20%;
|
||||||
|
|
||||||
|
&-left-aside {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-baseline {
|
||||||
|
grid-column: 1/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 15% 85%;
|
||||||
|
|
||||||
|
&-left-aside {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 20% 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let gameScene = gameManager.getCurrentGameScene();
|
let gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
@ -11,7 +12,7 @@
|
|||||||
let mapName: string = "";
|
let mapName: string = "";
|
||||||
let mapLink: string = "";
|
let mapLink: string = "";
|
||||||
let mapDescription: string = "";
|
let mapDescription: string = "";
|
||||||
let mapCopyright: string = "The map creator did not declare a copyright for the map.";
|
let mapCopyright: string = $LL.menu.about.copyrights.map.empty();
|
||||||
let tilesetCopyright: string[] = [];
|
let tilesetCopyright: string[] = [];
|
||||||
let audioCopyright: string[] = [];
|
let audioCopyright: string[] = [];
|
||||||
|
|
||||||
@ -62,47 +63,45 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="about-room-main">
|
<div class="about-room-main">
|
||||||
<h2>Information on the map</h2>
|
<h2>{$LL.menu.about.mapInfo()}</h2>
|
||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<h3>{mapName}</h3>
|
<h3>{mapName}</h3>
|
||||||
<p class="string-HTML">{mapDescription}</p>
|
<p class="string-HTML">{mapDescription}</p>
|
||||||
{#if mapLink}
|
{#if mapLink}
|
||||||
<p class="string-HTML">> <a href={mapLink} target="_blank">link to this map</a> <</p>
|
<p class="string-HTML">
|
||||||
|
> <a href={mapLink} target="_blank">{$LL.menu.about.mapLink()}</a> <
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<h3 class="nes-pointer hoverable" on:click={() => (expandedMapCopyright = !expandedMapCopyright)}>
|
<h3 class="nes-pointer hoverable" on:click={() => (expandedMapCopyright = !expandedMapCopyright)}>
|
||||||
Copyrights of the map
|
{$LL.menu.about.copyrights.map.title()}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="string-HTML" hidden={!expandedMapCopyright}>{mapCopyright}</p>
|
<p class="string-HTML" hidden={!expandedMapCopyright}>{mapCopyright}</p>
|
||||||
<h3 class="nes-pointer hoverable" on:click={() => (expandedTilesetCopyright = !expandedTilesetCopyright)}>
|
<h3 class="nes-pointer hoverable" on:click={() => (expandedTilesetCopyright = !expandedTilesetCopyright)}>
|
||||||
Copyrights of the tilesets
|
{$LL.menu.about.copyrights.tileset.title()}
|
||||||
</h3>
|
</h3>
|
||||||
<section hidden={!expandedTilesetCopyright}>
|
<section hidden={!expandedTilesetCopyright}>
|
||||||
{#each tilesetCopyright as copyright}
|
{#each tilesetCopyright as copyright}
|
||||||
<p class="string-HTML">{copyright}</p>
|
<p class="string-HTML">{copyright}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>{$LL.menu.about.copyrights.tileset.empty()}</p>
|
||||||
The map creator did not declare a copyright for the tilesets. This doesn't mean that those tilesets
|
|
||||||
have no license.
|
|
||||||
</p>
|
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
<h3 class="nes-pointer hoverable" on:click={() => (expandedAudioCopyright = !expandedAudioCopyright)}>
|
<h3 class="nes-pointer hoverable" on:click={() => (expandedAudioCopyright = !expandedAudioCopyright)}>
|
||||||
Copyrights of audio files
|
{$LL.menu.about.copyrights.audio.title()}
|
||||||
</h3>
|
</h3>
|
||||||
<section hidden={!expandedAudioCopyright}>
|
<section hidden={!expandedAudioCopyright}>
|
||||||
{#each audioCopyright as copyright}
|
{#each audioCopyright as copyright}
|
||||||
<p class="string-HTML">{copyright}</p>
|
<p class="string-HTML">{copyright}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p>{$LL.menu.about.copyrights.audio.empty()}</p>
|
||||||
The map creator did not declare a copyright for audio files. This doesn't mean that those tilesets
|
|
||||||
have no license.
|
|
||||||
</p>
|
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.string-HTML {
|
.string-HTML {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
@ -129,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.about-room-main {
|
div.about-room-main {
|
||||||
section.container-overflow {
|
section.container-overflow {
|
||||||
height: calc(100% - 120px);
|
height: calc(100% - 120px);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import uploadFile from "../images/music-file.svg";
|
import uploadFile from "../images/music-file.svg";
|
||||||
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
interface EventTargetFiles extends EventTarget {
|
interface EventTargetFiles extends EventTarget {
|
||||||
files: Array<File>;
|
files: Array<File>;
|
||||||
@ -76,7 +77,7 @@
|
|||||||
<img
|
<img
|
||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
src={uploadFile}
|
src={uploadFile}
|
||||||
alt="Upload a file"
|
alt={$LL.menu.globalAudio.uploadInfo()}
|
||||||
on:click|preventDefault={() => {
|
on:click|preventDefault={() => {
|
||||||
fileInput.click();
|
fileInput.click();
|
||||||
}}
|
}}
|
||||||
@ -85,7 +86,7 @@
|
|||||||
<p>{fileName} : {fileSize}</p>
|
<p>{fileName} : {fileSize}</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if errorFile}
|
{#if errorFile}
|
||||||
<p class="err">No file selected. You need to upload a file before sending it.</p>
|
<p class="err">{$LL.menu.globalAudio.error()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { contactPageStore } from "../../Stores/MenuStore";
|
||||||
|
|
||||||
function goToGettingStarted() {
|
function goToGettingStarted() {
|
||||||
const sparkHost = "https://workadventu.re/getting-started";
|
const sparkHost = "https://workadventu.re/getting-started";
|
||||||
window.open(sparkHost, "_blank");
|
window.open(sparkHost, "_blank");
|
||||||
@ -8,25 +11,24 @@
|
|||||||
const sparkHost = "https://workadventu.re/map-building/";
|
const sparkHost = "https://workadventu.re/map-building/";
|
||||||
window.open(sparkHost, "_blank");
|
window.open(sparkHost, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
import { contactPageStore } from "../../Stores/MenuStore";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="create-map-main">
|
<div class="create-map-main">
|
||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<section>
|
<section>
|
||||||
<h3>Getting started</h3>
|
<h3>{$LL.menu.contact.gettingStarted.title()}</h3>
|
||||||
<p>
|
<p>{$LL.menu.contact.gettingStarted.description()}</p>
|
||||||
WorkAdventure allows you to create an online space to communicate spontaneously with others. And it all
|
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}
|
||||||
starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
>{$LL.menu.contact.gettingStarted.title()}</button
|
||||||
</p>
|
>
|
||||||
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Create your map</h3>
|
<h3>{$LL.menu.contact.createMap.title()}</h3>
|
||||||
<p>You can also create your own custom map by following the step of the documentation.</p>
|
<p>{$LL.menu.contact.createMap.description()}</p>
|
||||||
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
<button type="button" class="nes-btn" on:click={goToBuildingMap}
|
||||||
|
>{$LL.menu.contact.createMap.title()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<iframe
|
<iframe
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import TextGlobalMessage from "./TextGlobalMessage.svelte";
|
import TextGlobalMessage from "./TextGlobalMessage.svelte";
|
||||||
import AudioGlobalMessage from "./AudioGlobalMessage.svelte";
|
import AudioGlobalMessage from "./AudioGlobalMessage.svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let handleSendText: { sendTextMessage(broadcast: boolean): void };
|
let handleSendText: { sendTextMessage(broadcast: boolean): void };
|
||||||
let handleSendAudio: { sendAudioMessage(broadcast: boolean): Promise<void> };
|
let handleSendAudio: { sendAudioMessage(broadcast: boolean): Promise<void> };
|
||||||
@ -35,14 +36,14 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}"
|
class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={activateInputText}>Text</button
|
on:click|preventDefault={activateInputText}>{$LL.menu.globalMessage.text()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nes-btn {uploadAudioActive ? 'is-disabled' : ''}"
|
class="nes-btn {uploadAudioActive ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={activateUploadAudio}>Audio</button
|
on:click|preventDefault={activateUploadAudio}>{$LL.menu.globalMessage.audio()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -57,15 +58,17 @@
|
|||||||
<div class="global-message-footer">
|
<div class="global-message-footer">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld} />
|
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld} />
|
||||||
<span>Broadcast to all rooms of the world</span>
|
<span>{$LL.menu.globalMessage.warning()}</span>
|
||||||
</label>
|
</label>
|
||||||
<section>
|
<section>
|
||||||
<button class="nes-btn is-primary" on:click|preventDefault={send}>Send</button>
|
<button class="nes-btn is-primary" on:click|preventDefault={send}>{$LL.menu.globalMessage.send()}</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.global-message-main {
|
div.global-message-main {
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -108,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
.global-message-content {
|
.global-message-content {
|
||||||
height: calc(100% - 5px);
|
height: calc(100% - 5px);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
function copyLink() {
|
function copyLink() {
|
||||||
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
|
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
|
||||||
input.focus();
|
input.focus();
|
||||||
@ -21,19 +23,21 @@
|
|||||||
<div class="guest-main">
|
<div class="guest-main">
|
||||||
<section class="container-overflow">
|
<section class="container-overflow">
|
||||||
<section class="share-url not-mobile">
|
<section class="share-url not-mobile">
|
||||||
<h3>Share the link of the room!</h3>
|
<h3>{$LL.menu.invite.description()}</h3>
|
||||||
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
||||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
||||||
</section>
|
</section>
|
||||||
<section class="is-mobile">
|
<section class="is-mobile">
|
||||||
<h3>Share the link of the room!</h3>
|
<h3>{$LL.menu.invite.description()}</h3>
|
||||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.guest-main {
|
div.guest-main {
|
||||||
height: calc(100% - 56px);
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
@ -55,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.guest-main {
|
div.guest-main {
|
||||||
section.share-url.not-mobile {
|
section.share-url.not-mobile {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -14,26 +14,27 @@
|
|||||||
SubMenusInterface,
|
SubMenusInterface,
|
||||||
subMenusStore,
|
subMenusStore,
|
||||||
} from "../../Stores/MenuStore";
|
} from "../../Stores/MenuStore";
|
||||||
|
import type { MenuItem } from "../../Stores/MenuStore";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { get } from "svelte/store";
|
|
||||||
import type { Unsubscriber } from "svelte/store";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let activeSubMenu: string = SubMenusInterface.profile;
|
let activeSubMenu: MenuItem = $subMenusStore[0];
|
||||||
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||||
let props: { url: string; allowApi: boolean };
|
let props: { url: string; allowApi: boolean };
|
||||||
let unsubscriberSubMenuStore: Unsubscriber;
|
let unsubscriberSubMenuStore: Unsubscriber;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||||
if (!get(subMenusStore).includes(activeSubMenu)) {
|
if (!$subMenusStore.includes(activeSubMenu)) {
|
||||||
switchMenu(SubMenusInterface.profile);
|
switchMenu($subMenusStore[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
checkSubMenuToShow();
|
checkSubMenuToShow();
|
||||||
|
|
||||||
switchMenu(SubMenusInterface.profile);
|
switchMenu($subMenusStore[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -42,10 +43,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function switchMenu(menu: string) {
|
function switchMenu(menu: MenuItem) {
|
||||||
if (get(subMenusStore).find((subMenu) => subMenu === menu)) {
|
if (menu.type === "translated") {
|
||||||
activeSubMenu = menu;
|
activeSubMenu = menu;
|
||||||
switch (menu) {
|
switch (menu.key) {
|
||||||
case SubMenusInterface.settings:
|
case SubMenusInterface.settings:
|
||||||
activeComponent = SettingsSubMenu;
|
activeComponent = SettingsSubMenu;
|
||||||
break;
|
break;
|
||||||
@ -64,36 +65,46 @@
|
|||||||
case SubMenusInterface.contact:
|
case SubMenusInterface.contact:
|
||||||
activeComponent = ContactSubMenu;
|
activeComponent = ContactSubMenu;
|
||||||
break;
|
break;
|
||||||
default: {
|
}
|
||||||
const customMenu = customMenuIframe.get(menu);
|
} else {
|
||||||
|
const customMenu = customMenuIframe.get(menu.label);
|
||||||
if (customMenu !== undefined) {
|
if (customMenu !== undefined) {
|
||||||
props = { url: customMenu.url, allowApi: customMenu.allowApi };
|
props = { url: customMenu.url, allowApi: customMenu.allowApi };
|
||||||
activeComponent = CustomSubMenu;
|
activeComponent = CustomSubMenu;
|
||||||
} else {
|
} else {
|
||||||
sendMenuClickedEvent(menu);
|
sendMenuClickedEvent(menu.label);
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else throw new Error("There is no menu called " + menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateMenuName(menu: MenuItem) {
|
||||||
|
if (menu.type === "scripting") {
|
||||||
|
return menu.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypass the proxy of typesafe for getting the menu name : https://github.com/ivanhofer/typesafe-i18n/issues/156
|
||||||
|
const getMenuName = $LL.menu.sub[menu.key];
|
||||||
|
|
||||||
|
return getMenuName();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} />
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
<div class="menu-container-main">
|
<div class="menu-container-main">
|
||||||
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
|
||||||
<h2>Menu</h2>
|
<h2>{$LL.menu.title()}</h2>
|
||||||
<nav>
|
<nav>
|
||||||
{#each $subMenusStore as submenu}
|
{#each $subMenusStore as submenu}
|
||||||
<button
|
<button
|
||||||
@ -101,19 +112,21 @@
|
|||||||
class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}"
|
class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={() => switchMenu(submenu)}
|
on:click|preventDefault={() => switchMenu(submenu)}
|
||||||
>
|
>
|
||||||
{submenu}
|
{translateMenuName(submenu)}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-submenu-container nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
<div class="menu-submenu-container nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
|
||||||
<button type="button" class="nes-btn is-error close" on:click={closeMenu}>×</button>
|
<button type="button" class="nes-btn is-error close" on:click={closeMenu}>×</button>
|
||||||
<h2>{activeSubMenu}</h2>
|
<h2>{translateMenuName(activeSubMenu)}</h2>
|
||||||
<svelte:component this={activeComponent} {...props} />
|
<svelte:component this={activeComponent} {...props} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.nes-container {
|
.nes-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
@ -125,11 +138,15 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
top: 10%;
|
top: 4%;
|
||||||
|
|
||||||
position: relative;
|
left: 0;
|
||||||
z-index: 80;
|
right: 0;
|
||||||
margin: auto;
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
z-index: 900;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
||||||
@ -162,12 +179,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.menu-container-main {
|
div.menu-container-main {
|
||||||
--size-first-columns-grid: 120px;
|
--size-first-columns-grid: 120px;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
top: 55px;
|
top: 55px;
|
||||||
width: 100%;
|
width: 95%;
|
||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
|
|
||||||
div.menu-nav-sidebar {
|
div.menu-nav-sidebar {
|
||||||
|
@ -9,10 +9,12 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showChat() {
|
function showChat() {
|
||||||
chatVisibilityStore.set(true);
|
chatVisibilityStore.set(true);
|
||||||
}
|
}
|
||||||
@ -20,63 +22,98 @@
|
|||||||
function register() {
|
function register() {
|
||||||
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInvite() {
|
function showInvite() {
|
||||||
showShareLinkMapModalStore.set(true);
|
showShareLinkMapModalStore.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window />
|
<svelte:window />
|
||||||
|
|
||||||
<main class="menuIcon">
|
<main class="menuIcon noselect">
|
||||||
{#if $limitMapStore}
|
{#if $limitMapStore}
|
||||||
<img src={logoInvite} alt="open menu" class="nes-pointer" on:click|preventDefault={showInvite} />
|
<img
|
||||||
<img src={logoRegister} alt="open menu" class="nes-pointer" on:click|preventDefault={register} />
|
src={logoInvite}
|
||||||
|
alt={$LL.menu.icon.open.invite()}
|
||||||
|
class="nes-pointer"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={showInvite}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={logoRegister}
|
||||||
|
alt={$LL.menu.icon.open.register()}
|
||||||
|
class="nes-pointer"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={register}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu} />
|
<img
|
||||||
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat} />
|
src={logoWA}
|
||||||
|
alt={$LL.menu.icon.open.menu()}
|
||||||
|
class="nes-pointer"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={showMenu}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={logoTalk}
|
||||||
|
alt={$LL.menu.icon.open.chat()}
|
||||||
|
class="nes-pointer"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={showChat}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.menuIcon {
|
.menuIcon {
|
||||||
display: inline-grid;
|
display: flex;
|
||||||
z-index: 90;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20%;
|
||||||
|
z-index: 800;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 25px;
|
|
||||||
|
img {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 60px;
|
||||||
|
padding-top: 0;
|
||||||
|
margin: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuIcon img:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.menuIcon {
|
||||||
|
margin-top: 10%;
|
||||||
img {
|
img {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
margin: 3px;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.menuIcon img:hover {
|
.menuIcon img:hover {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
.menuIcon {
|
.menuIcon {
|
||||||
display: inline-grid;
|
|
||||||
z-index: 90;
|
|
||||||
position: relative;
|
|
||||||
margin: 25px;
|
|
||||||
img {
|
|
||||||
pointer-events: auto;
|
|
||||||
width: 60px;
|
|
||||||
padding-top: 0;
|
|
||||||
margin: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.menuIcon img:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
|
||||||
.menuIcon {
|
|
||||||
margin: 3px;
|
|
||||||
img {
|
img {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||||
import Woka from "../Woka/Woka.svelte";
|
import Woka from "../Woka/Woka.svelte";
|
||||||
import Companion from "../Companion/Companion.svelte";
|
import Companion from "../Companion/Companion.svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
function disableMenuStores() {
|
function disableMenuStores() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
@ -62,20 +63,20 @@
|
|||||||
<div class="submenu">
|
<div class="submenu">
|
||||||
<section>
|
<section>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
||||||
<img src={btnProfileSubMenuIdentity} alt="Edit your name" />
|
<img src={btnProfileSubMenuIdentity} alt={$LL.menu.profile.edit.name()} />
|
||||||
<span class="btn-hover">Edit your name</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.name()}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
||||||
<Woka userId={-1} placeholderSrc="" width="26px" height="26px" />
|
<Woka userId={-1} placeholderSrc="" width="26px" height="26px" />
|
||||||
<span class="btn-hover">Edit your WOKA</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.woka()}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
||||||
<Companion userId={-1} placeholderSrc={btnProfileSubMenuCompanion} width="26px" height="26px" />
|
<Companion userId={-1} placeholderSrc={btnProfileSubMenuCompanion} width="26px" height="26px" />
|
||||||
<span class="btn-hover">Edit your companion</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.companion()}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
||||||
<img src={btnProfileSubMenuCamera} alt="Edit your camera" />
|
<img src={btnProfileSubMenuCamera} alt={$LL.menu.profile.edit.camera()} />
|
||||||
<span class="btn-hover">Edit your camera</span>
|
<span class="btn-hover">{$LL.menu.profile.edit.camera()}</span>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -88,17 +89,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
<button type="button" class="nes-btn" on:click|preventDefault={logOut}
|
||||||
|
>{$LL.menu.profile.logout()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<section>
|
<section>
|
||||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
<a type="button" class="nes-btn" href="/login">{$LL.menu.profile.login()}</a>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.customize-main {
|
div.customize-main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -158,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.customize-main.content section button {
|
div.customize-main.content section button {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
|
||||||
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
||||||
|
import LL, { locale } from "../../i18n/i18n-svelte";
|
||||||
|
import type { Locales } from "../../i18n/i18n-types";
|
||||||
|
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||||
@ -11,14 +14,17 @@
|
|||||||
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
|
let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests();
|
||||||
let valueGame: number = localUserStore.getGameQualityValue();
|
let valueGame: number = localUserStore.getGameQualityValue();
|
||||||
let valueVideo: number = localUserStore.getVideoQualityValue();
|
let valueVideo: number = localUserStore.getVideoQualityValue();
|
||||||
|
let valueLocale: string = $locale;
|
||||||
let previewValueGame = valueGame;
|
let previewValueGame = valueGame;
|
||||||
let previewValueVideo = valueVideo;
|
let previewValueVideo = valueVideo;
|
||||||
|
let previewValueLocale = valueLocale;
|
||||||
|
|
||||||
function saveSetting() {
|
function saveSetting() {
|
||||||
if (valueGame !== previewValueGame) {
|
let change = false;
|
||||||
previewValueGame = valueGame;
|
|
||||||
localUserStore.setGameQualityValue(valueGame);
|
if (valueLocale !== previewValueLocale) {
|
||||||
window.location.reload();
|
previewValueLocale = valueLocale;
|
||||||
|
setCurrentLocale(valueLocale as Locales);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueVideo !== previewValueVideo) {
|
if (valueVideo !== previewValueVideo) {
|
||||||
@ -26,6 +32,16 @@
|
|||||||
videoConstraintStore.setFrameRate(valueVideo);
|
videoConstraintStore.setFrameRate(valueVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (valueGame !== previewValueGame) {
|
||||||
|
previewValueGame = valueGame;
|
||||||
|
localUserStore.setGameQualityValue(valueGame);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
closeMenu();
|
closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,38 +85,80 @@
|
|||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMobile = isMediaBreakpointUp("md");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
||||||
<section>
|
<section>
|
||||||
<h3>Game quality</h3>
|
<h3>{$LL.menu.settings.gameQuality.title()}</h3>
|
||||||
<div class="nes-select is-dark">
|
<div class="nes-select is-dark">
|
||||||
<select bind:value={valueGame}>
|
<select bind:value={valueGame}>
|
||||||
<option value={120}>{isMobile() ? "High (120 fps)" : "High video quality (120 fps)"}</option>
|
<option value={120}
|
||||||
<option value={60}
|
>{isMobile
|
||||||
>{isMobile() ? "Medium (60 fps)" : "Medium video quality (60 fps, recommended)"}</option
|
? $LL.menu.settings.gameQuality.short.high()
|
||||||
|
: $LL.menu.settings.gameQuality.long.high()}</option
|
||||||
|
>
|
||||||
|
<option value={60}
|
||||||
|
>{isMobile
|
||||||
|
? $LL.menu.settings.gameQuality.short.medium()
|
||||||
|
: $LL.menu.settings.gameQuality.long.medium()}</option
|
||||||
|
>
|
||||||
|
<option value={40}
|
||||||
|
>{isMobile
|
||||||
|
? $LL.menu.settings.gameQuality.short.small()
|
||||||
|
: $LL.menu.settings.gameQuality.long.small()}</option
|
||||||
|
>
|
||||||
|
<option value={20}
|
||||||
|
>{isMobile
|
||||||
|
? $LL.menu.settings.gameQuality.short.minimum()
|
||||||
|
: $LL.menu.settings.gameQuality.long.minimum()}</option
|
||||||
>
|
>
|
||||||
<option value={40}>{isMobile() ? "Minimum (40 fps)" : "Minimum video quality (40 fps)"}</option>
|
|
||||||
<option value={20}>{isMobile() ? "Small (20 fps)" : "Small video quality (20 fps)"}</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h3>Video quality</h3>
|
<h3>{$LL.menu.settings.videoQuality.title()}</h3>
|
||||||
<div class="nes-select is-dark">
|
<div class="nes-select is-dark">
|
||||||
<select bind:value={valueVideo}>
|
<select bind:value={valueVideo}>
|
||||||
<option value={30}>{isMobile() ? "High (30 fps)" : "High video quality (30 fps)"}</option>
|
<option value={30}
|
||||||
<option value={20}
|
>{isMobile
|
||||||
>{isMobile() ? "Medium (20 fps)" : "Medium video quality (20 fps, recommended)"}</option
|
? $LL.menu.settings.videoQuality.short.high()
|
||||||
|
: $LL.menu.settings.videoQuality.long.high()}</option
|
||||||
>
|
>
|
||||||
<option value={10}>{isMobile() ? "Minimum (10 fps)" : "Minimum video quality (10 fps)"}</option>
|
<option value={20}
|
||||||
<option value={5}>{isMobile() ? "Small (5 fps)" : "Small video quality (5 fps)"}</option>
|
>{isMobile
|
||||||
|
? $LL.menu.settings.videoQuality.short.medium()
|
||||||
|
: $LL.menu.settings.videoQuality.long.medium()}</option
|
||||||
|
>
|
||||||
|
<option value={10}
|
||||||
|
>{isMobile
|
||||||
|
? $LL.menu.settings.videoQuality.short.small()
|
||||||
|
: $LL.menu.settings.videoQuality.long.small()}</option
|
||||||
|
>
|
||||||
|
<option value={5}
|
||||||
|
>{isMobile
|
||||||
|
? $LL.menu.settings.videoQuality.short.minimum()
|
||||||
|
: $LL.menu.settings.videoQuality.long.minimum()}</option
|
||||||
|
>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h3>{$LL.menu.settings.language.title()}</h3>
|
||||||
|
<div class="nes-select is-dark">
|
||||||
|
<select class="languages-switcher" bind:value={valueLocale}>
|
||||||
|
{#each displayableLocales as locale (locale.id)}
|
||||||
|
<option value={locale.id}>{`${locale.language} (${locale.country})`}</option>
|
||||||
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section-save">
|
<section class="settings-section-save">
|
||||||
<p>(Saving these settings will restart the game)</p>
|
<p>{$LL.menu.settings.save.warning()}</p>
|
||||||
<button type="button" class="nes-btn is-primary" on:click|preventDefault={saveSetting}>Save</button>
|
<button type="button" class="nes-btn is-primary" on:click|preventDefault={saveSetting}
|
||||||
|
>{$LL.menu.settings.save.button()}</button
|
||||||
|
>
|
||||||
</section>
|
</section>
|
||||||
<section class="settings-section-noSaveOption">
|
<section class="settings-section-noSaveOption">
|
||||||
<label>
|
<label>
|
||||||
@ -110,7 +168,7 @@
|
|||||||
bind:checked={fullscreen}
|
bind:checked={fullscreen}
|
||||||
on:change={changeFullscreen}
|
on:change={changeFullscreen}
|
||||||
/>
|
/>
|
||||||
<span>Fullscreen</span>
|
<span>{$LL.menu.settings.fullscreen()}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@ -119,7 +177,7 @@
|
|||||||
bind:checked={notification}
|
bind:checked={notification}
|
||||||
on:change={changeNotification}
|
on:change={changeNotification}
|
||||||
/>
|
/>
|
||||||
<span>Notifications</span>
|
<span>{$LL.menu.settings.notifications()}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@ -128,7 +186,7 @@
|
|||||||
bind:checked={forceCowebsiteTrigger}
|
bind:checked={forceCowebsiteTrigger}
|
||||||
on:change={changeForceCowebsiteTrigger}
|
on:change={changeForceCowebsiteTrigger}
|
||||||
/>
|
/>
|
||||||
<span>Always ask before opening websites and Jitsi Meet rooms</span>
|
<span>{$LL.menu.settings.cowebsiteTrigger()}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@ -137,12 +195,14 @@
|
|||||||
bind:checked={ignoreFollowRequests}
|
bind:checked={ignoreFollowRequests}
|
||||||
on:change={changeIgnoreFollowRequests}
|
on:change={changeIgnoreFollowRequests}
|
||||||
/>
|
/>
|
||||||
<span>Ignore requests to follow other users</span>
|
<span>{$LL.menu.settings.ignoreFollowRequest()}</span>
|
||||||
</label>
|
</label>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.settings-main {
|
div.settings-main {
|
||||||
height: calc(100% - 40px);
|
height: calc(100% - 40px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -174,9 +234,13 @@
|
|||||||
margin: 0 0 15px;
|
margin: 0 0 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.languages-switcher option {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.settings-main {
|
div.settings-main {
|
||||||
section {
|
section {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
|
||||||
import type { Quill } from "quill";
|
import type { Quill } from "quill";
|
||||||
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
//toolbar
|
//toolbar
|
||||||
const toolbarOptions = [
|
const toolbarOptions = [
|
||||||
@ -58,7 +59,7 @@
|
|||||||
const { default: Quill } = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const { default: Quill } = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
quill = new Quill(QUILL_EDITOR, {
|
quill = new Quill(QUILL_EDITOR, {
|
||||||
placeholder: "Enter your message here...",
|
placeholder: $LL.menu.globalMessage.enter(),
|
||||||
theme: "snow",
|
theme: "snow",
|
||||||
modules: {
|
modules: {
|
||||||
toolbar: toolbarOptions,
|
toolbar: toolbarOptions,
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 500;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
|
@ -75,6 +75,7 @@
|
|||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 450;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||||
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
||||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { srcObject } from "./Video/utils";
|
import { srcObject } from "./Video/utils";
|
||||||
|
import LL from "../i18n/i18n-svelte";
|
||||||
|
|
||||||
let stream: MediaStream | null;
|
let stream: MediaStream | null;
|
||||||
|
|
||||||
@ -22,15 +23,75 @@
|
|||||||
isSilent = value;
|
isSilent = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let cameraContainer: HTMLDivElement;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
cameraContainer.addEventListener("transitionend", () => {
|
||||||
|
if (cameraContainer.classList.contains("hide")) {
|
||||||
|
cameraContainer.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraContainer.addEventListener("transitionstart", () => {
|
||||||
|
if (!cameraContainer.classList.contains("hide")) {
|
||||||
|
cameraContainer.style.visibility = "visible";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onDestroy(unsubscribeIsSilent);
|
onDestroy(unsubscribeIsSilent);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
<div class="video-container div-myCamVideo" class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
|
class="nes-container is-rounded my-cam-video-container"
|
||||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
class:hide={($localStreamStore.type !== "success" || !$obtainedMediaConstraintStore.video) && !isSilent}
|
||||||
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline />
|
bind:this={cameraContainer}
|
||||||
|
>
|
||||||
|
{#if isSilent}
|
||||||
|
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
|
||||||
|
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||||
|
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
|
||||||
<SoundMeterWidget {stream} />
|
<SoundMeterWidget {stream} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
<div class="is-silent" class:hide={isSilent}>Silent zone</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.my-cam-video-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 30px;
|
||||||
|
max-height: 20%;
|
||||||
|
transition: transform 1000ms;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
background-clip: content-box;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 0;
|
||||||
|
z-index: 250;
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-cam-video-container.hide {
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-cam-video {
|
||||||
|
background-color: #00000099;
|
||||||
|
max-height: 20vh;
|
||||||
|
max-width: max(25vw, 150px);
|
||||||
|
width: 100%;
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-silent {
|
||||||
|
font-size: 2em;
|
||||||
|
color: white;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { blackListManager } from "../../WebRtc/BlackListManager";
|
import { blackListManager } from "../../WebRtc/BlackListManager";
|
||||||
import { showReportScreenStore, userReportEmpty } from "../../Stores/ShowReportScreenStore";
|
import { showReportScreenStore, userReportEmpty } from "../../Stores/ShowReportScreenStore";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let userUUID: string | undefined;
|
export let userUUID: string | undefined;
|
||||||
export let userName: string;
|
export let userName: string;
|
||||||
@ -29,10 +30,10 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="block-container">
|
<div class="block-container">
|
||||||
<h3>Block</h3>
|
<h3>{$LL.report.block.title()}</h3>
|
||||||
<p>Block any communication from and to {userName}. This can be reverted.</p>
|
<p>{$LL.report.block.content({ userName })}</p>
|
||||||
<button type="button" class="nes-btn is-error" on:click|preventDefault={blockUser}>
|
<button type="button" class="nes-btn is-error" on:click|preventDefault={blockUser}>
|
||||||
{userIsBlocked ? "Unblock this user" : "Block this user"}
|
{userIsBlocked ? $LL.report.block.unblock() : $LL.report.block.block()}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import { playersStore } from "../../Stores/PlayersStore";
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
let blockActive = true;
|
let blockActive = true;
|
||||||
let reportActive = !blockActive;
|
let reportActive = !blockActive;
|
||||||
@ -59,7 +60,7 @@
|
|||||||
|
|
||||||
<div class="report-menu-main nes-container is-rounded">
|
<div class="report-menu-main nes-container is-rounded">
|
||||||
<section class="report-menu-title">
|
<section class="report-menu-title">
|
||||||
<h2>Moderate {userName}</h2>
|
<h2>{$LL.report.moderate.title({ userName })}</h2>
|
||||||
<section class="justify-center">
|
<section class="justify-center">
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
|
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
|
||||||
</section>
|
</section>
|
||||||
@ -69,14 +70,14 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nes-btn {blockActive ? 'is-disabled' : ''}"
|
class="nes-btn {blockActive ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={activateBlock}>Block</button
|
on:click|preventDefault={activateBlock}>{$LL.report.moderate.block()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
<section class="justify-center">
|
<section class="justify-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="nes-btn {reportActive ? 'is-disabled' : ''}"
|
class="nes-btn {reportActive ? 'is-disabled' : ''}"
|
||||||
on:click|preventDefault={activateReport}>Report</button
|
on:click|preventDefault={activateReport}>{$LL.report.moderate.report()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
@ -86,7 +87,7 @@
|
|||||||
{:else if reportActive}
|
{:else if reportActive}
|
||||||
<ReportSubMenu {userUUID} />
|
<ReportSubMenu {userUUID} />
|
||||||
{:else}
|
{:else}
|
||||||
<p>ERROR : There is no action selected.</p>
|
<p>{$LL.report.moderate.noSelect()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -107,12 +108,16 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
z-index: 650;
|
||||||
position: relative;
|
position: absolute;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
top: 10vh;
|
top: 4%;
|
||||||
margin: auto;
|
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
section.report-menu-title {
|
section.report-menu-title {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -136,13 +141,4 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
div.report-menu-main {
|
|
||||||
top: 21vh;
|
|
||||||
height: 60vh;
|
|
||||||
width: 100vw;
|
|
||||||
font-size: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { showReportScreenStore, userReportEmpty } from "../../Stores/ShowReportScreenStore";
|
import { showReportScreenStore, userReportEmpty } from "../../Stores/ShowReportScreenStore";
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let userUUID: string | undefined;
|
export let userUUID: string | undefined;
|
||||||
let reportMessage: string;
|
let reportMessage: string;
|
||||||
@ -22,18 +23,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="report-container-main">
|
<div class="report-container-main">
|
||||||
<h3>Report</h3>
|
<h3>{$LL.report.title()}</h3>
|
||||||
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
|
<p>{$LL.report.content()}</p>
|
||||||
<form>
|
<form>
|
||||||
<section>
|
<section>
|
||||||
<label>
|
<label>
|
||||||
<span>Your message: </span>
|
<span>{$LL.report.message.title()}</span>
|
||||||
<textarea type="text" class="nes-textarea" bind:value={reportMessage} />
|
<textarea type="text" class="nes-textarea" bind:value={reportMessage} />
|
||||||
</label>
|
</label>
|
||||||
<p hidden={hiddenError}>Report message cannot to be empty.</p>
|
<p hidden={hiddenError}>{$LL.report.message.empty()}</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<button type="submit" class="nes-btn is-error" on:click={submitReport}>Report this user</button>
|
<button type="submit" class="nes-btn is-error" on:click={submitReport}>{$LL.report.submit()}</button>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
|
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
|
||||||
|
|
||||||
@ -25,7 +26,7 @@
|
|||||||
|
|
||||||
<form class="selectCompanionScene">
|
<form class="selectCompanionScene">
|
||||||
<section class="text-center">
|
<section class="text-center">
|
||||||
<h2>Select your companion</h2>
|
<h2>{$LL.companion.select.title()}</h2>
|
||||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}>
|
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
@ -35,17 +36,19 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="action">
|
<section class="action">
|
||||||
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}
|
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}
|
||||||
>No companion</button
|
>{$LL.companion.select.any()}</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCompanionSceneFormSubmit nes-btn is-primary"
|
class="selectCompanionSceneFormSubmit nes-btn is-primary"
|
||||||
on:click|preventDefault={selectCompanion}>Continue</button
|
on:click|preventDefault={selectCompanion}>{$LL.companion.select.continue()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.selectCompanionScene {
|
form.selectCompanionScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@ -84,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.selectCompanionScene button.selectCharacterButtonLeft {
|
form.selectCompanionScene button.selectCharacterButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly, fade } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let message: Message;
|
export let message: Message;
|
||||||
|
|
||||||
@ -12,6 +14,8 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
timeToRead();
|
timeToRead();
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
gameScene.playSound("audio-report-message");
|
||||||
});
|
});
|
||||||
|
|
||||||
function timeToRead() {
|
function timeToRead() {
|
||||||
@ -37,7 +41,8 @@
|
|||||||
out:fade={{ duration: 200 }}
|
out:fade={{ duration: 200 }}
|
||||||
>
|
>
|
||||||
<h2 class="title-ban-message">
|
<h2 class="title-ban-message">
|
||||||
<img src="resources/logos/report.svg" alt="***" /> Important message
|
<img src="resources/logos/report.svg" alt="***" />
|
||||||
|
{$LL.warning.importantMessage()}
|
||||||
<img src="resources/logos/report.svg" alt="***" />
|
<img src="resources/logos/report.svg" alt="***" />
|
||||||
</h2>
|
</h2>
|
||||||
<div class="content-ban-message">
|
<div class="content-ban-message">
|
||||||
@ -51,18 +56,19 @@
|
|||||||
on:click|preventDefault={closeBanMessage}>{nameButton}</button
|
on:click|preventDefault={closeBanMessage}>{nameButton}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
|
||||||
<audio id="report-message" autoplay>
|
|
||||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3" />
|
|
||||||
</audio>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-ban-message {
|
div.main-ban-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 15vh;
|
top: 4%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 850;
|
||||||
|
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
|
@ -11,3 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.main-ban-message-container {
|
||||||
|
z-index: 800;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -42,14 +42,17 @@
|
|||||||
div.main-text-message {
|
div.main-text-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
max-height: 25vh;
|
max-height: 25%;
|
||||||
width: 80vw;
|
width: 60%;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-bottom: 16px;
|
top: 6%;
|
||||||
margin-top: 0;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
z-index: 240;
|
||||||
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
|
@ -15,7 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-text-message-container {
|
.main-text-message-container {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
z-index: 800;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import megaphoneImg from "./images/megaphone.svg";
|
import megaphoneImg from "./images/megaphone.svg";
|
||||||
import { soundPlayingStore } from "../../Stores/SoundPlayingStore";
|
import { soundPlayingStore } from "../../Stores/SoundPlayingStore";
|
||||||
import { afterUpdate } from "svelte";
|
import { afterUpdate } from "svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let url: string;
|
export let url: string;
|
||||||
let audio: HTMLAudioElement;
|
let audio: HTMLAudioElement;
|
||||||
@ -18,7 +19,7 @@
|
|||||||
|
|
||||||
<div class="audio-playing" transition:fly={{ x: 210, duration: 500 }}>
|
<div class="audio-playing" transition:fly={{ x: 210, duration: 500 }}>
|
||||||
<img src={megaphoneImg} alt="Audio playing" />
|
<img src={megaphoneImg} alt="Audio playing" />
|
||||||
<p>Audio message</p>
|
<p>{$LL.audio.message()}</p>
|
||||||
<audio bind:this={audio} src={url} on:ended={soundEnded}>
|
<audio bind:this={audio} src={url} on:ended={soundEnded}>
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
</audio>
|
</audio>
|
||||||
@ -36,6 +37,7 @@
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
border-radius: 30px 0 0 30px;
|
border-radius: 30px 0 0 30px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
z-index: 750;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { errorStore, hasClosableMessagesInErrorStore } from "../../Stores/ErrorStore";
|
import { errorStore, hasClosableMessagesInErrorStore } from "../../Stores/ErrorStore";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
function close(): boolean {
|
function close(): boolean {
|
||||||
errorStore.clearClosableMessages();
|
errorStore.clearClosableMessages();
|
||||||
@ -8,7 +9,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="error-div nes-container is-dark is-rounded" open>
|
<div class="error-div nes-container is-dark is-rounded" open>
|
||||||
<p class="nes-text is-error title">Error</p>
|
<p class="nes-text is-error title">{$LL.error.error()}</p>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
{#each $errorStore as error}
|
{#each $errorStore as error}
|
||||||
<p>{error.message}</p>
|
<p>{error.message}</p>
|
||||||
@ -24,11 +25,17 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.error-div {
|
div.error-div {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
margin-top: 10vh;
|
margin-top: 4%;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
z-index: 230;
|
||||||
|
height: auto !important;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
.button-bar {
|
.button-bar {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1,15 +1,33 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
import { srcObject } from "./utils";
|
import { srcObject } from "./utils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: ScreenSharingLocalMedia;
|
export let peer: ScreenSharingLocalMedia;
|
||||||
let stream = peer.stream;
|
let stream = peer.stream;
|
||||||
export let cssClass: string | undefined;
|
export let cssClass: string | undefined;
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container {cssClass ? cssClass : ''}" class:hide={!stream}>
|
<div class="video-container {cssClass ? cssClass : ''}" class:hide={!stream}>
|
||||||
{#if stream}
|
{#if stream}
|
||||||
<video use:srcObject={stream} autoplay muted playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
|
use:srcObject={stream}
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,14 +7,110 @@
|
|||||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
export let streamable: Streamable;
|
export let streamable: Streamable;
|
||||||
|
export let isHightlighted = false;
|
||||||
|
export let isClickable = false;
|
||||||
|
export let mozaicFullWidth = false;
|
||||||
|
export let mozaicQuarter = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="media-container">
|
<div
|
||||||
|
class="media-container nes-container is-rounded {isHightlighted ? 'hightlighted' : ''}"
|
||||||
|
class:clickable={isClickable}
|
||||||
|
class:mozaic-full-width={mozaicFullWidth}
|
||||||
|
class:mozaic-quarter={mozaicQuarter}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
{#if streamable instanceof VideoPeer}
|
{#if streamable instanceof VideoPeer}
|
||||||
<VideoMediaBox peer={streamable} />
|
<VideoMediaBox peer={streamable} clickable={isClickable} />
|
||||||
{:else if streamable instanceof ScreenSharingPeer}
|
{:else if streamable instanceof ScreenSharingPeer}
|
||||||
<ScreenSharingMediaBox peer={streamable} />
|
<ScreenSharingMediaBox peer={streamable} clickable={isClickable} />
|
||||||
{:else}
|
{:else}
|
||||||
<LocalStreamMediaBox peer={streamable} cssClass="" />
|
<LocalStreamMediaBox peer={streamable} clickable={isClickable} cssClass="" />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.media-container {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 4%;
|
||||||
|
margin-bottom: 4%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s,
|
||||||
|
max-width 0.2s;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 85%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: 2%;
|
||||||
|
margin-bottom: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hightlighted {
|
||||||
|
margin-top: 0% !important;
|
||||||
|
margin-bottom: 0% !important;
|
||||||
|
margin-left: 0% !important;
|
||||||
|
|
||||||
|
max-height: 100% !important;
|
||||||
|
max-width: 96% !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: 0% !important;
|
||||||
|
margin-bottom: 0% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mozaic-full-width {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 95%;
|
||||||
|
margin-left: 3%;
|
||||||
|
margin-right: 3%;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mozaic-quarter {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 95%;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clickable {
|
||||||
|
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-only(md) {
|
||||||
|
.media-container {
|
||||||
|
margin-top: 10%;
|
||||||
|
margin-bottom: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { afterUpdate } from "svelte";
|
|
||||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
|
||||||
import MediaBox from "./MediaBox.svelte";
|
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
biggestAvailableAreaStore.recompute();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="main-section">
|
|
||||||
{#if $videoFocusStore}
|
|
||||||
{#key $videoFocusStore.uniqueId}
|
|
||||||
<MediaBox streamable={$videoFocusStore} />
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<aside class="sidebar">
|
|
||||||
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
|
||||||
{#if peer !== $videoFocusStore}
|
|
||||||
<MediaBox streamable={peer} />
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</aside>
|
|
@ -1,12 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { getColorByString, srcObject } from "./utils";
|
import { getColorByString, srcObject } from "./utils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: ScreenSharingPeer;
|
export let peer: ScreenSharingPeer;
|
||||||
let streamStore = peer.streamStore;
|
let streamStore = peer.streamStore;
|
||||||
let name = peer.userName;
|
let name = peer.userName;
|
||||||
let statusStore = peer.statusStore;
|
let statusStore = peer.statusStore;
|
||||||
|
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
|
||||||
|
if (peer) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
@ -16,11 +30,17 @@
|
|||||||
{#if $statusStore === "error"}
|
{#if $statusStore === "error"}
|
||||||
<div class="rtc-error" />
|
<div class="rtc-error" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $streamStore === null}
|
{#if $streamStore !== null}
|
||||||
<i style="background-color: {getColorByString(name)};">{name}</i>
|
<i class="container">
|
||||||
{:else}
|
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||||
|
</i>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
|
use:srcObject={$streamStore}
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -29,5 +49,10 @@
|
|||||||
video {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
i {
|
||||||
|
span {
|
||||||
|
padding: 2px 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -4,11 +4,17 @@
|
|||||||
import microphoneCloseImg from "../images/microphone-close.svg";
|
import microphoneCloseImg from "../images/microphone-close.svg";
|
||||||
import reportImg from "./images/report.svg";
|
import reportImg from "./images/report.svg";
|
||||||
import blockSignImg from "./images/blockSign.svg";
|
import blockSignImg from "./images/blockSign.svg";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||||
import { getColorByString, srcObject } from "./utils";
|
import { getColorByString, srcObject } from "./utils";
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
import Woka from "../Woka/Woka.svelte";
|
import Woka from "../Woka/Woka.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { isMediaBreakpointOnly } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: VideoPeer;
|
export let peer: VideoPeer;
|
||||||
let streamStore = peer.streamStore;
|
let streamStore = peer.streamStore;
|
||||||
@ -19,9 +25,37 @@
|
|||||||
function openReport(peer: VideoPeer): void {
|
function openReport(peer: VideoPeer): void {
|
||||||
showReportScreenStore.set({ userId: peer.userId, userName: peer.userName });
|
showReportScreenStore.set({ userId: peer.userId, userName: peer.userName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
let videoContainer: HTMLDivElement;
|
||||||
|
let minimized = isMediaBreakpointOnly("md");
|
||||||
|
|
||||||
|
if (peer) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
minimized = isMediaBreakpointOnly("md");
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(videoContainer);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container">
|
<div
|
||||||
|
class="video-container"
|
||||||
|
class:no-clikable={!clickable}
|
||||||
|
bind:this={videoContainer}
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
>
|
||||||
{#if $statusStore === "connecting"}
|
{#if $statusStore === "connecting"}
|
||||||
<div class="connecting-spinner" />
|
<div class="connecting-spinner" />
|
||||||
{/if}
|
{/if}
|
||||||
@ -29,43 +63,46 @@
|
|||||||
<div class="rtc-error" />
|
<div class="rtc-error" />
|
||||||
{/if}
|
{/if}
|
||||||
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
||||||
<i
|
<i class="container">
|
||||||
class="container {!$constraintStore || $constraintStore.video === false ? '' : 'minimized'}"
|
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||||
style="background-color: {getColorByString(name)};"
|
|
||||||
>
|
|
||||||
<span>{peer.userName}</span>
|
|
||||||
<div class="woka-icon"><Woka userId={peer.userId} placeholderSrc={""} /></div>
|
|
||||||
</i>
|
</i>
|
||||||
|
<div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}">
|
||||||
|
<Woka userId={peer.userId} placeholderSrc={""} />
|
||||||
|
</div>
|
||||||
<!-- {/if} -->
|
<!-- {/if} -->
|
||||||
{#if $constraintStore && $constraintStore.audio === false}
|
{#if $constraintStore && $constraintStore.audio === false}
|
||||||
<img src={microphoneCloseImg} class="active" alt="Muted" />
|
<img
|
||||||
|
src={microphoneCloseImg}
|
||||||
|
class="active noselect"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
alt="Muted"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="report" on:click={() => openReport(peer)}>
|
<button class="report" on:click={() => openReport(peer)}>
|
||||||
<img alt="Report this user" src={reportImg} />
|
<img alt="Report this user" draggable="false" on:dragstart|preventDefault={noDrag} src={reportImg} />
|
||||||
<span>Report/Block</span>
|
<span class="noselect">Report/Block</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
<img src={blockSignImg} class="block-logo" alt="Block" />
|
class:no-video={!$constraintStore || $constraintStore.video === false}
|
||||||
|
use:srcObject={$streamStore}
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
|
<img src={blockSignImg} draggable="false" on:dragstart|preventDefault={noDrag} class="block-logo" alt="Block" />
|
||||||
{#if $constraintStore && $constraintStore.audio !== false}
|
{#if $constraintStore && $constraintStore.audio !== false}
|
||||||
<SoundMeterWidget stream={$streamStore} />
|
<SoundMeterWidget stream={$streamStore} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 15px;
|
|
||||||
}
|
}
|
||||||
|
video.no-video {
|
||||||
.minimized {
|
visibility: collapse;
|
||||||
left: auto;
|
|
||||||
transform: scale(0.5);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.woka-icon {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
// import {LayoutMode} from "../../WebRtc/LayoutManager";
|
||||||
import { layoutModeStore } from "../../Stores/StreamableCollectionStore";
|
// import {layoutModeStore} from "../../Stores/StreamableCollectionStore";
|
||||||
import PresentationLayout from "./PresentationLayout.svelte";
|
// import PresentationLayout from "./PresentationLayout.svelte";
|
||||||
import ChatLayout from "./ChatLayout.svelte";
|
// import ChatLayout from "./ChatLayout.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-overlay">
|
<div class="video-overlay">
|
||||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
<!-- {#if $layoutModeStore === LayoutMode.Presentation }
|
||||||
<PresentationLayout />
|
<PresentationLayout />
|
||||||
{:else}
|
{:else}
|
||||||
<ChatLayout />
|
<ChatLayout />
|
||||||
{/if}
|
{/if} -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let visitCardUrl: string;
|
export let visitCardUrl: string;
|
||||||
let w = "500px";
|
let w = "500px";
|
||||||
@ -40,7 +41,7 @@
|
|||||||
/>
|
/>
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<div class="buttonContainer">
|
<div class="buttonContainer">
|
||||||
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
|
<button class="nes-btn is-popUpElement" on:click={closeCard}>{$LL.menu.visitCard.close()}</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
@ -56,6 +57,7 @@
|
|||||||
height: 120px;
|
height: 120px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
|
z-index: 350;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import { userIsAdminStore, limitMapStore } from "../../Stores/GameStore";
|
import { userIsAdminStore, limitMapStore } from "../../Stores/GameStore";
|
||||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
const upgradeLink = ADMIN_URL + "/pricing";
|
const upgradeLink = ADMIN_URL + "/pricing";
|
||||||
const registerLink = ADMIN_URL + "/second-step-register";
|
const registerLink = ADMIN_URL + "/second-step-register";
|
||||||
@ -9,37 +10,45 @@
|
|||||||
|
|
||||||
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
|
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
|
||||||
{#if $userIsAdminStore}
|
{#if $userIsAdminStore}
|
||||||
<h2>Warning!</h2>
|
<h2>{$LL.warning.title()}</h2>
|
||||||
<p>
|
<p>
|
||||||
This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank"
|
{$LL.warning.content({ upgradeLink })}
|
||||||
>here</a
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
{:else if $limitMapStore}
|
{:else if $limitMapStore}
|
||||||
<p>
|
<p>
|
||||||
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
|
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<h2>Warning!</h2>
|
<h2>{$LL.warning.title()}</h2>
|
||||||
<p>This world is close to its limit!</p>
|
<p>{$LL.warning.limit()}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.warningMain {
|
main.warningMain {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
width: 100vw;
|
width: 80%;
|
||||||
background-color: red;
|
background-color: #f9e81e;
|
||||||
|
color: #14304c;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
|
||||||
|
top: 4%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
z-index: 2;
|
z-index: 700;
|
||||||
h2 {
|
h2 {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #ff475a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -22,10 +22,21 @@
|
|||||||
src = source ?? placeholderSrc;
|
src = source ?? placeholderSrc;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
onDestroy(unsubscribe);
|
onDestroy(unsubscribe);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img {src} alt="" class="nes-pointer" style="--theme-width: {width}; --theme-height: {height}" />
|
<img
|
||||||
|
{src}
|
||||||
|
alt=""
|
||||||
|
class="nes-pointer noselect"
|
||||||
|
style="--theme-width: {width}; --theme-height: {height}"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Game } from "../../Phaser/Game/Game";
|
import type { Game } from "../../Phaser/Game/Game";
|
||||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||||
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
|
|
||||||
@ -25,7 +26,7 @@
|
|||||||
|
|
||||||
<form class="selectCharacterScene">
|
<form class="selectCharacterScene">
|
||||||
<section class="text-center">
|
<section class="text-center">
|
||||||
<h2>Select your WOKA</h2>
|
<h2>{$LL.woka.selectWoka.title()}</h2>
|
||||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}>
|
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
@ -37,17 +38,19 @@
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
||||||
on:click|preventDefault={cameraScene}>Continue</button
|
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
||||||
on:click|preventDefault={customizeScene}>Customize your WOKA</button
|
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
||||||
>
|
>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.selectCharacterScene {
|
form.selectCharacterScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@ -90,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.selectCharacterScene button.selectCharacterButtonLeft {
|
form.selectCharacterScene button.selectCharacterButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import * as rax from "retry-axios";
|
import * as rax from "retry-axios";
|
||||||
import { errorStore } from "../Stores/ErrorStore";
|
import { errorStore } from "../Stores/ErrorStore";
|
||||||
|
import LL from "../i18n/i18n-svelte";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This instance of Axios will retry in case of an issue and display an error message as a HTML overlay.
|
* This instance of Axios will retry in case of an issue and display an error message as a HTML overlay.
|
||||||
@ -26,7 +28,7 @@ axiosWithRetry.defaults.raxConfig = {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
console.log(cfg);
|
console.log(cfg);
|
||||||
console.log(`Retry attempt #${cfg?.currentRetryAttempt} on URL '${err.config.url}'`);
|
console.log(`Retry attempt #${cfg?.currentRetryAttempt} on URL '${err.config.url}'`);
|
||||||
errorStore.addErrorMessage("Unable to connect to WorkAdventure. Are you connected to internet?", {
|
errorStore.addErrorMessage(get(LL).error.connectionRetry.unableConnect(), {
|
||||||
closable: false,
|
closable: false,
|
||||||
id: "axios_retry",
|
id: "axios_retry",
|
||||||
});
|
});
|
||||||
|
@ -183,14 +183,14 @@ class ConnectionManager {
|
|||||||
window.location.hash;
|
window.location.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||||
|
//use href to keep # value
|
||||||
|
await localUserStore.setLastRoomUrl(new URL(roomPath).href);
|
||||||
|
|
||||||
//get detail map for anonymous login and set texture in local storage
|
//get detail map for anonymous login and set texture in local storage
|
||||||
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
||||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||||
|
|
||||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
|
||||||
//use href to keep # value
|
|
||||||
await localUserStore.setLastRoomUrl(this._currentRoom.href);
|
|
||||||
|
|
||||||
//todo: add here some kind of warning if authToken has expired.
|
//todo: add here some kind of warning if authToken has expired.
|
||||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
|
@ -91,9 +91,7 @@ export class Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
baseUrl.pathname = "/_/" + instance + "/" + absoluteExitSceneUrl.host + absoluteExitSceneUrl.pathname;
|
||||||
if (absoluteExitSceneUrl.hash) {
|
|
||||||
baseUrl.hash = absoluteExitSceneUrl.hash;
|
baseUrl.hash = absoluteExitSceneUrl.hash;
|
||||||
}
|
|
||||||
|
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,46 @@
|
|||||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
declare global {
|
||||||
const START_ROOM_URL: string =
|
interface Window {
|
||||||
process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
env?: Record<string, string>;
|
||||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
}
|
||||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
}
|
||||||
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
|
||||||
const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost";
|
const getEnv = (key: string): string | undefined => {
|
||||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
if (global.window?.env) {
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
return global.window.env[key];
|
||||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
}
|
||||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
if (global.process?.env) {
|
||||||
const TURN_USER: string = process.env.TURN_USER || "";
|
return global.process.env[key];
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || "";
|
}
|
||||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
return;
|
||||||
const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
};
|
||||||
|
|
||||||
|
const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true";
|
||||||
|
const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||||
|
const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost";
|
||||||
|
export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re";
|
||||||
|
const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost";
|
||||||
|
const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost";
|
||||||
|
const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302";
|
||||||
|
const TURN_SERVER: string = getEnv("TURN_SERVER") || "";
|
||||||
|
const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true";
|
||||||
|
const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true";
|
||||||
|
const TURN_USER: string = getEnv("TURN_USER") || "";
|
||||||
|
const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || "";
|
||||||
|
const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL");
|
||||||
|
const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true";
|
||||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
|
export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8;
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4");
|
||||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true";
|
||||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
export const NODE_ENV = getEnv("NODE_ENV") || "development";
|
||||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
export const CONTACT_URL = getEnv("CONTACT_URL") || undefined;
|
||||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
export const PROFILE_URL = getEnv("PROFILE_URL") || undefined;
|
||||||
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || "";
|
||||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined;
|
||||||
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true";
|
||||||
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER");
|
||||||
|
const FALLBACK_LOCALE = getEnv("FALLBACK_LOCALE") || undefined;
|
||||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
@ -44,4 +58,5 @@ export {
|
|||||||
TURN_PASSWORD,
|
TURN_PASSWORD,
|
||||||
JITSI_URL,
|
JITSI_URL,
|
||||||
JITSI_PRIVATE_MODE,
|
JITSI_PRIVATE_MODE,
|
||||||
|
FALLBACK_LOCALE,
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user