This commit is contained in:
AdrienLSH
2024-05-14 08:50:37 +02:00
parent 95f0097ce5
commit e308e8f012
231 changed files with 70 additions and 22 deletions

View File

@ -0,0 +1,20 @@
import { lang } from "../index.js";
import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthenticatedView {
constructor(params) {
super(params, 'homeWindowTitle');
this.redirect_url = "/login";
}
async getHtml() {
return /* HTML */ `
<h1>${lang.get('homeTitle', 'Home')}</h1>
<a href="/matchmaking" data-link>${lang.get('homeOnline', 'Play online')}</a>
<a href="/games/pong/offline" data-link>${lang.get('homeOffline', 'Play offline')}</a>
<a href="/games/tictactoe/offline" data-link>${lang.get('ticTacToe')}</a>
<a href="/settings" data-link>${lang.get('homeSettings', 'Settings')}</a>
<a href="/logout" data-link>${lang.get('homeLogout', 'Logout')}</a>
`;
}
}

View File

@ -0,0 +1,143 @@
import { client, lang, navigateTo } from "../index.js";
import { clearIds, fill_errors } from "../utils/formUtils.js";
import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthenticatedView {
constructor(params)
{
super(params, "Matchmaking");
}
async toggle_search()
{
clearIds("innerText", ["detail"]);
if (client.matchmaking.searching)
{
client.matchmaking.stop();
this.button.innerHTML = lang.get("matchmakingStartSearch");
}
else
{
await client.matchmaking.start(this.onreceive.bind(this), this.ondisconnect.bind(this), this.game_type_input.value, this.nb_players_input.value);
this.button.innerHTML = lang.get("matchmakingStopSearch");
}
}
ondisconnect(event)
{
this.button.innerHTML = lang.get("matchmakingStartSearch");
}
onreceive(data)
{
if (data.detail === "game_found")
{
navigateTo(`/games/${data.game_type}/${data.game_id}`);
return;
}
this.display_data(data);
}
display_data(data)
{
clearIds("innerText", ["detail"]);
fill_errors(data, "innerText");
}
addEnterEvent()
{
[this.nb_players_input, this.game_type_input].forEach((input) => {
input.addEventListener('keydown', async ev => {
if (ev.key !== 'Enter')
return;
await this.toggle_search.bind(this);
});
});
}
addChangeNbPlayersEvent()
{
let update = () => {
this.button.disabled = (this.nb_players_input.value < 2 || this.nb_players_input.value > 4);
};
["change", "oninput"].forEach((event_name) => {
this.nb_players_input.addEventListener(event_name, update);
});
}
addChangegame_typeEvent()
{
let nb_players_div = document.getElementById("nb-players-div");
this.game_type_input.addEventListener("change", () => {
if (this.game_type_input.value === "tictactoe")
{
nb_players_div.style.display = 'none';
this.nb_players_input.value = 2;
}
else
nb_players_div.style.display = 'block';
client.matchmaking.stop();
clearIds("innerText", ["detail"]);
});
}
addEvents()
{
this.addEnterEvent();
this.addChangegame_typeEvent();
this.addChangeNbPlayersEvent();
}
async postInit()
{
this.button = document.getElementById("toggle-search");
this.nb_players_input = document.getElementById("nb-players-input");
this.game_type_input = document.getElementById("game-type-input");
this.button.onclick = this.toggle_search.bind(this);
this.addEvents()
}
async getHtml() {
return /* HTML */ `
<div class='container-fluid'>
<div class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4' id="title">${lang.get("matchmakingTitle")}</h4>
<div>
<div class='form-floating mb-2' id='game_type-div'>
<select class='form-control' id='game-type-input'>
<option value='pong'>Pong</option>
<option value='tictactoe'>ticTacToe</option>
</select>
<label for='game-type-input'>${lang.get("gamemodeChoice")}</label>
</div>
<div class='form-floating mb-2' id='nb-players-div'>
<input type='number' min='2' value='2' max='4' class='form-control' id='nb-players-input' placeholder='${lang.get("matchmakingNbPlayers")}'>
<label for='nb-players-input' id='username-label'>${lang.get("matchmakingNbPlayers")}</label>
<span class='text-danger' id='username'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2 mx-2' id='toggle-search'>${lang.get("matchmakingStartSearch")}</button>
<span class='text-danger my-auto mx-2' id='detail'></span>
</div>
</div>
</div>
</div>
`;
}
async leavePage()
{
await client.matchmaking.stop();
}
}

View File

@ -0,0 +1,16 @@
import AbstractView from "./abstracts/AbstractView.js";
import { lang } from '../index.js';
export default class extends AbstractView {
constructor(params) {
super(params, '404WindowTitle');
}
async getHtml() {
return `
<h1>404 Bozo</h1>
<img src="https://media.giphy.com/media/pm0BKtuBFpdM4/giphy.gif">
<p>Git gud</p>
`;
}
}

View File

