dockered
This commit is contained in:
20
django/frontend/static/js/views/HomeView.js
Normal file
20
django/frontend/static/js/views/HomeView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
143
django/frontend/static/js/views/MatchMakingView.js
Normal file
143
django/frontend/static/js/views/MatchMakingView.js
Normal 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();
|
||||
}
|
||||
}
|
16
django/frontend/static/js/views/PageNotFoundView.js
Normal file
16
django/frontend/static/js/views/PageNotFoundView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
656
django/frontend/static/js/views/PongOfflineView.js
Normal file
656
django/frontend/static/js/views/PongOfflineView.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
261
django/frontend/static/js/views/PongOnlineView.js
Normal file
261
django/frontend/static/js/views/PongOnlineView.js
Normal 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()
|
||||
}
|
||||
}
|
234
django/frontend/static/js/views/ProfilePageView.js
Normal file
234
django/frontend/static/js/views/ProfilePageView.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
350
django/frontend/static/js/views/Search.js
Normal file
350
django/frontend/static/js/views/Search.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
332
django/frontend/static/js/views/SettingsView.js
Normal file
332
django/frontend/static/js/views/SettingsView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
253
django/frontend/static/js/views/TicTacToeOfflineView.js
Normal file
253
django/frontend/static/js/views/TicTacToeOfflineView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
48
django/frontend/static/js/views/TicTacToeOnlineView.js
Normal file
48
django/frontend/static/js/views/TicTacToeOnlineView.js
Normal 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>
|
||||
`
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
22
django/frontend/static/js/views/abstracts/AbstractView.js
Normal file
22
django/frontend/static/js/views/abstracts/AbstractView.js
Normal 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 "";
|
||||
}
|
||||
}
|
176
django/frontend/static/js/views/accounts/AuthenticationView.js
Normal file
176
django/frontend/static/js/views/accounts/AuthenticationView.js
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
15
django/frontend/static/js/views/accounts/LogoutView.js
Normal file
15
django/frontend/static/js/views/accounts/LogoutView.js
Normal 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user