@ -0,0 +1,656 @@
import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
import "../3D/maths/gl-matrix-min.js"
import { initShaderProgram, shaderInfos } from "../3D/shaders.js"
import { initBuffers } from "../3D/buffers.js"
import { renderCube } from "../3D/cube.js"
export class PongOfflineView extends AbstractAuthenticatedView {
constructor(params) {
super(params, 'Game');
this.init();
}
init(round) {
this.game = null;
this.player1 = "Player 1";
this.player2 = "Player 2";
this.player3 = "Player 3";
this.player4 = "Player 4";
this.winner1 = undefined;
this.winner2 = undefined;
this.final_winner = undefined;
this.round = -1;
this.game_mode = 1; // 1 is 2D, 2 is 3D
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/gameOffline.css">
<h1>Game</h1>
<button id='startGameButton'>Start Game</button>
<button id='resetGameButton'>Reset Game</button>
<button id='stopGameButton'>Stop Game</button>
<button id='startTournament'>Start Tournament</button>
<button id='gameMode'>Switch to 3D</button>
<div id="display"></div>
<div id="display-button"></div>
<svg id="tree" height="3000" width="3000"></svg>
`;
}
async postInit() {
this.app = document.getElementById("display");
document.getElementById('startGameButton').onclick = this.startGame.bind(this);
document.getElementById('resetGameButton').onclick = this.resetGame.bind(this);
document.getElementById('resetGameButton').hidden = 1;
document.getElementById('stopGameButton').onclick = this.stopGame.bind(this);
document.getElementById('stopGameButton').hidden = 1;
document.getElementById('startTournament').onclick = this.startTournament.bind(this);
document.getElementById('gameMode').hidden = 1;
document.getElementById('gameMode').onclick = this.toggleGameMode.bind(this);
}
async leavePage() {
this.game?.cleanup();
}
async toggleGameMode()
{
if(this.game_mode === 1) // 3D
{
this.game_mode = 2;
document.getElementById("gameMode").innerText = "Switch to 2D";
}
else if(this.game_mode === 2) // 2D
{
this.game_mode = 1;
document.getElementById("gameMode").innerText = "Switch to 3D";
}
this.game.changeGameMode(this.game_mode);
}
startTournament() {
let startTournament = document.getElementById("startTournament");
let player1 = document.createElement("input");
player1.id="player1"; player1.type="text"; player1.name="message"; player1.placeholder="Player 1"; player1.maxLength=10; player1.value = "";
startTournament.before(player1);
let player2 = document.createElement("input");
player2.id="player2"; player2.type="text"; player2.name="message"; player2.placeholder="Player 2"; player2.maxLength=10; player2.value = "";
player1.after(player2);
let player3 = document.createElement("input");
player3.id="player3"; player3.type="text"; player3.name="message"; player3.placeholder="Player 3"; player3.maxLength=10; player3.value = "";
player2.after(player3);
let player4 = document.createElement("input");
player4.id="player4"; player4.type="text"; player4.name="message"; player4.placeholder="Player 4"; player4.maxLength=10; player4.value = "";
player3.after(player4);
startTournament.onclick = () => {
if (player1.value.length > 0)
this.player1 = player1.value;
if (player2.value.length > 0)
this.player2 = player2.value;
if (player3.value.length > 0)
this.player3 = player3.value;
if (player4.value.length > 0)
this.player4 = player4.value;
player1.remove();
player2.remove();
player3.remove();
player4.remove();
this.round = 0;
this.startGame(this.player1, this.player2);
startTournament.onclick = this.startTournament.bind(this);
}
}
createButton()
{
this.up1 = document.createElement("button");
this.up1.textContent = "↑";
this.up1.id = "up1";
this.down1 = document.createElement("button");
this.down1.textContent = "↓";
this.down1.id = "down1";
this.up2 = document.createElement("button");
this.up2.textContent = "↑";
this.up2.id = "up2";
this.down2 = document.createElement("button");
this.down2.textContent = "↓";
this.down2.id = "down2";
this.up1.setAttribute("user_id", 1);
this.up1.setAttribute("direction", "up");
this.up2.setAttribute("user_id", 2);
this.up2.setAttribute("direction", "up");
this.down1.setAttribute("user_id", 1);
this.down1.setAttribute("direction", "down");
this.down2.setAttribute("user_id", 2);
this.down2.setAttribute("direction", "down");
document.addEventListener('touchstart', this.keyDownHandler);
document.addEventListener('touchend', this.keyUpHandler);
[this.up1, this.up2, this.down1, this.down2].forEach(button => {
button.onmousedown = this.game.keyDownHandler.bind(this.game);
button.onmouseup = this.game.keyUpHandler.bind(this.game);
button.ontouchstart = this.game.keyDownHandler.bind(this.game);
button.ontouchend = this.game.keyUpHandler.bind(this.game);
});
document.getElementById("display-button").append(this.up1, this.down1);
document.getElementById("display-button").append(this.up2, this.down2);
}
resetGame(player1, player2) {
if (this.round >= 0)
this.round = 0;
this.winner1 = undefined;
this.winner2 = undefined;
this.final_winner = undefined;
this.startGame(player1, player2)
}
async startGame(player1, player2) {
if (player1 == null || player2 == null) {
player1 = this.player1;
player2 = this.player2;
}
if (this.game == null) {
this.game = new Game(this.game_mode, this, player1, player2);
this.createButton();
}
else {
this.app.removeChild(this.game.canvas);
this.app.removeChild(this.game.scoresDisplay);
this.game.cleanup();
this.game = new Game(this.game_mode, this, player1, player2);
this.createButton();
}
if (this.round >= 0)
await this.display_tree_tournament();
document.getElementById('startTournament').hidden = 1;
document.getElementById('startGameButton').hidden = 1;
document.getElementById('stopGameButton').hidden = 0;
document.getElementById('resetGameButton').hidden = 0;
document.getElementById('gameMode').hidden = 0;
}
stopGame() {
if (!this.game)
return;
this.app.removeChild(this.game.canvas);
this.app.removeChild(this.game.scoresDisplay);
this.game.cleanup();
this.init();
document.getElementById('startGameButton').innerHTML = 'Start Game';
document.getElementById('startTournament').hidden = 0;
document.getElementById('startGameButton').hidden = 0;
document.getElementById('stopGameButton').hidden = 1;
document.getElementById('resetGameButton').hidden = 1;
document.getElementById('gameMode').hidden = 1;
this.app.style.maxWidth = null;
}
async display_tree_tournament() {
let players = [this.player1, this.player2, this.player3, this.player4, this.winner1, this.winner2, this.final_winner];
const svg = document.getElementById('tree');
svg.innerHTML = '';
let width = 150;
let height = 40;
let gap_y = height + 25;
let gap_x = width + 25;
let start_y = height / 2;
let rounds = Math.log2(4) + 1;
let k = 0;
for (let i = 0; i < rounds; i++) {
let number_square = 4 / Math.pow(2, i)
for(let j = 0; j < number_square; j++) {
const y = start_y + gap_y * j * Math.pow(2, i) + (gap_y / 2 * Math.pow(2, i));
svg.appendChild(await this.create_square(gap_x * i, y, width, height, "white", "black", players[k]));
svg.appendChild(await this.create_text(gap_x * i, y, width, height, "white", "black", players[k]));
k++;
}
}
}
async create_square(x, y, width, height, fill, stroke, text) {
const square = document.createElementNS("http://www.w3.org/2000/svg", "rect");
square.setAttribute("id", "square")
square.setAttribute("x", x);
square.setAttribute("y", y);
square.setAttribute("width", width);
square.setAttribute("height", height);
square.setAttribute("fill", fill);
square.setAttribute("stroke", stroke);
return square
}
async create_text(x, y, width, height, fill, stroke, text) {
const textElement = document.createElementNS("http://www.w3.org/2000/svg", "text");
textElement.setAttribute("id", "textElement")
textElement.setAttribute("x", x);
textElement.setAttribute("y", y + height / 2 + 5);
textElement.setAttribute("font-family", "Arial")
textElement.setAttribute("font-size", "20")
textElement.setAttribute("fill", stroke);
textElement.setAttribute("stroke", fill);
if (text == undefined)
text = "";
textElement.appendChild(document.createTextNode(text));
return textElement;
}
}
class Game {
constructor(game_mode, this_pong, player_name1, player_name2) {
this.pong = this_pong;
//Global variables
this.def = {
CANVASHEIGHT: 270,
CANVASWIDTH: 480,
PADDLEHEIGHT: 70,
PADDLEWIDTH: 10,
PADDLEMARGIN: 5,
PADDLESPEED: 3,
BALLRADIUS: 5,
BALLSPEED: 2,
BALLSPEEDINCR: 0.15,
MAXBOUNCEANGLE: 10 * (Math.PI / 12),
MAXSCORE: 2
};
this.app = document.getElementById("display");
this.app.style.maxWidth = Number(this.def.CANVASWIDTH) + "px";
this.canvas = null;
this.context = null;
this.player_name1 = player_name1;
this.player_name2 = player_name2;
this.scoresDisplay = document.createElement('p');
this.scoresDisplay.innerHTML = `${this.player_name1}: 0 - ${this.player_name2}: 0`;
this.app.appendChild(this.scoresDisplay);
this.players = [
{
paddle: new Paddle(this.def.PADDLEMARGIN, this.def),
score: 0
},
{
paddle: new Paddle(this.def.CANVASWIDTH - this.def.PADDLEMARGIN - this.def.PADDLEWIDTH, this.def),
score: 0
}
];
this.ballStartSide = 0;
this.ballRespawned = false;
this.ball = new Ball(this.def, this.ballStartSide, this.context);
this.interval = setInterval(this.updateGame.bind(this), 10);
this.keys = [];
this.keyUpHandler = this.keyUpHandler.bind(this);
this.keyDownHandler = this.keyDownHandler.bind(this);
document.addEventListener('keydown', this.keyDownHandler);
document.addEventListener('keyup', this.keyUpHandler);
// stufs for 3D
this.shader_prog = null;
this.buffers = null;
this.cam_pos = [0, 150, -10];
this.cam_target = [0, 0, 0];
this.cam_up = [0, 0, -1];
this.initGame(game_mode);
}
initGame(mode)
{
if(mode === 1)
this.init2D();
else if(mode === 2)
this.initWebGL();
}
initWebGL()
{
this.canvas = document.createElement("canvas");
this.canvas.width = this.def.CANVASWIDTH;
this.canvas.height = this.def.CANVASHEIGHT;
this.canvas.id = "gameCanvas";
this.canvas.style.border = '1px solid #d3d3d3';
this.canvas.style.backgroundColor = '#f1f1f1';
this.app.appendChild(this.canvas);
this.context = this.canvas.getContext("webgl");
if(this.context === null)
{
alert("Unable to initialize WebGL. Your browser or machine may not support it. You may also be a bozo");
return;
}
this.shader_prog = initShaderProgram(this.context);
this.buffers = initBuffers(this.context);
this.context.enable(this.context.CULL_FACE);
this.context.cullFace(this.context.BACK);
}
init2D()
{
this.canvas = document.createElement('canvas');
this.canvas.id = 'gameCanvas';
this.canvas.width = this.def.CANVASWIDTH;
this.canvas.height = this.def.CANVASHEIGHT;
this.canvas.style.border = '1px solid #d3d3d3';
this.canvas.style.backgroundColor = '#f1f1f1';
this.context = this.canvas.getContext('2d');
this.app.appendChild(this.canvas);
}
changeGameMode(mode)
{
this.app.removeChild(this.canvas);
this.initGame(mode);
}
finish(winner) {
this.cleanup();
if (this.pong.round >= 0) {
if (this.pong.round == 0) {
this.pong.winner1 = winner;
this.pong.startGame(this.pong.player3, this.pong.player4)
this.pong.round++;
}
else if (this.pong.round == 1) {
this.pong.winner2 = winner;
this.pong.startGame(this.pong.winner1, this.pong.winner2)
this.pong.round++;
}
else {
this.pong.final_winner = winner;
this.pong.display_tree_tournament();
this.scoresDisplay.innerHTML = winner + ' wins!! GGS';
}
}
else
this.scoresDisplay.innerHTML = winner + ' wins!! GGS';
}
cleanup() {
clearInterval(this.interval);
document.removeEventListener('keydown', this.keyDownHandler);
document.removeEventListener('keyup', this.keyUpHandler);
this.canvas.style.display = 'none';
["up1", "up2", "down1", "down2", "player_name1", "player_name2"].forEach(button => {
if (document.getElementById(button) != null)
document.getElementById(button).remove();
});
let svg = document.getElementById("tree");
while (svg.firstChild)
svg.removeChild(svg.firstChild);
}
clear() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
updateGame() {
//Paddle movement
if ((this.keys.includes('s') || this.keys.includes('down1')) &&
this.players[0].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
this.players[0].paddle.y += this.def.PADDLESPEED;
if ((this.keys.includes('w') || this.keys.includes('up1')) &&
this.players[0].paddle.y > 0 + this.def.PADDLEMARGIN)
this.players[0].paddle.y -= this.def.PADDLESPEED;
if ((this.keys.includes('ArrowDown') || this.keys.includes('down2')) &&
this.players[1].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
this.players[1].paddle.y += this.def.PADDLESPEED;
if ((this.keys.includes('ArrowUp') || this.keys.includes('up2')) &&
this.players[1].paddle.y > 0 + this.def.PADDLEMARGIN)
this.players[1].paddle.y -= this.def.PADDLESPEED;
//GOOAAAAL
if (this.ball.x <= 0)
this.updateScore.bind(this)(this.players[0].score, ++this.players[1].score);
else if (this.ball.x >= this.def.CANVASWIDTH)
this.updateScore.bind(this)(++this.players[0].score, this.players[1].score);
//Ball collisions
if (this.detectCollision(this.players[0].paddle, this.ball))
this.calculateBallVelocity(this.players[0].paddle.getCenter().y, this.ball);
else if (this.detectCollision(this.players[1].paddle, this.ball))
this.calculateBallVelocity(this.players[1].paddle.getCenter().y, this.ball, -1);
if (this.ball.y - this.ball.radius <= 0)
this.ball.vy *= -1;
else if (this.ball.y + this.ball.radius >= this.canvas.height)
this.ball.vy *= -1;
if (!this.ballRespawned) {
this.ball.x += this.ball.vx;
this.ball.y += this.ball.vy;
}
if(this.context instanceof CanvasRenderingContext2D)
{
this.clear();
}
else if(this.context instanceof WebGLRenderingContext)
{
this.context.clearColor(0.1, 0.1, 0.1, 1.0);
this.context.clearDepth(1.0);
this.context.enable(this.context.DEPTH_TEST);
this.context.depthFunc(this.context.LEQUAL);
this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);
const projectionMatrix = mat4.create();
const viewMatrix = mat4.create();
mat4.perspective(projectionMatrix, (90 * Math.PI) / 180, this.context.canvas.clientWidth / this.context.canvas.clientHeight, 0.1, 10000000.0);
mat4.lookAt(viewMatrix, this.cam_pos, this.cam_target, this.cam_up);
this.setPositionAttribute();
this.setNormalAttribute();
this.context.useProgram(shaderInfos.program);
this.context.uniformMatrix4fv(shaderInfos.uniformLocations.projectionMatrix, false, projectionMatrix);
this.context.uniformMatrix4fv(shaderInfos.uniformLocations.viewMatrix, false, viewMatrix);
}
else
{
alert('Unknown rendering context type');
}
this.players[0].paddle.update(this.context);
this.players[1].paddle.update(this.context);
this.ball.update(this.context);
}
updateScore(p1Score, p2Score) {
if (p1Score > this.def.MAXSCORE) {
this.finish(this.player_name1);
}
else if (p2Score > this.def.MAXSCORE) {
this.finish(this.player_name2);
} else {
this.scoresDisplay.innerHTML = `${this.player_name1}: ${p1Score} - ${this.player_name2}: ${p2Score}`;
this.ballStartSide = 1 - this.ballStartSide;
this.ball = new Ball(this.def, this.ballStartSide, this.context);
this.ballRespawned = true;
new Promise(r => setTimeout(r, 300))
.then(_ => this.ballRespawned = false);
}
}
detectCollision(paddle, ball) {
let paddleCenter = paddle.getCenter();
let dx = Math.abs(ball.x - paddleCenter.x);
let dy = Math.abs(ball.y - paddleCenter.y);
if (dx <= ball.radius + paddle.width / 2 &&
dy <= ball.radius + paddle.height / 2)
return true;
return false;
}
calculateBallVelocity(paddleCenterY, ball, side = 1) {
let relativeIntersectY = paddleCenterY - ball.y;
let normRelIntersectY = relativeIntersectY / this.def.PADDLEHEIGHT / 2;
let bounceAngle = normRelIntersectY * this.def.MAXBOUNCEANGLE;
ball.speed += this.def.BALLSPEEDINCR;
ball.vx = ball.speed * side * Math.cos(bounceAngle);
ball.vy = ball.speed * -Math.sin(bounceAngle);
}
keyUpHandler(ev) {
let attributes = ev.originalTarget.attributes;
let key = ev.key === undefined ? `${attributes.direction.value}${attributes.user_id.value}` : ev.key;
const idx = this.keys.indexOf(key);
if (idx != -1)
this.keys.splice(idx, 1);
}
keyDownHandler(ev) {
let attributes = ev.originalTarget.attributes;
let key = ev.key === undefined ? `${attributes.direction.value}${attributes.user_id.value}` : ev.key;
if (!this.keys.includes(key))
this.keys.push(key);
}
setNormalAttribute()
{
const numComponents = 3;
const type = this.context.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
this.context.bindBuffer(this.context.ARRAY_BUFFER, this.buffers.normal);
this.context.vertexAttribPointer(
shaderInfos.attribLocations.vertexNormal,
numComponents,
type,
normalize,
stride,
offset,
);
this.context.enableVertexAttribArray(shaderInfos.attribLocations.vertexNormal);
}
setPositionAttribute()
{
const numComponents = 3;
const type = this.context.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
this.context.bindBuffer(this.context.ARRAY_BUFFER, this.buffers.vertex);
this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, this.buffers.index);
this.context.vertexAttribPointer(
shaderInfos.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset
);
this.context.enableVertexAttribArray(shaderInfos.attribLocations.vertexPosition);
}
}
class Paddle {
constructor(paddleSide, def) {
this.width = def.PADDLEWIDTH;
this.height = def.PADDLEHEIGHT;
this.x = paddleSide;
this.y = def.CANVASHEIGHT / 2 - this.height / 2;
this.def = def;
this.update();
}
update(ctx) {
if(ctx instanceof CanvasRenderingContext2D)
{
ctx.fillStyle = 'black';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
else if(ctx instanceof WebGLRenderingContext)
{
const posx = (this.x - this.def.CANVASWIDTH / 2);
const posy = (this.y - this.def.CANVASHEIGHT / 3);
renderCube(ctx, posx, 0, posy, 0, this.width, this.width, this.height / 2);
}
}
getCenter() {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2
};
}
}
class Ball {
constructor(def, startSide, ctx) {
this.radius = def.BALLRADIUS;
this.speed = def.BALLSPEED;
this.x = def.CANVASWIDTH / 2;
this.y = def.CANVASHEIGHT / 2;
this.vy = 0;
if (startSide === 0)
this.vx = -this.speed;
else
this.vx = this.speed;
this.def = def;
this.update(ctx);
}
update(ctx) {
if(ctx instanceof CanvasRenderingContext2D)
{
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
else if(ctx instanceof WebGLRenderingContext)
{
const size = this.radius;
const posx = (this.x - this.radius / 2) - this.def.CANVASWIDTH / 2;
const posy = (this.y - this.radius / 2) - this.def.CANVASHEIGHT / 2;
renderCube(ctx, posx, 0, posy, 0, size, size, size);
}
}
}

View File

@ -0,0 +1,261 @@
import { client, reloadView } from "../index.js";
import { PongGame } from "../api/game/pong/PongGame.js";
import { PongPlayer } from "../api/game/pong/PongPlayer.js";
import AbstractAuthenticatedView from "./abstracts/AbstractAuthenticatedView.js";
import { Profile } from "../api/Profile.js";
import { PongMyPlayer } from "../api/game/pong/PongMyPlayer.js";
export default class PongOnlineView extends AbstractAuthenticatedView
{
constructor(params)
{
super(params, 'Pong');
/**
* @type {Number}
*/
this.game_id = params.id;
/**
* @type {PongGame}
*/
this.game;
/**
* @type {HTMLCanvasElement}
*/
this.canva;
/**
* @type {CanvasRenderingContext2D}
*/
this.gameboard;
/**
* @type {HTMLElement}
*/
this.gamestate;
/**
* @type {HTMLTableElement}
*/
this.scoreboard;
/**
* @type {HTMLElement}
*/
this.app = document.getElementById("app");
/**
* @type {[]}
*/
this.keysPressed
}
createMyPlayer()
{
let index = this.game.players.findIndex((player) => player.id === client.me.id);
if (index === -1)
return;
let myPlayer = this.game.players[index];
if (myPlayer.isEliminated)
return;
this.keysPressed = [];
this.myPlayer = new PongMyPlayer(client,
this.game,
myPlayer.score,
myPlayer.rail,
myPlayer.position,
);
myPlayer = this.myPlayer;
this.registerKey();
}
keyReleaseHandler(event)
{
const idx = this.keysPressed.indexOf(event.key);
if (idx != -1)
this.keysPressed.splice(idx, 1);
}
keyPressHandler(event)
{
console.log("bozo")
if (!this.keysPressed.includes(event.key))
this.keysPressed.push(event.key);
}
registerKey()
{
this.keyPressHandler = this.keyPressHandler.bind(this);
this.keyReleaseHandler = this.keyReleaseHandler.bind(this);
document.addEventListener('keydown', this.keyPressHandler);
document.addEventListener('keyup', this.keyReleaseHandler);
}
unregisterKey()
{
document.removeEventListener('keydown', this.keyPressHandler);
document.removeEventListener('keyup', this.keyReleaseHandler);
}
/**
* @param {PongPlayer} player
*/
async onGoal(player)
{
document.getElementById(`score-${player.id}`).innerText = player.score.length;
}
async onStart()
{
this.gamestate.innerHTML = this.game.getState();
}
/**
* @param {PongPlayer} winner
*/
async onFinish(winner)
{
this.gamestate.innerHTML = this.game.getState();
this.destroyGameboard();
this.displayWinner(winner)
}
async onDisconnect()
{
await reloadView();
}
createScoreboard()
{
this.scoreboard = document.createElement("table");
this.app.appendChild(this.scoreboard);
let row = document.createElement("tr");
this.game.players.forEach(player => {
let th = document.createElement("th");
th.innerText = player.username;
row.appendChild(th);
});
this.scoreboard.appendChild(row);
row = document.createElement("tr");
this.game.players.forEach(player => {
let th = document.createElement("th");
th.id = `score-${player.id}`;
th.innerText = player.score.length;
this.scoreboard.appendChild(th);
});
this.scoreboard.appendChild(row);
}
createGameboard()
{
this.canva = document.createElement("canvas");
this.app.appendChild(this.canva)
this.canva.height = this.game.config.MAP_SIZE_Y
this.canva.width = this.game.config.MAP_SIZE_X
this.gameboard = this.canva.getContext('2d');
}
destroyGameboard()
{
this.app.removeChild(this.canva);
}
/**
*
* @param {Profile} winner
*/
displayWinner(winner)
{
const winnerStand = document.createElement("h1");
document.getElementById("app").appendChild(winnerStand);
winnerStand.innerText = `winner: ${winner.username}`;
}
async connect()
{
await this.game.join();
await this.game.waitInit();
}
async postInit()
{
this.gamestate = document.getElementById("gamestate");
this.game = new PongGame(client, this.game_id, this.onStart.bind(this), this.onGoal.bind(this), this.onStart.bind(this), this.onFinish.bind(this));
const errorCode = await this.game.init();
if (errorCode)
return errorCode;
this.gamestate.innerHTML = this.game.getState();
if (this.game.finished)
{
this.displayWinner(this.game.winner);
}
else
{
await this.connect();
this.createScoreboard();
this.createGameboard();
this.createMyPlayer();
this.render();
}
}
render()
{
let loop_id = setInterval(() => {
if (this.game === undefined)
clearInterval(loop_id);
this.myPlayer?.updatePaddle(this.keysPressed);
this.renderGame();
this.game?.time?.new_frame();
//clearInterval(loop_id);
// 1 sec fps
}, 1000 / 60);
}
renderGame()
{
this.gameboard.beginPath();
this.game.render(this.gameboard);
this.gameboard.strokeStyle = "#000000";
this.gameboard.lineWidth = 1;
this.gameboard.stroke();
}
async getHtml()
{
return /* HTML */ `
<h1 id='gamestate'></h1>
`;
}
async leavePage()
{
this.unregisterKey();
this.game.leave()
}
}

View File

@ -0,0 +1,234 @@
import AbstractView from "./abstracts/AbstractView.js";
import { client, lang } from "../index.js";
export default class extends AbstractView {
constructor(params) {
super(params, `${decodeURI(params.username)} - Profile`);
this.username = decodeURI(params.username);
}
setTitle() {
document.title = this.titleKey;
}
async postInit()
{
if (!this.profile)
return 404;
const games = await this.profile.getGameHistory();
await this.fillHistory(games);
await this.fillStatistics(games);
if (this.profile.id === client.me.id)
return;
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton'),
blockButton = document.getElementById('blockButton'),
unblockButton = document.getElementById('unblockButton');
this.loadFriendshipStatus();
if (this.profile.isBlocked)
unblockButton.classList.remove('d-none');
else
blockButton.classList.remove('d-none');
addFriendButton.onclick = _ => this.addFriend();
removeFriendButton.onclick = _ => this.removeFriend();
unblockButton.onclick = _ => this.unblockUser();
blockButton.onclick = _ => this.blockUser();
}
loadFriendshipStatus() {
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton'),
statusIndicator = document.getElementById('statusIndicator');
if (this.profile.hasIncomingRequest) {
removeFriendButton.classList.add('d-none');
addFriendButton.classList.remove('d-none');
addFriendButton.innerHTML = 'Accept Request';
} else if (this.profile.hasOutgoingRequest) {
addFriendButton.classList.add('d-none');
removeFriendButton.classList.remove('d-none');
removeFriendButton.classList.replace('btn-danger', 'btn-secondary');
removeFriendButton.innerHTML = 'Cancel Request';
} else if (this.profile.isFriend) {
addFriendButton.classList.add('d-none');
removeFriendButton.classList.remove('d-none');
removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
removeFriendButton.innerHTML = 'Remove Friend';
} else {
addFriendButton.innerHTML = 'Add Friend';
removeFriendButton.classList.add('d-none');
addFriendButton.classList.remove('d-none');
}
statusIndicator.classList.remove('bg-success', 'bg-danger');
if (this.profile.online === true)
statusIndicator.classList.add('bg-success');
else if (this.profile.online === false)
statusIndicator.classList.add('bg-danger');
}
/**
* @param {[Object]} games
*/
async fillStatistics(games)
{
let winrateDiv = document.getElementById("winrate");
let win = 0;
let lose = 0;
games.forEach(game => {
if (game.finished === false)
return
if (client.me.id === game.winner.id)
win++;
else
lose++;
});
winrateDiv.innerText = `winrate: ${win + lose === 0 ? "🤓" : win / (win + lose)}`
}
async fillHistory(games)
{
let game_list = document.getElementById("game-list");
games.forEach(game => {
let a = document.createElement("a");
a.href = `/games/${game.game_type}/${game.id}`;
a.setAttribute("data-link", true);
let game_item = document.createElement("div");
game_item.className = "game-item";
game_item.style.backgroundColor = "grey";
if (game.started)
game_item.style.backgroundColor = "yellow";
if (game.finished)
game_item.style.backgroundColor = this.profile.id === game.winner.id ? "green" : "red";
game.players.forEach(player => {
let player_score = document.createElement("a");
player_score.href = `/profiles/${player.username}`;
player_score.innerText = `${player.username}`;
player_score.setAttribute("data-link", true);
game_item.appendChild(player_score);
});
a.appendChild(game_item);
game_list.appendChild(a);
});
}
async getHtml() {
this.profile = await client.profiles.getProfile(this.username);
if (!this.profile)
return '';
return /* HTML */ `
<div>
<div class='mb-3' id='profileInfo'>
<h1>${this.username}<span id='statusIndicator' style='height:0.75em; width:0.75em' class='ms-2 rounded-circle border d-inline-block'></span></h1>
<a href=${this.profile.avatar} target='_blank'>
<img class='img-thumbnail' src=${this.profile.avatar} style='width:auto; max-height:20vh; min-height:10vh'>
</a>
</div>
<div>
<button class='btn btn-sm btn-success d-none' id='addFriendButton'>Add Friend</button>
<button class='btn btn-sm btn-danger d-none' id='removeFriendButton'>Remove Friend</button>
<button class='btn btn-sm btn-danger d-none' id='blockButton'>Block</button>
<button class='btn btn-sm btn-secondary d-none' id='unblockButton'>Unblock</button>
</div>
<h1>Games</h1>
<div>
<h1 id='winrate'></h1>
</div>
<div>
<link rel="stylesheet" href="/static/css/gameHistory.css">
<div id="game-list"></div>
</div>
</div>
`;
}
async addFriend() {
const removeFriendButton = document.getElementById('removeFriendButton');
const response = await client._post(`/api/profiles/friends/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
removeFriendButton.classList.remove('d-none');
document.getElementById('addFriendButton').classList.add('d-none');
}
if (response.status === 200) {
removeFriendButton.innerHTML = 'Cancel Request';
removeFriendButton.classList.replace('btn-danger', 'btn-secondary');
this.profile.hasOutgoingRequest = true;
} else if (response.status === 201) {
removeFriendButton.innerHTML = 'Remove Friend';
removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
this.profile.isFriend = true;
this.profile.hasIncomingRequest = false;
}
}
async removeFriend() {
const addFriendButton = document.getElementById('addFriendButton'),
statusIndicator = document.getElementById('statusIndicator');
const response = await client._delete(`/api/profiles/friends/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
addFriendButton.innerHTML = 'Add Friend';
addFriendButton.classList.remove('d-none');
statusIndicator.classList.remove('bg-danger', 'bg-success');
document.getElementById('removeFriendButton').classList.add('d-none');
}
if (response.status === 200) {
this.profile.hasOutgoingRequest = false;
} else if (response.status === 201) {
this.profile.isFriend = false;
this.profile.online = null;
}
}
async blockUser() {
const response = await client._post(`/api/profiles/block/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
document.getElementById('blockButton').classList.add('d-none');
document.getElementById('unblockButton').classList.remove('d-none');
client.me.blockedUsers.push(this.profile);
this.profile.isBlocked = true;
}
}
async unblockUser() {
const response = await client._delete(`/api/profiles/block/${this.profile.id}`);
const body = await response.json();
console.log(body);
if (response.ok) {
document.getElementById('unblockButton').classList.add('d-none');
document.getElementById('blockButton').classList.remove('d-none');
client.me.blockedUsers = client.me.blockedUsers.filter(profile => profile.id !== this.profile.id);
this.profile.isBlocked = false;
}
}
}

View File

@ -0,0 +1,350 @@
import AbstractView from "./abstracts/AbstractView.js";
import { client, lang } from "../index.js";
import Channels from '../api/chat/Channels.js'
export default class extends AbstractView {
constructor(params) {
super(params, 'SearchWindowTitle');
this.channelManager = new Channels(client);
}
async postInit() {
this.profiles = await client.profiles.all();
this.logged = await client.isAuthenticated();
this.logged = await client.isAuthenticated();
this.profiles = await client.profiles.all();
document.getElementById('username-input').oninput = () => this.display_users(this.logged, this.profiles);
let search = document.getElementById("input_user");
if (search != undefined)
search.oninput = () => this.display_users();
this.last_add_chat = undefined;
this.display_users();
this.display_chat();
}
async display_users() {
const search = document.getElementById("username-input").value.toLowerCase();
let list_users = document.getElementById('user-list');
list_users.innerHTML = "";
this.profiles.filter(user => user.username.toLowerCase().startsWith(search) == true).forEach(async (user) => {
if (user.id == null) {
console.log("list User one with id null;");
return;
}
var new_user = document.createElement("li");
// username
let username = document.createElement("a");
username.setAttribute('data-link', '');
username.id = `username${user.id}`;
username.href = `/profiles/${user.username}`;
if (this.logged && user.id == client.me.id)
username.style.color = "green";
else {
let profile = await client.profiles.getProfileId(user.id);
let online = profile.online;
if (online == undefined)
username.style.color = "gray";
else if (online == true)
username.style.color = "green";
else
username.style.color = "red";
}
username.appendChild(document.createTextNode(user.username));
new_user.appendChild(username);
// space
new_user.appendChild(document.createTextNode(" "));
// button chat
if (this.logged && client.me.id != user.id) {
let add_chat = document.createElement("a");
add_chat.id = "add_chat_off";
add_chat.onclick = async () => {
if (this.channelManager.channel != undefined) {
// Permet de savoir si c'est le même channel
// Check to know if it's the same channel
this.channelManager.channel.members_id.forEach((member_id) => {
if (member_id == user.id)
this.channelManager.channel = undefined;
});
if (this.channelManager.channel == undefined) {
add_chat.id = "add_chat_off";
this.last_add_chat = undefined;
return await this.hide_chat();
}
await this.channelManager.channel.disconnect();
}
await this.channelManager.createChannel([client.me.id , user.id], () => this.reload_display_messages());
this.display_chat();
if (this.last_add_chat != undefined)
this.last_add_chat.id = "add_chat_off";
this.last_add_chat = add_chat;
add_chat.id = "add_chat_on";
};
add_chat.appendChild(document.createTextNode("Chat"));
new_user.appendChild(add_chat);
new_user.appendChild(document.createTextNode(" "));
}
// break line
new_user.appendChild(document.createElement("br"));
// avatar
var img = document.createElement("img");
img.src = user.avatar;
new_user.appendChild(img);
list_users.appendChild(new_user);
});
}
async display_specific_user(id) {
let user = document.getElementById("username" + id);
if (user == undefined)
return ;
if (this.logged && id == client.me.id)
user.style.color = "green";
else {
let profile = await client.profiles.getProfileId(id);
let online = profile.online;
if (online == undefined)
user.style.color = "gray";
else if (online == true)
user.style.color = "green";
else
user.style.color = "red";
}
}
async display_chat()
{
let reloads = ["members", "messages"];
reloads.forEach(reload => {
if (document.getElementById(reload) != undefined)
document.getElementById(reload).remove();
});
if (this.channelManager.channel === undefined || this.logged === false)
return ;
let chats = document.getElementById("chats");
if (document.getElementById("chat") == null) {
let chat = document.createElement("div");
chat.id = "chat";
chats.appendChild(chat);
}
// nom des membres du channel
let members = await this.display_members(chat);
// L'affiche des messages
let messages = await this.display_messages(chat);
// Input pour rentrer un message
let chat_input = document.getElementById("chat-input") || document.createElement("input");
chat_input.id="chat_input";
chat_input.type="text";
chat_input.name="message";
chat_input.placeholder="message bozo";
chat_input.maxLength=255;
chat.appendChild(chat_input);
let members_id = this.channelManager.channel.members_id;
chat_input.onkeydown = async () => {
if (event.keyCode == 13 && this.channelManager.channel != undefined) {
//let chat_input = document.getElementById("chat-input");
let chat_text = chat_input.value;
let receivers_id = [];
members_id.forEach((member_id) => {
if (member_id != client.me.id)
receivers_id.push(this.profiles.filter(user => user.id == member_id)[0].id);
});
await this.channelManager.channel.sendMessageChannel(chat_text, receivers_id);
// Reset
chat_input.value = "";
}
};
chat_input.focus();
// Scroll to the bottom of messages
messages.scrollTop = messages.scrollHeight;
// this.display_invite();
}
async display_messages(chat) {
let messages = document.createElement("div");
messages.id = "messages";
chat.appendChild(messages);
// les messages, réecriture seulement du dernier
this.channelManager.channel.messages.forEach((message) => {
let text = document.createElement("p");
let date = new Date(message.time);
text.title = date.toLocaleString("fr-FR");
text.appendChild(document.createTextNode(message.content));
console.log(message.author, client.me.id)
text.id = message.author === client.me.id ? "you" : "other";
messages.appendChild(text);
});
return messages;
}
async reload_display_messages() {
let messages = document.getElementById("messages");
let i = 0;
this.channelManager.channel.messages.forEach((message) => {
if (messages.children[i] == null || message.content != messages.children[i].innerText) {
let text = document.createElement("p");
let date = new Date(message.time);
text.title = date.toLocaleString("fr-FR");
text.appendChild(document.createTextNode(message.content));
text.id = message.author === client.me.id ? "you" : "other";
messages.appendChild(text);
}
i++;
});
messages.scrollTop = messages.scrollHeight;
}
async display_members(chat) {
let members_id = this.channelManager.channel.members_id;
let members = document.createElement("h2");
members.id = "members";
let usernames = "";
members_id.forEach((member_id) => {
if (member_id != client.me.id) {
if (usernames.length > 0)
usernames += ", ";
usernames += (this.profiles.filter(user => user.id == member_id)[0].username);
}
});
members.textContent = usernames;
chat.appendChild(members);
return members;
}
async display_invite() {
let chat = document.getElementById("chat");
if (chat == undefined)
return ;
let members_id = this.channelManager.channel.members_id;
let others = members_id.filter(id => id !== client.me.id);
let invite = document.getElementById("invite") || document.createElement("button");
let yes = document.getElementById("yes") || document.createElement("button");
let no = document.getElementById("no") || document.createElement("button");
let invitedBy;
if (invitedBy == undefined) {
if (yes && no) {
yes.remove();
no.remove();
}
// Button to send invite to play
invite.id = "invite";
invite.style.background = "orange";
invite.innerText = "invite";
invite.title = "Invite to play a game";
invite.onclick = async () => {
await client.notice.send_invite(others);
};
chat.appendChild(invite);
}
else {
if (invite)
invite.remove();
yes.id = "yes";
yes.style.background = "green";
yes.title = "Accept to play a game";
yes.onclick = async () => {
await client.notice.accept_invite(invitedBy);
};
no.id = "no";
no.style.background = "red";
no.title = "Refuse to play a game";
no.onclick = async () => {
await client.notice.refuse_invite(invitedBy);
};
chat.appendChild(yes);
chat.appendChild(no);
}
}
async hide_chat() {
let closes = ["chat", "invite"];
closes.forEach(close => {
if (document.getElementById(close))
document.getElementById(close).remove();
});
}
async leavePage() {
if (this.channelManager.channel != undefined)
this.channelManager.channel.disconnect();
this.channelManager.channel = undefined;
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/search.css">
<div id="chats">
<div id="users">
<input id="username-input" type="text" name="message" placeholder="userbozo"/>
<ul id='user-list'>
</ul>
</div>
</div>
`;
}
}

View File

@ -0,0 +1,332 @@
import {client, lang, navigateTo} from '../index.js';
import {clearElements, fill_errors} from '../utils/formUtils.js'
import AbstractAuthenticatedView from './abstracts/AbstractAuthenticatedView.js';
export default class extends AbstractAuthenticatedView
{
constructor(params)
{
super(params, 'settingsWindowTitle');
this.PROFILE_PICTURE_MAX_SIZE = 2 * 1024 * 1024; // 2MB
}
async postInit()
{
this.avatarInit();
this.usernameInit();
this.passwordInit();
this.deleteInit();
}
deleteInit() {
const deleteInput = document.getElementById('deleteInput');
const deleteModal = document.getElementById('deleteModal');
deleteModal.addEventListener('shown.bs.modal', _ => {
deleteInput.focus();
});
deleteModal.addEventListener('hidden.bs.modal', _ => {
deleteInput.value = '';
});
deleteInput.onkeydown = e => {
if (e.key === 'Enter')
this.deleteAccount();
}
document.getElementById('deleteButton').onclick = this.deleteAccount;
}
passwordInit() {
document.getElementById('currentPasswordInput').onkeydown = e => {
if (e.key === 'Enter')
this.savePassword();
};
document.getElementById('newPasswordInput').onkeydown = e => {
if (e.key === 'Enter')
this.savePassword();
};
document.getElementById('newPassword2Input').onkeydown = e => {
if (e.key === 'Enter')
this.savePassword();
};
document.getElementById('passwordSave').onclick = this.savePassword;
}
usernameInit() {
const usernameInput = document.getElementById('usernameInput');
const usernameSave = document.getElementById('usernameSave');
usernameInput.oninput = e => {
const value = e.target.value;
if (value != client.me.username && value.length)
usernameSave.classList.remove('disabled');
else
usernameSave.classList.add('disabled');
}
usernameSave.onclick = _ => this.saveUsername();
}
avatarInit() {
const avatar = document.getElementById('avatar');
const avatarInput = document.getElementById('avatarInput');
const avatarUpload = document.getElementById('avatarUpload');
const avatarDelete = document.getElementById('avatarDelete');
avatar.onclick = _ => avatarInput.click();
avatarInput.onchange = function () {
const selectedFile = this.files[0];
if (!selectedFile)
return;
avatar.src = URL.createObjectURL(selectedFile);
avatarUpload.classList.remove('d-none');
}
avatarUpload.onclick = _ => this.saveAvatar();
avatarDelete.onclick = _ => this.deleteAvatar();
}
async displayAvatar() {
let avatar = document.getElementById('avatar');
avatar.src = client.me.avatar + '?t=' + new Date().getTime();
}
async savePassword() {
const currentPasswordInput = document.getElementById('currentPasswordInput');
const currentPassword = currentPasswordInput.value;
const currentPasswordDetail = document.getElementById('currentPasswordDetail');
const newPasswordInput = document.getElementById('newPasswordInput');
const newPassword = newPasswordInput.value;
const newPasswordDetail = document.getElementById('newPasswordDetail');
const newPassword2Input = document.getElementById('newPassword2Input');
const newPassword2 = newPassword2Input.value;
const newPassword2Detail = document.getElementById('newPassword2Detail');
const passwordDetail = document.getElementById('passwordDetail');
clearElements('innerHTML', [currentPasswordDetail,
newPasswordDetail,
newPassword2Detail,
passwordDetail
]);
currentPasswordInput.classList.remove('is-invalid');
newPasswordInput.classList.remove('is-invalid');
newPassword2Input.classList.remove('is-invalid');
if (!currentPassword.length) {
currentPasswordDetail.innerHTML = lang.get('errorEmptyField');
currentPasswordInput.classList.add('is-invalid');
}
if (!newPassword.length) {
newPasswordDetail.innerHTML = lang.get('errorEmptyField');
newPasswordInput.classList.add('is-invalid');
}
if (!newPassword2.length) {
newPassword2Detail.innerHTML = lang.get('errorEmptyField');
newPassword2Input.classList.add('is-invalid');
}
if (!currentPassword.length || !newPassword.length || !newPassword2.length)
return;
const error = await client.account.updatePassword(currentPassword, newPassword, newPassword2);
if (!error) {
passwordDetail.classList.remove('text-danger');
passwordDetail.classList.add('text-success');
passwordDetail.innerHTML = lang.get('settingsPasswordSaved');
setTimeout(_ => passwordDetail.innerHTML = '', 3000);
clearElements('value', [currentPasswordInput, newPasswordInput, newPassword2Input]);
} else {
passwordDetail.classList.add('text-danger');
passwordDetail.classList.remove('text-success');
fill_errors(error, 'innerHTML');
if (error.currentPasswordDetail)
currentPasswordInput.classList.add('is-invalid');
if (error.newPasswordDetail)
newPasswordInput.classList.add('is-invalid');
if (error.newPassword2Detail)
newPassword2Input.classList.add('is-invalid');
}
}
async saveUsername()
{
const usernameInput = document.getElementById('usernameInput');
const username = usernameInput.value;
const usernameDetail = document.getElementById('usernameDetail');
if (!username.length || username === client.me.username)
return;
const error = await client.account.updateUsername(username);
if (!error) {
usernameDetail.classList.remove('text-danger', 'd-none');
usernameDetail.classList.add('text-success');
usernameDetail.innerHTML = lang.get('settingsUsernameSaved');
setTimeout(_ => usernameDetail.classList.add('d-none'), 2000);
document.getElementById('usernameSave').classList.add('disabled');
} else {
usernameDetail.classList.remove('text-success', 'd-none');
usernameDetail.classList.add('text-danger');
usernameDetail.innerHTML = error;
document.getElementById('usernameSave').classList.add('disabled');
console.log(error);
}
}
async saveAvatar()
{
const avatarInput = document.getElementById('avatarInput');
const selectedFile = avatarInput.files[0];
const avatarDetail = document.getElementById('avatarDetail');
if (!selectedFile)
return;
if (selectedFile.size > this.PROFILE_PICTURE_MAX_SIZE) {
avatarDetail.classList.remove('text-success');
avatarDetail.classList.add('text-danger');
avatarDetail.innerHTML = lang.get('settingsAvatarTooLarge');
return;
}
const error = await client.me.changeAvatar(selectedFile);
if (!error) {
avatarDetail.classList.remove('text-danger');
avatarDetail.classList.add('text-success');
avatarDetail.innerHTML = lang.get('settingsAvatarSaved');
setTimeout(_ => avatarDetail.innerHTML = '', 2000);
document.getElementById('avatarDelete').classList.remove('d-none');
document.getElementById('avatarUpload').classList.add('d-none');
avatarInput.value = null;
} else {
avatarDetail.classList.remove('text-success');
avatarDetail.classList.add('text-danger');
avatarDetail.innerHTML = error.avatar[0];
document.getElementById('avatarUpload').classList.add('d-none');
avatarInput.value = null;
console.log(error);
}
this.displayAvatar();
}
async deleteAvatar() {
const avatarDetail = document.getElementById('avatarDetail');
const error = await client.me.deleteAvatar();
if (!error) {
avatarDetail.classList.remove('text-danger');
avatarDetail.classList.add('text-success');
avatarDetail.innerHTML = lang.get('settingsAvatarDeleted');
setTimeout(_ => avatarDetail.innerHTML = '', 2000);
document.getElementById('avatarDelete').classList.add('d-none');
} else {
avatarDetail.classList.remove('text-success');
avatarDetail.classList.add('text-danger');
avatarDetail.innerHTML = lang.get('settingsAvatarDeleteError');
}
this.displayAvatar();
}
async deleteAccount() {
const passwordInput = document.getElementById('deleteInput');
const password = passwordInput.value;
const passwordDetail = document.getElementById('deleteDetail');
passwordInput.classList.remove('is-invalid');
passwordDetail.innerHTML = '';
if (!password.length) {
passwordInput.classList.add('is-invalid');
passwordDetail.innerHTML = lang.get('errorEmptyField');
return;
}
const error = await client.account.delete(password);
if (!error) {
passwordDetail.classList.replace('text-danger', 'text-success');
passwordDetail.innerHTML = lang.get('settingsDeleteSuccess');
setTimeout(_ => {
bootstrap.Modal.getInstance(document.getElementById('deleteModal')).hide();
navigateTo('/login');
}, 2000);
return;
}
passwordInput.classList.add('is-invalid');
passwordDetail.innerHTML = error['password'];
}
async getHtml()
{
const avatarUnchanged = client.me.avatar === '/static/avatars/default.avif';
return /* HTML */ `
<div class='container-fluid col-lg-8 d-flex rounded border border-2 bg-light-subtle py-3'>
<div class='row col-4 bg-body-tertiary border rounded p-2 m-2 d-flex justify-content-center align-items-center'>
<h2 class='border-bottom'>${lang.get('settingsAvatarTitle')}</h2>
<img id='avatar' class='rounded p-0' src=${client.me.avatar} style='cursor: pointer;'>
<input id='avatarInput' class='d-none' type='file' accept='image/*'>
<div class='d-flex gap-2 mt-1 px-0'>
<span class='my-auto ms-1 me-auto' id='avatarDetail'></span>
<button class='btn btn-primary d-none' id='avatarUpload'>${lang.get('settingsAvatarSave')}</button>
<button class='btn btn-danger${avatarUnchanged ? ' d-none' : ''}' id='avatarDelete'>${lang.get('settingsAvatarDelete')}</button>
</div>
</div>
<div class='flex-grow-1'>
<h1 class='border-bottom ps-1 mb-3'>${lang.get('settingsTitle')}</h1>
<div class='d-flex flex-column gap-2'>
<div>
<div class='input-group'>
<div class='form-floating'>
<input type='text' class='form-control' id='usernameInput' placeholder='${lang.get('settingsUsername')}' value=${client.me.username}>
<label for='usernameInput'>${lang.get('settingsUsername')}</label>
</div>
<button class='input-group-text btn btn-success disabled' id='usernameSave'>${lang.get('settingsUsernameSave')}</button>
</div>
<span class='form-text' id='usernameDetail'></span>
</div>
<div class='flex-grow-1 p-1 border rounded bg-body-tertiary'>
<h5 class='ps-1 border-bottom'>${lang.get('settingsPasswordTitle')}</h5>
<div class='d-flex flex-column gap-1'>
<div class='form-floating'>
<input type='password' class='form-control' id='currentPasswordInput' placeholder='${lang.get('settingsCurrentPassword')}'>
<label for='currentPasswordInput'>${lang.get('settingsCurrentPassword')}</label>
</div>
<span class='text-danger ps-1' id='currentPasswordDetail'></span>
<div class='form-floating'>
<input type='password' class='form-control' id='newPasswordInput' placeholder='${lang.get('settingsNewPassword')}'>
<label for='newPassword2Input'>${lang.get('settingsNewPassword')}</label>
</div>
<span class='text-danger ps-1' id='newPasswordDetail'></span>
<div class='form-floating'>
<input type='password' class='form-control' id='newPassword2Input' placeholder='${'settingsRepeatNewPassword'}'>
<label for='newPassword2Input'>${lang.get('settingsRepeatNewPassword')}</label>
</div>
<span class='text-danger ps-1' id='newPassword2Detail'></span>
<div class='d-flex flex-row'>
<span class='ps-1 my-auto text-danger' id='passwordDetail'></span>
<button class='btn btn-success ms-auto' id='passwordSave'>${lang.get('settingsPasswordSave')}</button>
</div>
</div>
</div>
<button class='btn btn-outline-danger' data-bs-toggle='modal' data-bs-target='#deleteModal'>${lang.get('settingsDeleteButton')}</button>
<div class='modal fade' id='deleteModal' tabindex='-1'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-header bg-danger bg-gradient'>
<h1 class='modal-title fs-5'>${lang.get('settingsDeleteTitle')}</h1>
<button class='btn-close' data-bs-dismiss='modal'></button>
</div>
<div class='modal-body'>
<h6 class='form-label'>${lang.get('settingsDeleteConfirm')}</h6>
<input type='password' class='form-control' id='deleteInput' placeholder='${lang.get('settingsDeleteInput')}'>
<span class='form-text text-danger' id='deleteDetail'></span>
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-secondary' data-bs-dismiss='modal'>${lang.get('settingsDeleteCancel')}</button>
<button type='button' class='btn btn-danger' id='deleteButton'>${lang.get('settingsDeleteButton')}</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
}
}

View File

@ -0,0 +1,253 @@
import { lang } from "../index.js";
import AbstractView from "./abstracts/AbstractView.js";
export class TicTacToeOfflineView extends AbstractView
{
constructor(params)
{
super(params, lang.get('ticTacToe'));
this.width = 510;
this.height = 510;
this.morpion = [[],[],[],[],[],[],[],[],[]];
this.rectsize = 48;
this.gap = 40;
this.playerTurn = 0;
this.playerOneCurrentTab = 4;
this.playerTwoCurrentTab = 4;
this.Winner = -1;
}
async leavePage()
{
this.canvas.removeEventListener("mousedown", this.onClick, false);
}
onClick(event)
{
let targetTabX, targetTabY, targetTab;
let squareTab;
let x = event.offsetX;
let y = event.offsetY;
if (this.Winner != -1)
return;
function findPlace(x, morpion)
{
if (x <= morpion.gap || x >= morpion.gap + morpion.rectsize * 9)
return -1;
if (x <= morpion.gap + morpion.rectsize * 3)
return 0;
if (x >= morpion.gap + morpion.rectsize * 3 && x <= morpion.gap + morpion.rectsize * 6)
return 1;
if (x >= morpion.gap + morpion.rectsize * 6)
return 2;
return -1;
}
function drawNewCase(targetTab, squareTab, morpion)
{
let targetX = (morpion.gap + targetTab % 3 * morpion.rectsize * 3) + (squareTab % 3 * morpion.rectsize);
let targetY = (morpion.gap + Math.floor(targetTab / 3) * morpion.rectsize * 3) + (Math.floor(squareTab / 3) * morpion.rectsize);
if (!morpion.playerTurn)
{
morpion.ctx.beginPath();
morpion.ctx.strokeStyle = "green";
morpion.ctx.moveTo(targetX + 10, targetY + 10);
morpion.ctx.lineTo(targetX + 40, targetY + 40);
morpion.ctx.moveTo(targetX + 40, targetY + 10);
morpion.ctx.lineTo(targetX + 10, targetY + 40);
morpion.ctx.stroke();
morpion.ctx.closePath();
}
else
{
morpion.ctx.beginPath();
morpion.ctx.strokeStyle = "red";
targetX += morpion.rectsize / 2;
targetY += morpion.rectsize / 2;
morpion.ctx.arc(targetX, targetY, 15, 0, 2 * Math.PI);
morpion.ctx.stroke();
morpion.ctx.closePath();
}
}
function PaintCurrentTab(morpion, currentTab, color)
{
let targetX = (morpion.gap + currentTab % 3 * morpion.rectsize * 3);
let targetY = (morpion.gap + Math.floor(currentTab / 3) * morpion.rectsize * 3);
morpion.ctx.beginPath();
morpion.ctx.strokeStyle = color;
morpion.ctx.lineWidth = 6;
morpion.ctx.strokeRect(targetX, targetY, morpion.rectsize * 3, morpion.rectsize * 3);
morpion.ctx.closePath();
}
function highlightNewTab(morpion)
{
if (!morpion.playerTurn)
PaintCurrentTab(morpion, morpion.playerOneCurrentTab, "green");
else
PaintCurrentTab(morpion, morpion.playerTwoCurrentTab, "red");
}
function checkForWinningMove(morpion, targetTab)
{
let count = 0;
for (let i = 0; i < 3; i++)
{
if (morpion.morpion[targetTab][i] != -1 && (morpion.morpion[targetTab][i] === morpion.morpion[targetTab][i + 3] && morpion.morpion[targetTab][i + 3] === morpion.morpion[targetTab][i + 6]))
morpion.Winner = !morpion.playerTurn;
}
for (let i = 0; i < morpion.morpion.length; i += 3)
{
if (morpion.morpion[targetTab][i] != -1 && (morpion.morpion[targetTab][i] === morpion.morpion[targetTab][i + 1] && morpion.morpion[targetTab][i + 1] === morpion.morpion[targetTab][i + 2]))
morpion.Winner = !morpion.playerTurn;
}
if (morpion.morpion[targetTab][0] != -1 && (morpion.morpion[targetTab][0] === morpion.morpion[targetTab][4] && morpion.morpion[targetTab][4] === morpion.morpion[targetTab][8]))
morpion.Winner = !morpion.playerTurn;
if (morpion.morpion[targetTab][2] != -1 && (morpion.morpion[targetTab][2] === morpion.morpion[targetTab][4] && morpion.morpion[targetTab][4] === morpion.morpion[targetTab][6]))
morpion.Winner = !morpion.playerTurn;
morpion.morpion[targetTab].forEach((v) => (v !== -1 && count++));
if (count == 9 && morpion.Winner == -1)
morpion.Winner = morpion.playerTurn;
}
function paintWinningNotification(morpion)
{
morpion.ctx.beginPath();
morpion.ctx.fillStyle = "white";
morpion.ctx.fillRect(morpion.width / 2 - 200, morpion.height - morpion.gap + 10, 400, 80);
morpion.ctx.closePath();
morpion.ctx.beginPath();
morpion.ctx.fillStyle = (morpion.Winner) ? "red" : "green";
morpion.ctx.fillText((morpion.Winner) ? "Winner is : O" : "Winner is : X", morpion.width / 2 - 30, morpion.height - morpion.gap / 2, 140);
morpion.ctx.closePath();
}
function updateMorpion(targetTab, squareTab, morpion)
{
if (morpion.playerTurn && targetTab != morpion.playerTwoCurrentTab)
return -1;
if (!morpion.playerTurn && targetTab != morpion.playerOneCurrentTab)
return -1;
if (morpion.morpion[targetTab][squareTab] == -1)
morpion.morpion[targetTab][squareTab] = (morpion.playerTurn) ? 1 : 0;
else
return -1;
drawNewCase(targetTab, squareTab, morpion);
morpion.playerTurn = !morpion.playerTurn;
PaintCurrentTab(morpion, targetTab, "white");
if (!morpion.playerTurn)
morpion.playerTwoCurrentTab = squareTab;
else
morpion.playerOneCurrentTab = squareTab;
checkForWinningMove(morpion, targetTab);
if (morpion.Winner != -1)
{
paintWinningNotification(morpion);
return;
}
highlightNewTab(morpion);
return 0;
}
function findSquare(x, gap, morpion)
{
if (x <= gap + morpion.rectsize)
return 0;
if (x >= gap + morpion.rectsize && x <= gap + morpion.rectsize * 2)
return 1;
if (x >= gap + morpion.rectsize * 2)
return 2;
return -1;
}
targetTabX = findPlace(x, this);
targetTabY = findPlace(y, this);
if (targetTabX < 0 || targetTabY < 0)
return -1;
targetTab = targetTabX + targetTabY * 3;
squareTab = findSquare(x, this.rectsize * 3 * targetTabX + this.gap, this) + findSquare(y, this.rectsize * 3 * targetTabY + this.gap, this) * 3;
updateMorpion(targetTab, squareTab, this);
}
async postInit()
{
for (let i = 0; i < 9; i++)
for (let j = 0; j < 9; j++)
this.morpion[i].push(-1);
this.canvas = document.getElementById("Morpion");
this.ctx = this.canvas.getContext("2d");
this.ctx.fillStyle = "black";
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
for (let i = 1, x = this.gap, y = this.gap; i <= 9; i++)
{
this.DrawMorpion(x, y);
x += this.rectsize * 3;
if (i % 3 == 0)
{
y += this.rectsize * 3;
x = this.gap;
}
}
this.ctx.lineWidth = 6;
for (let i = 0; i < 4; i++)
{
this.ctx.beginPath();
this.ctx.strokeStyle = `rgb(230 230 230)`;
this.ctx.moveTo(this.gap + i * this.rectsize * 3, this.gap - 3);
this.ctx.lineTo(this.gap + i * this.rectsize * 3, this.canvas.height - this.gap + 3);
this.ctx.stroke();
this.ctx.closePath();
};
for (let i = 0; i < 4; i++)
{
this.ctx.beginPath();
this.ctx.strokeStyle = `rgb(230 230 230)`;
this.ctx.moveTo(this.gap, this.gap + i * this.rectsize * 3);
this.ctx.lineTo(this.canvas.height - this.gap, this.gap + i * this.rectsize * 3);
this.ctx.stroke();
this.ctx.closePath();
}
this.canvas.addEventListener("mousedown", (event) => { this.onClick(event) }, false);
}
DrawMorpion(start_x, start_y)
{
this.ctx.beginPath();
this.ctx.strokeStyle = `rgb(200 200 200)`;
for (let i = 1, x = 0, y = 0; i <= 9; i++)
{
this.ctx.strokeRect(start_x + x, start_y + y, this.rectsize, this.rectsize);
x += this.rectsize;
if (i % 3 == 0)
{
y += this.rectsize;
x = 0;
}
}
this.ctx.closePath();
}
async getHtml()
{
return `
<link rel="stylesheet" href="/static/css/tictactoe.css">
<h1>${lang.get('ticTacToe')}</h1>
<div style="display: flex">
<canvas id="Morpion" width="${this.width}" height="${this.height}"></canvas>
</div>
<div id="rule">
<h2 style="padding-left: 50px"><B>${lang.get('ruleTitle')}</B></h2>
<h5 style="padding-left: 30px">${lang.get('ruleBase')}<br><br></h5>
<h5 style="padding-left: 30px">${lang.get('ruleMovement')}<br><br></h5>
<h5 style="padding-left: 30px">${lang.get('ruleDraw')}</h5>
</div>
`;
}
}

View File

@ -0,0 +1,48 @@
import AbstractView from "./abstracts/AbstractView.js";
import { lang } from "../index.js";
import { TicTacToe } from "../api/game/tictactoe/TicTacToeGame.js"
export class TicTacToeOnlineView extends AbstractView
{
constructor(params, titleKey)
{
super(params, lang.get('ticTacToeTitle'));
this.params = params;
this.titleKey = titleKey;
this.game_id = params.id;
this.height = 510;
this.width = 510;
}
async postInit()
{
this.Morpion = new TicTacToe(this.height, this.width, 30, 50, document.getElementById("Morpion"), this.game_id);
this.Morpion.DrawSuperMorpion();
await this.Morpion.init();
}
async leavePage()
{
this.Morpion.uninit();
}
setTitle()
{
document.title = lang.get(this.titleKey, this.titleKey);
}
async getHtml() {
return `
<link rel="stylesheet" href="/static/css/tictactoe.css">
<div id="canva">
<canvas id="Morpion" width="${this.width}" height="${this.height}"></canvas>
</div>
<div id ="rule">
<h2 style="padding-left: 50px"><B>${lang.get('ruleTitle')}</B></h2>
<h5 style="padding-left: 30px">${lang.get('ruleBase')}<br><br></h5>
<h5 style="padding-left: 30px">${lang.get('ruleMovement')}<br><br></h5>
<h5 style="padding-left: 30px">${lang.get('ruleDraw')}</h5>
</div>
`
}
}

View File

@ -0,0 +1,18 @@
import { client, navigateTo } from "../../index.js";
import AbstractRedirectView from "./AbstractRedirectView.js";
export default class extends AbstractRedirectView{
constructor(params, titleKey, uri = "/login") {
super(params, titleKey, uri);
}
async redirect()
{
if (await client.isAuthenticated() === false)
{
navigateTo(this.redirect_url);
return 1;
}
return 0;
}
}

View File

@ -0,0 +1,16 @@
import { client, navigateTo } from "../../index.js";
import AbstractRedirectView from "./AbstractRedirectView.js";
export default class extends AbstractRedirectView{
constructor(params, titleKey, uri = "/home") {
super(params, titleKey, uri);
}
async redirect()
{
if (await client.isAuthenticated() === false)
return 0;
navigateTo(this.redirect_url);
return 1;
}
}

View File

@ -0,0 +1,15 @@
import { navigateTo } from "../../index.js";
import AbstractView from "./AbstractView.js";
export default class extends AbstractView{
constructor(params, titleKey, uri)
{
super(params, titleKey);
this.redirect_url = uri;
}
async redirect()
{
navigateTo(this.redirect_url);
}
}

View File

@ -0,0 +1,22 @@
import {lang} from '../../index.js';
export default class {
constructor(params, titleKey) {
this.params = params;
this.titleKey = titleKey;
}
async postInit() {
}
async leavePage() {
}
setTitle() {
document.title = lang.get(this.titleKey, this.titleKey);
}
async getHtml() {
return "";
}
}

View File

@ -0,0 +1,176 @@
import { client, lang, navigateTo } from "../../index.js";
import { clearIds, fill_errors } from "../../utils/formUtils.js";
import AbstractNonAuthenticatedView from "../abstracts/AbstractNonAuthenticatedView.js";
export default class extends AbstractNonAuthenticatedView
{
constructor(params, lastUrlBeforeLogin = '/home')
{
super(params, 'loginWindowTitle', lastUrlBeforeLogin);
this.redirect_url = lastUrlBeforeLogin;
}
async leavePage()
{
this.current_mode = undefined;
}
/**
* @returns {Promise}
*/
async postInit()
{
let element = document.getElementById("toggle-register-login");
element.onclick = this.toggle_register_login.bind(this);
let new_mode = location.pathname.slice(1);
this.update_mode(new_mode);
document.getElementById("button").onclick = this.authentication.bind(this);
let username_input = document.getElementById('username-input'),
password_input = document.getElementById('password-input');
[username_input, password_input].forEach(input => {
input.addEventListener('keydown', async ev => {
if (ev.key === 'Enter')
await this.authentication.bind(this)();
});
});
username_input.focus();
}
/**
* Check if field is normal
* @param username {String}
* @param password {String}
* @returns {Boolean}
*/
basic_verif(username, password)
{
if (username === '')
document.getElementById('username').innerHTML = lang.get('errorEmptyField', 'This field may not be blank.');
if (password === '')
document.getElementById('password').innerHTML = lang.get('errorEmptyField', 'This field may not be blank.');
if (username === '' || password === '')
return false;
return true;
}
/**
* @returns { undefined }
*/
toggle_register_login(event)
{
event.preventDefault();
let new_mode = this.current_mode === "register" ? "login" : "register";
this.update_mode(new_mode);
}
/**
* @param {String} new_mode
*/
update_mode(new_mode)
{
if (new_mode === this.current_mode)
return;
this.current_mode = new_mode;
let title = document.getElementById("title"),
username_label = document.getElementById("username-label"),
password_label = document.getElementById("password-label"),
toggle_register_login = document.getElementById("toggle-register-login"),
toggle_register_login_label = document.getElementById("toggle-register-login-label"),
button = document.getElementById("button")
;
let title_text = this.current_mode === "register" ? "registerFormTitle" : "loginFormTitle";
title.innerText = lang.get(title_text, "ERROR LANG");
let username_label_text = this.current_mode === "register" ? "registerFormUsername" : "loginFormUsername";
username_label.innerText = lang.get(username_label_text, "ERROR LANG");
let password_label_text = this.current_mode === "register" ? "registerFormPassword" : "loginFormPassword";
password_label.innerText = lang.get(password_label_text, "ERROR LANG");
let toggle_register_login_label_text = this.current_mode === "register" ? "registerAlreadyAccount" : "loginNoAccount";
toggle_register_login_label.innerText = lang.get(toggle_register_login_label_text, "ERROR LANG");
let toggle_register_login_text = this.current_mode === "register" ? "registerLogin" : "loginRegister";
toggle_register_login.innerText = lang.get(toggle_register_login_text, "ERROR LANG");
let button_text = this.current_mode === "register" ? "registerFormButton" : "loginFormButton";
button.innerText = lang.get(button_text, "ERROR LANG");
this.titleKey = this.current_mode === 'register' ? 'registerWindowTitle' : 'loginWindowTitle';
this.setTitle();
document.getElementById('username-input').focus();
}
/**
* @returns {Promise}
*/
async authentication()
{
let username = document.getElementById("username-input").value,
password = document.getElementById("password-input").value;
if (!this.basic_verif())
return;
let response;
if (this.current_mode === "register")
response = await client.account.create(username, password);
else
response = await client.login(username, password);
if (response.status === 200 || response.status === 201)
{
navigateTo(this.redirect_url);
return;
}
let response_data = await response.json();
clearIds("innerHTML", ["username", "password", 'login']);
fill_errors(response_data, "innerHTML");
}
async getHtml()
{
return /* HTML */ `
<div class='container-fluid'>
<form class='border border-2 rounded bg-light-subtle mx-auto p-2 col-md-7 col-lg-4'>
<h4 class='text-center fw-semibold mb-4' id="title">Loading...</h4>
<div class='form-floating mb-2'>
<input type='text' class='form-control' id='username-input' placeholder='Username'>
<label for='usernameInput' id='username-label'>Loading...</label>
<span class='text-danger' id='username'></span>
</div>
<div class='form-floating'>
<input type='password' class='form-control' id='password-input' placeholder='Password'>
<label for='password-input' id='password-label'>Loading...</label>
<span class='text-danger' id='password'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2' id='button'>Loading...</button>
<span class='text-danger my-auto mx-2' id='login'></span>
<div class='ms-auto mt-auto flex-row d-flex gap-2' id="toggle">
<p id='toggle-register-login-label'>Loading...</p>
<a id="toggle-register-login" href='#'>Loading...</a>
</div>
</div>
</form>
</div>
`;
}
}

View File

@ -0,0 +1,15 @@
import { client, navigateTo } from "../../index.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
export default class extends AbstractAuthenticatedView
{
constructor(params, lastPageUrl = '/login') {
super(params, 'logoutWindowTitle', lastPageUrl);
this.lastPageUrl = lastPageUrl;
}
async postInit() {
await client.logout();
navigateTo(this.lastPageUrl);
}
}