This commit is contained in:
2024-04-27 08:56:51 +02:00
57 changed files with 974 additions and 822 deletions

View File

@ -4,7 +4,7 @@ import { Profiles } from "./Profiles.js";
import { Channels } from './chat/Channels.js';
import { MyProfile } from "./MyProfile.js";
import { Tourmanents } from "./tournament/Tournaments.js";
import { Notice } from "./chat/Notice.js";
import Notice from "./notice/Notice.js";
import { Channel } from "./chat/Channel.js";
import LanguageManager from './LanguageManager.js';
@ -83,7 +83,7 @@ class Client
{
if (this._logged == undefined)
this._logged = await this._test_logged();
return this._logged;
return this._logged;
}
/**
@ -217,7 +217,7 @@ class Client
{
this.me = new MyProfile(this);
await this.me.init();
this.notice.connect();
this.notice.start();
document.getElementById('navbarLoggedOut').classList.add('d-none');
document.getElementById('navbarLoggedIn').classList.remove('d-none');
document.getElementById('navbarDropdownButton').innerHTML = this.me.username;
@ -226,7 +226,7 @@ class Client
else
{
this.me = undefined;
this.notice.disconnect();
this.notice.stop();
document.getElementById('navbarLoggedOut').classList.remove('d-none');
document.getElementById('navbarLoggedIn').classList.add('d-none');
document.getElementById('navbarDropdownButton').innerHTML = 'Me';

View File

@ -21,12 +21,12 @@ class MatchMaking
* @param {Number} mode The number of players in a game
* @returns {Promise<?>}
*/
async start(receive_func, disconnect_func, gamemode, mode)
async start(receive_func, disconnect_func, game_type, mode)
{
if (!await this.client.isAuthenticated())
return null;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${gamemode}/${mode}`;
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/matchmaking/${game_type}/${mode}`;
this._socket = new WebSocket(url);

View File

@ -18,7 +18,7 @@ class MyProfile extends Profile
/**
* @type {[Profile]}
*/
this.friends = [];
this.friendList = [];
/**
* @type {[Profile]}
*/
@ -40,28 +40,74 @@ class MyProfile extends Profile
async getBlockedUsers() {
const response = await this.client._get('/api/profiles/block');
const data = await response.json();
data.forEach(profileData => this.blockedUsers.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar)));
data.forEach(profileData => this.blockedUsers.push(new Profile(this.client, profileData.username, profileData.id, profileData.avatar)));
}
async getFriends() {
const response = await this.client._get('/api/profiles/friends');
const data = await response.json();
data.forEach(profileData => this.friends.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar)));
data.forEach(profileData => this.friendList.push(new Profile(this.client, profileData.username, profileData.id, profileData.avatar)));
}
async getIncomingFriendRequests() {
const response = await this.client._get('/api/profiles/incoming_friend_requests');
const data = await response.json();
data.forEach(profileData => this.incomingFriendRequests.push(
new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar)
new Profile(this.client, profileData.username, profileData.id, profileData.avatar)
));
}
async getOutgoingFriendRequests() {
const response = await this.client._get('/api/profiles/outgoing_friend_requests');
const data = await response.json();
data.forEach(profileData => this.outgoingFriendRequests.push(
new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar)
new Profile(this.client, profileData.username, profileData.id, profileData.avatar)
));
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_isFriend(profile) {
for (const user of this.friendList) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_isBlocked(profile) {
for (const user of this.blockedUsers) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_hasIncomingRequestFrom(profile) {
for (const user of this.incomingFriendRequests) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
* @param {Profile} profile
* @returns {Boolean}
*/
_hasOutgoingRequestTo(profile) {
for (const user of this.outgoingFriendRequests) {
if (user.id === profile.id)
return true;
}
return false;
}
/**
*
* @param {File} selectedFile

View File

@ -33,8 +33,10 @@ export class Profile extends AExchangeable
/**
* @type {Boolean}
*/
this.isBlocked = false;
this.isFriend = false;
this.isFriend;
this.isBlocked;
this.hasIncomingRequest;
this.hasOutgoingRequest;
}
/**
@ -53,10 +55,17 @@ export class Profile extends AExchangeable
return response.status;
let response_data = await response.json();
this.id = response_data.user_id;
this.id = response_data.id;
this.username = response_data.username;
this.avatar = response_data.avatar;
if (!this.client.me || this.client.me.id === this.id)
return;
this.isBlocked = this.client.me._isBlocked(this);
this.hasIncomingRequest = this.client.me._hasIncomingRequestFrom(this);
this.hasOutgoingRequest = this.client.me._hasOutgoingRequestTo(this);
this.isFriend = this.client.me._isFriend(this);
}
/**

View File

@ -24,7 +24,7 @@ class Profiles
let profiles = [];
response_data.forEach((profile) => {
profiles.push(new Profile(this.client, profile.username, profile.user_id, profile.avatar));
profiles.push(new Profile(this.client, profile.username, profile.id, profile.avatar));
});
return profiles;
}
@ -54,40 +54,6 @@ class Profiles
return null;
return profile;
}
/**
* Block a user
* @param {Number} user_id
* @returns {Promise<Object>}
*/
async block(user_id) {
// blocker & blocked
let response = await this.client._post("/api/profiles/block", {
users_id:[this.client.me.id, user_id],
});
let data = await response.json();
return data;
}
/**
* Unblock a user
* @param {Number} user_id
* @returns {Promise<Object>}
*/
async deblock(user_id) {
// blocker & blocked
let response = await this.client._delete("/api/profiles/block", {
users_id:[this.client.me.id, user_id],
});
let data = await response.json();
return data;
}
}
export {Profiles};

View File

@ -1,308 +0,0 @@
import { navigateTo } from "../../index.js";
import {createNotification} from "../../utils/noticeUtils.js";
class Notice {
constructor(client) {
this.client = client;
this.data = {};
// users online, invited by ..., asked by ..., asker to ...
let data_variable = ["online", "invited", "asked", "asker"];
for (let i in data_variable)
this.data[data_variable[i]] = [];
//this.connect();
}
async connect() {
return
let url = `${window.location.protocol[4] === 's' ? 'wss' : 'ws'}://${window.location.host}/ws/chat/notice`;
this.chatSocket = new WebSocket(url);
this.chatSocket.onmessage = (event) =>{
let send = JSON.parse(event.data);
//console.log("notice: ", send);
try {
this["receive_" + send.type](send);
}
catch (error) {
console.log("receive_" + send.type + ": Function not found");
}
};
this.chatSocket.onopen = (event) => {
this.getOnlineUser();
this.ask_friend();
};
}
async disconnect() {
if (this.chatSocket == undefined)
return ;
this.chatSocket.close();
}
async reconnect() {
this.disconnect();
this.connect();
}
async accept_invite(invitedBy) {
this.sendRequest({
type: "accept_invite",
targets: [invitedBy],
});
}
async receive_accept_invite(send) {
this.data.invited = send.invites;
let id_game = send.id_game;
navigateTo("/game/" + id_game);
}
async refuse_invite(invitedBy) {
this.sendRequest({
type: "refuse_invite",
targets: [invitedBy],
});
}
async receive_refuse_invite(send) {
this.data.invited = send.invites;
if (send.author_id == this.client.me.id) {
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
else {
let sender = await this.client.profiles.getProfileId(send.author_id);
createNotification(sender.username + " refuse your invitation");
}
}
async send_invite(id_inviteds) {
this.sendRequest({
type: "invite",
targets: id_inviteds,
time: new Date().getTime(),
});
}
async receive_invite(send) {
if (this.client.me == undefined)
return ;
let content = send.invites;
if (send.author_id == this.client.me.id) {
if (send.status == 200) {
for (let target in send.targets)
return createNotification("Invitation send");
}
else if (send.status == 444)
return createNotification("User not connected");
else if (send.status == 409)
return createNotification("Already invited");
}
else {
// Regarder qu'il est bien invité par l'auteur
// Et qu'il n'est pas déjà invité
if (!content.includes(send.author_id) ||
this.data.invited.includes(send.author_id))
return;
this.data.invited = content;
let sender = await this.client.profiles.getProfileId(send.author_id);
createNotification("Invitation received by " + sender.username);
if (this.rewrite_invite !== undefined)
this.rewrite_invite();
}
}
async getOnlineUser() {
this.online_users = {};
this.sendRequest({
type: "online_users",
targets: [],
time: new Date().getTime(),
});
}
async receive_online_users(send) {
let content = send.online;
if (content !== undefined) {
if (this.data.online.length > 0) {
// get all disconnect user
//let disconnects = this.data["online"].filter(id => !Object.keys(content).includes(id));
let disconnects = [];
for (const [key, value] of Object.entries(this.data.online)) {
if (content[key] == "red" && value == "green")
disconnects.push(key);
}
// delete invite
this.data.invited = this.data.invited.filter(id => !disconnects.includes(id));
//console.log(this.data["invited"]);
}
this.data.online = content;
if (this.rewrite_usernames !== undefined)
this.rewrite_usernames();
}
}
async ask_friend(user_id=undefined) {
this.sendRequest({
type: "ask_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_ask_friend(send) {
let my_id = (this.client.me && this.client.me.id) || send.author_id;
if (send.author_id == my_id) {
if (send.status == 400)
createNotification("Friend ask error");
else if (send.status == 409)
createNotification("Already asked friend");
}
//if (!send.asked.includes(send.author_id) ||
//this.data["asked"].includes(send.author_id))
//return;
//if (!send.asker.includes(send.author_id) ||
//this.data["asker"].includes(send.author_id))
//return;
this.data.asked = send.asked;
this.data.asker = send.asker;
if (send.author_id != my_id) {
let sender = await this.client.profiles.getProfileId(send.author_id);
if (this.data.asker.includes(send.author_id))
createNotification(sender.username + " ask you as friend");
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
}
async remove_friend(user_id) {
this.sendRequest({
type: "remove_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_remove_friend(send) {
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error remove Friend");
else if (send.status == 409)
createNotification("Not friend, wtf");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async accept_friend(user_id) {
this.sendRequest({
type: "accept_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_accept_friend(send) {
this.data.asked = send.asked;
this.data.asker = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error accept Friend");
else if (send.status == 404)
createNotification("Not found request Friend");
else if (send.status == 409)
createNotification("Already Friend, wtf");
}
else {
createNotification(sender.username + " accept your friend request");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
this.receive_online_users(send);
}
async refuse_friend(user_id) {
this.sendRequest({
type: "refuse_friend",
targets: [user_id],
time: new Date().getTime(),
});
}
async receive_refuse_friend(send) {
this.data.asked = send.asked;
this.data.asker = send.asker;
let sender = await this.client.profiles.getProfileId(send.author_id);
if (send.author_id == this.client.me.id) {
if (send.status == 400)
createNotification("Error refuse Friend");
else if (send.status == 404)
createNotification("Not found request Friend");
else if (send.status == 409)
createNotification("Already Friend, WTF");
}
if (this.rewrite_profile !== undefined)
await this.rewrite_profile();
}
async sendRequest(content) {
if (this.chatSocket == undefined)
return;
this.chatSocket.send(JSON.stringify(content));
}
}
export {Notice};

View File

@ -18,12 +18,12 @@ class TicTacToe
this.canvas = canvas
this.context = this.canvas.getContext("2d");
this.sign;
this.currentMorpion = 4;
this.turn;
}
async init()
{
console.log(this.game_id);
await this.game.join();
this.canvas.addEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion));
}
@ -37,22 +37,46 @@ class TicTacToe
async onReceive(messageData)
{
console.log(messageData)
if (messageData.detail == "x" || messageData.detail == "o")
switch (messageData.detail)
{
this.sign = messageData.detail;
this.turn = messageData.detail == "x";
}
else if (messageData.detail == "game_start")
this.game.started = true;
else if (messageData.targetMorpion && messageData.targetCase)
{
this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0;
this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x");
if (this.checkWin() != -1)
printWin();
case 'x':
case 'o':
this.sign = messageData.detail;
this.turn = messageData.detail == "x";
if (this.turn)
this.setOutline(4, false);
break;
case 'game_start':
this.game.started = true;
this.game.finished = false;
break;
case 'game_move':
this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0;
this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x");
this.setOutline(this.currentMorpion, false);
break;
case 'game_end':
this.game.finished = true;
this.canvas.removeEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion));
this.printWin(messageData.winning_sign);
break;
}
}
printWin(winning_sign)
{
this.context.beginPath();
this.context.fillStyle = "white";
this.context.fillRect(this.width / 2 - 200, this.height - this.gap + 10, 400, 80);
this.context.closePath();
this.context.beginPath();
this.context.fillStyle = (winning_sign == "o") ? "red" : "green";
this.context.fillText((winning_sign == "o") ? "Winner is : O" : "Winner is : X", this.width / 2 - 30, this.height - this.gap / 2, 140);
this.context.closePath();
}
checkWin()
{
for (let i = 0; i < 9; i++)
@ -85,6 +109,10 @@ class TicTacToe
let y = event.offsetY;
let targetMorpion, targetCase;
if (this.game.finished)
{
return;
}
targetMorpion = morpion.findPlace(x, this) + morpion.findPlace(y, this) * 3;
if (morpion.findPlace(x, this) < 0 || morpion.findPlace(y, this) < 0)
return -1;
@ -92,8 +120,8 @@ class TicTacToe
if (morpion.checkCase(targetMorpion, targetCase))
{
morpion.setOutline(this.currentMorpion, true);
morpion.sendCase(targetMorpion, targetCase);
morpion.setOutline();
}
else
morpion.incorrectCase();
@ -101,19 +129,20 @@ class TicTacToe
checkCase(targetMorpion, targetCase)
{
return (this.map[targetMorpion][targetCase] == -1 && this.turn == true);
return (this.map[targetMorpion][targetCase] == -1 && this.turn == true && targetMorpion == this.currentMorpion);
}
incorrectCase()
{
console.log("bozo");
}
sendCase(targetMorpion, targetCase)
{
this.map[targetMorpion][targetCase] = (this.sign == "x") ? 0 : 1;
this.currentMorpion = targetCase;
this.printSign(targetMorpion, targetCase, this.sign);
console.log(this.game.send, targetMorpion, targetCase)
console.log(targetMorpion, targetCase)
this.game.send(JSON.stringify({"targetMorpion" : targetMorpion, "targetCase" : targetCase, "sign" : this.sign}));
console.log(this.turn);
this.turn = !this.turn;
@ -172,21 +201,25 @@ class TicTacToe
return -1;
}
setOutline()
setOutline(targetMorpion, clear)
{
if (this.turn)
let targetX = (this.gap + targetMorpion % 3 * this.rectsize * 3);
let targetY = (this.gap + Math.floor(targetMorpion / 3) * this.rectsize * 3);
if (this.game.finished)
return;
if (!clear)
{
this.context.beginPath();
this.context.strokeStyle = (this.sign == "x") ? "green" : "red";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3)
this.context.stroke();
this.context.closePath();
}
else
{
this.context.beginPath();
this.context.strokeStyle = "#1a1a1d";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.strokeStyle = `rgb(230 230 230)`;
this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3)
this.context.stroke();
this.context.closePath();
}
@ -244,34 +277,6 @@ class TicTacToe
}
this.context.closePath();
}
selectCase(x, y)
{
case_morpion = Math.floor(x / this.rectsize) + Math.floor((y / this.rectsize)) * 3
case_square = Math.floor(x / Math.floor(this.rectsize / 3)) % this.rectsize + Math.floor(y / this.rectsize) % this.rectsize
// ask server if case_morpion == playing_case && case_square == empty
}
setOutline()
{
if (this.turn)
{
this.context.beginPath();
this.context.strokeStyle = (this.sign == "x") ? "green" : "red";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.stroke();
this.context.closePath();
}
else
{
this.context.beginPath();
this.context.strokeStyle = "#1a1a1d";
this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25);
this.context.stroke();
this.context.closePath();
}
}
}
export { TicTacToe };

View File

@ -0,0 +1,81 @@
import {Client} from '../Client.js';
import {createNotification} from '../../utils/noticeUtils.js'
import { client, lastView } from '../../index.js';
import { Profile } from '../Profile.js';
import ProfilePageView from '../../views/ProfilePageView.js';
export default class Notice {
/**
* @param {Client} client
*/
constructor(client) {
/**
* @type {Client}
*/
this.client = client;
this.url = location.origin.replace('http', 'ws') + '/ws/notice';
}
start() {
this._socket = new WebSocket(this.url);
this._socket.onclose = _ => this._socket = undefined;
this._socket.onmessage = message => {
const data = JSON.parse(message.data);
if (data.type === 'friend_request') {
this.friend_request(data.author);
} else if (data.type === 'new_friend') {
this.new_friend(data.friend);
} else if (data.type === 'friend_removed') {
this.friend_removed(data.friend);
} else if (data.type === 'friend_request_canceled') {
this.friend_request_canceled(data.author);
}
};
}
stop() {
if (this._socket) {
this._socket.close();
this._socket = undefined;
}
}
friend_request(author) {
client.me.incomingFriendRequests.push(new Profile(author.username, author.id, author.avatar));
createNotification('Friend Request', `<strong>${author.username}</strong> sent you a friend request.`);
if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) {
lastView.profile.hasIncomingRequest = true;
lastView.loadFriendshipStatus();
}
}
new_friend(friend) {
client.me.friendList.push(new Profile(friend.username, friend.id, friend.avatar));
createNotification('New Friend', `<strong>${friend.username}</strong> accepted your friend request.`);
if (lastView instanceof ProfilePageView && lastView.profile.id === friend.id) {
lastView.profile.isFriend = true;
lastView.profile.hasIncomingRequest = false;
lastView.profile.hasOutgoingRequest = false;
lastView.loadFriendshipStatus();
}
}
friend_removed(exFriend) {
client.me.friendList = client.me.friendList.filter(friend => friend.id !== exFriend.id);
if (lastView instanceof ProfilePageView && lastView.profile.id === exFriend.id) {
lastView.profile.isFriend = false;
lastView.loadFriendshipStatus();
}
}
friend_request_canceled(author) {
client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(user => user.id !== author.id);
if (lastView instanceof ProfilePageView && lastView.profile.id === author.id) {
lastView.profile.hasIncomingRequest = false;
lastView.loadFriendshipStatus();
}
}
}

View File

@ -42,14 +42,33 @@ class Tourmanent extends AExchangeable
* @type {Number}
*/
this.finished;
/**
* @type {"finished" | "started" | "waiting"} must be "finished", or "started", or "waiting". Any other return all elements
*/
this.state;
/**
* @type {Boolean} the client is a participant of the tournament
*/
this.is_participating;
}
/**
* @param {Boolean} newParticipation
*/
async setParticipation(newParticipation)
{
if (this.isParticipating == newParticipation)
return;
this.isParticipating = newParticipation;
this._socket.send(JSON.stringify({"detail": "update_participating",
"is_participating": newParticipation})
);
}
/**
*
* @returns {Promise<?>}
@ -79,28 +98,66 @@ class Tourmanent extends AExchangeable
/**
* @param {Object} data
*/
async _receiveParticipantUpdate(data)
async _receiveAddParticipant(data)
{
this.par
const participant = new Profile(this.client, undefined, data.participant.user_id);
participant.import(data.participant)
this.participantList.push(participant);
await this._addParticipantHandler(this.participantList.length)
}
async onError(data)
/**
* @param {Object} data
*/
async _receiveDelParticipant(data)
{
const index = this.participantList.indexOf((profile) => profile.id === data.profile.user_id)
this.participantList.splice(index, 1);
await this._delParticipantHandler(this.participantList.length);
}
async _receiveError(data)
{
await this.errorHandler(data);
}
async _receiveGoTo(data)
{
await this._goToHandler(data)
}
/**
*
* @param {MessageEvent} event
*/
onReceive(event)
async onReceive(event)
{
const data = JSON.parse(event.data);
if (data.detail === "error")
this.onError(data);
else if (["del_participant", "add_participant"].includes(data.detail))
this._receiveParticipantUpdate(data);
switch (data.detail) {
case "error":
await this._receiveError(data)
break;
case "add_participant":
await this._receiveAddParticipant(data);
break;
case "del_participant":
await this._receiveDelParticipant(data);
break;
case "go_to":
await this._receiveGoTo(data);
break
default:
break;
}
}
/**
@ -110,9 +167,11 @@ class Tourmanent extends AExchangeable
* @param {CallableFunction} delParticipantHandler called when a participants leave the tournament
* @param {CallableFunction} disconnectHandler
* @param {CallableFunction} goToHandler called when the next game will start
* @returns {?}
* @param {CallableFunction} startHandler called when tournament start
* @param {CallableFunction} finishHandler called when tournament finish
* @returns {Promise}
*/
async join(participantsUpdateHandler, errorHandler, goToHandler, disconnectHandler)
async join(addParticipantHandler, delParticipantHandler, startHandler, finishHandler, errorHandler, goToHandler, disconnectHandler)
{
if (!await this.client.isAuthenticated())
return null;
@ -124,10 +183,13 @@ class Tourmanent extends AExchangeable
this.connected = true;
this.isParticipating = false;
this.participantsUpdateHandler = participantsUpdateHandler;
this.errorHandler = errorHandler;
this.disconnectHandler = disconnectHandler;
this.goToHandler = goToHandler;
this._startHandler = startHandler;
this._finishHandler = finishHandler;
this._addParticipantHandler = addParticipantHandler;
this._delParticipantHandler = delParticipantHandler;
this._errorHandler = errorHandler;
this._disconnectHandler = disconnectHandler;
this._goToHandler = goToHandler;
this._socket.onmessage = this.onReceive.bind(this);

View File

@ -167,4 +167,4 @@ document.addEventListener("DOMContentLoaded", async () => {
document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active');
});
export { client, lang, navigateTo, reloadView };
export { client, lang, lastView, navigateTo, reloadView };

Binary file not shown.

View File

@ -1,22 +1,15 @@
export function createNotification(text, timer = 3000) {
export function createNotification(title = 'New notification', content, delay = 3000) {
if (!createNotification.templateToast) {
createNotification.templateToast = new DOMParser().parseFromString(`
<div class='toast' role='alert' data-bs-delay='${timer}'>
<div class='toast-header'>
<strong class='me-auto'>Notification</strong>
<button type='button' class='btn-close' data-bs-dismiss='toast'></button>
</div>
<div class='toast-body'>
</div>
</div>
`, 'text/html')
.querySelector('body')
.firstChild;
}
const toastElement = createNotification.templateToast.cloneNode(true);
toastElement.getElementsByClassName('toast-body')[0].innerHTML = text;
const toastElement = document.createElement('div');
toastElement.classList.add('toast');
toastElement.role = 'alert';
toastElement.setAttribute('data-bs-delay', delay);
toastElement.innerHTML =
`<div class='toast-header'>
<strong class='me-auto'>${title}</strong>
<button type='button' class='btn-close' data-bs-dismiss='toast'></button>
</div>
<div class='toast-body'>${content}</div>`
toastElement.addEventListener('hidden.bs.toast', e => e.target.remove());
new bootstrap.Toast(toastElement).show();

View File

@ -19,7 +19,7 @@ export default class extends AbstractAuthenticatedView {
}
else
{
await client.matchmaking.start(this.onreceive.bind(this), this.ondisconnect.bind(this), this.gamemode_input.value, this.nb_players_input.value);
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");
}
@ -34,10 +34,10 @@ export default class extends AbstractAuthenticatedView {
{
if (data.detail === "game_found")
{
if (this.gamemode_input.value == "pong")
navigateTo(`/games/${data.gamemode}/${data.game_id}`);
if (this.game_type_input.value == "pong")
navigateTo(`/games/${data.game_type}/${data.game_id}`);
else
navigateTo(`/games/${data.gamemode}/${data.game_id}`);
navigateTo(`/games/${data.game_type}/${data.game_id}`);
return;
}
this.display_data(data);
@ -51,7 +51,7 @@ export default class extends AbstractAuthenticatedView {
addEnterEvent()
{
[this.nb_players_input, this.gamemode_input].forEach((input) => {
[this.nb_players_input, this.game_type_input].forEach((input) => {
input.addEventListener('keydown', async ev => {
@ -74,13 +74,13 @@ export default class extends AbstractAuthenticatedView {
});
}
addChangeGameModeEvent()
addChangegame_typeEvent()
{
let nb_players_div = document.getElementById("nb-players-div");
this.gamemode_input.addEventListener("change", () => {
this.game_type_input.addEventListener("change", () => {
if (this.gamemode_input.value === "tictactoe")
if (this.game_type_input.value === "tictactoe")
{
nb_players_div.style.display = 'none';
this.nb_players_input.value = 2;
@ -96,7 +96,7 @@ export default class extends AbstractAuthenticatedView {
addEvents()
{
this.addEnterEvent();
this.addChangeGameModeEvent();
this.addChangegame_typeEvent();
this.addChangeNbPlayersEvent();
}
@ -104,7 +104,7 @@ export default class extends AbstractAuthenticatedView {
{
this.button = document.getElementById("toggle-search");
this.nb_players_input = document.getElementById("nb-players-input");
this.gamemode_input = document.getElementById("gamemode-input");
this.game_type_input = document.getElementById("game-type-input");
this.button.onclick = this.toggle_search.bind(this);
@ -117,12 +117,12 @@ export default class extends AbstractAuthenticatedView {
<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='gamemode-div'>
<select class='form-control' id='gamemode-input'>
<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='gamemode-input'>${lang.get("gamemodeChoice")}</label>
<label for='game-type-input'>${lang.get("game_typeChoice")}</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")}'>

View File

@ -8,7 +8,7 @@ export default class extends AbstractView {
}
setTitle() {
document.title = `${this.username} - Profile`;
document.title = this.titleKey;
}
async postInit()
@ -16,99 +16,48 @@ export default class extends AbstractView {
if (!this.profile)
return 404;
this.userId = this.profile.id;
await this.blockButton();
await this.friendButton();
client.notice.rewrite_profile = async () => {
await this.profile.getFriend();
await this.profile.getBlock();
await this.friendButton();
};
}
async blockButton() {
// Block option
if (await client.isAuthenticated() === false)
if (this.profile.id === client.me.id)
return;
if (client.me.id != this.userId) {
let block = document.getElementById("block");
if (block == undefined) {
block = document.createElement("p");
// this.info.appendChild(block);
}
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton'),
blockButton = document.getElementById('blockButton'),
unblockButton = document.getElementById('unblockButton');
block.id = "block";
block.onclick = async () => {
if (!this.profile.isBlocked)
await client.profiles.block(this.userId);
else
await client.profiles.deblock(this.userId);
this.profile = await client.profiles.getProfile(this.username);
this.loadFriendshipStatus();
if (this.profile.isBlocked)
unblockButton.classList.remove('d-none');
else
blockButton.classList.remove('d-none');
this.blockButton();
};
if (this.profile.isBlocked)
block.textContent = lang.get('profileUnblock', 'Unblock');
else
block.textContent = lang.get('profileBlock', 'Block');
}
addFriendButton.onclick = _ => this.addFriend();
removeFriendButton.onclick = _ => this.removeFriend();
unblockButton.onclick = _ => this.unblockUser();
blockButton.onclick = _ => this.blockUser();
}
async friendButton() {
if (await client.isAuthenticated() === false)
return;
loadFriendshipStatus() {
const addFriendButton = document.getElementById('addFriendButton'),
removeFriendButton = document.getElementById('removeFriendButton');
if (client.me.id != this.userId) {
let yes = document.getElementById("yes") || document.createElement("p");
let no = document.getElementById("no") || document.createElement("p");
let friend = document.getElementById("friend") || document.createElement("p");
if (client.notice.data.asker.includes(this.userId)) {
if (friend)
friend.remove();
yes.id = "yes";
yes.textContent = lang.get('profileAcceptRequest', 'Accept Friend');
yes.onclick = async () => {
client.notice.accept_friend(this.userId);
};
no.id = "no";
no.textContent = lang.get('profileDenyRequest', 'Decline Friend');
no.onclick = async () => {
client.notice.refuse_friend(this.userId);
};
// this.info.appendChild(yes);
// this.info.appendChild(document.createTextNode(" "));
// this.info.appendChild(no);
}
else {
if (yes && no)
yes.remove(); no.remove();
friend.id = "friend";
friend.onclick = async () => {
if (this.profile.isFriend)
await client.notice.remove_friend(this.userId);
else
await client.notice.ask_friend(this.userId);
await client.profiles.getProfile(this.username);
this.friendButton();
};
if (this.profile.isFriend)
friend.textContent = lang.get('profileRemoveFriend', 'Remove Friend');
else {
friend.textContent = lang.get('profileAddFriend', 'Ask Friend');
}
// this.info.appendChild(friend);
}
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');
}
}
@ -118,19 +67,94 @@ export default class extends AbstractView {
if (!this.profile)
return '';
const logged = await client.isAuthenticated();
return `
<div class='mb-3' id='profileInfo'>
<h1>${this.username}</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 ${logged ? '' : 'd-none'}' id='addFriendButton'>Add Friend</button>
<button class='btn btn-sm btn-danger ${logged ? '' : 'd-none'}' id='removeFriendButton'>Remove Friend</button>
</div>
`;
<div>
<div class='mb-3' id='profileInfo'>
<h1>${this.username}</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>
</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');
client.me.outgoingFriendRequests.push(this.profile);
this.profile.hasOutgoingRequest = true;
} else if (response.status === 201) {
removeFriendButton.innerHTML = 'Remove Friend';
removeFriendButton.classList.replace('btn-secondary', 'btn-danger');
this.profile.friend = true;
this.profile.hasIncomingRequest = false;
client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(profile => profile.id !== this.profile.id);
client.me.friendList.push(this.profile);
}
}
async removeFriend() {
const addFriendButton = document.getElementById('addFriendButton');
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');
document.getElementById('removeFriendButton').classList.add('d-none');
}
if (response.status === 200) {
this.profile.hasOutgoingRequest = false;
client.me.outgoingFriendRequests = client.me.outgoingFriendRequests.filter(profile => profile.id !== this.profile.id);
} else if (response.status === 201) {
this.profile.isFriend = false;
client.me.friendList = client.me.friendList.filter(friend => friend.id !== this.profile.id);
}
}
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

@ -19,7 +19,6 @@ export class TicTacToeOnlineView extends AbstractView
this.Morpion = new TicTacToe(this.height, this.width, 60, 60, document.getElementById("Morpion"), this.game_id);
this.Morpion.DrawSuperMorpion();
await this.Morpion.init();
this.Morpion.setOutline();
}
async leavePage()

View File

@ -10,7 +10,6 @@ export default class extends AbstractAuthenticatedView
async postInit() {
await client.logout();
await client.notice.disconnect();
navigateTo(this.lastPageUrl);
}
}

View File

@ -16,10 +16,9 @@ export default class extends AbstractAuthenticatedView
if (name.length == 0)
name = lang.get("TournamentCreateTournamentName");
console.log(name);
let nb_players = document.getElementById("nb-players-input").value;
let nb_participant = document.getElementById("nb-participant-input").value;
let response = await client.tournaments.createTournament(nb_players, name);
let response = await client.tournaments.createTournament(nb_participant, name);
let response_data = await response.json();
let id = response_data.id;
@ -29,7 +28,7 @@ export default class extends AbstractAuthenticatedView
return;
}
clearIds("innerHTML", ["name", "nb_players"]);
clearIds("innerHTML", ["name", "nb_participants"]);
fill_errors(response_data, "innerHTML");
}
@ -50,9 +49,16 @@ export default class extends AbstractAuthenticatedView
<span class='text-danger' id='name'></span>
</div>
<div class='form-floating mb-2'>
<input type='number' class='form-control' min='2' value='4' id='nb-players-input' placeholder='${lang.get("TournamentCreateNbPlayer")}'>
<label for='nb-players-input' id='nb-players-label'>${lang.get("TournamentCreateNbPlayer")}</label>
<span class='text-danger' id='nb_players'></span>
<select class='form-control' id="nb-participant-input">
<option value="2">2</option>
<option value="4">4</option>
<option value="8">8</option>
<option value="16">16</option>
<option value="32">32</option>
<option value="64">64</option>
</select>
<label for='nb-participant-input' id='nb-participant-label'>${lang.get("TournamentCreateNbPlayer")}</label>
<span class='text-danger' id='nb_participants'></span>
</div>
<div class='d-flex'>
<button type='button' class='btn btn-primary mt-3 mb-2 mx-2' id='create-button'>${lang.get("TournamentCreateButton")}</button>

View File

@ -3,6 +3,10 @@ import { Tourmanent } from "../../api/tournament/Tournament.js";
import {client, navigateTo} from "../../index.js";
import AbstractAuthenticatedView from "../abstracts/AbstractAuthenticatedView.js";
const TEXT_CONVENTION = {
"error": "[ERROR]"
}
export default class extends AbstractAuthenticatedView
{
constructor(params)
@ -13,44 +17,49 @@ export default class extends AbstractAuthenticatedView
pressButton()
{
this.tournament.toggle_participation();
this.tournament.setParticipation(!this.tournament.isParticipating);
this.updateParticipating()
}
async receive(data)
updateParticipating()
{
if (data.detail === "update_participants")
document.getElementById("nb_participants").innerText = `${data.participants} / ${this.tournament.nb_participants}`;
if (data.detail === "go_to")
navigateTo(data.url);
if (data.detail === "is_participant")
this.updateParticipating(data.is_participant);
if (data.detail === "error")
document.getElementById("display").innerText = data.error_message;
}
async updateParticipating(state)
{
document.getElementById("button").value = state ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`;
document.getElementById("display").innerText = state ? "You are a particpant" : "You are not a participant";
}
/**
*
* @param {[Profile]} oldParticipantsList
* @param {[Profile]} currentParticipantsList
*/
async onParticipantsUpdate(oldParticipantsList, currentParticipantsList)
{
document.getElementById("button").value = this.tournament.isParticipating ? `Leave ${this.tournament.name}` : `Join ${this.tournament.name}`;
document.getElementById("display").innerText = this.tournament.isParticipating ? "You are a particpant" : "You are not a participant";
}
async onDisconnect(event)
{
}
async onError(data)
{
this.addChatMessage(`${TEXT_CONVENTION} data.error_message`);
}
async onFinish()
{
document.getElementById("state").innerText = "finished"
}
async onStart()
{
document.getElementById("state").innerText = "started"
}
async onGoTo(data)
{
await navigateTo(`/games/pong/${data.game_id}`)
}
async onAddParticipant(nb_participants)
{
document.getElementById("nb_participants").innerText = nb_participants;
}
async onDelParticipant(nb_participants)
{
document.getElementById("nb_participants").innerText = nb_participants;
}
async postInit()
@ -63,25 +72,32 @@ export default class extends AbstractAuthenticatedView
if (this.tournament === null)
return 404;
this.tournament.join(this.onParticipantsUpdate.bind(this), this.onError.bind(this), this.onDisconnect.bind(this));
this.tournament.join(this.onAddParticipant, this.onDelParticipant, this.onStart, this.onFinish, this.onError, this.onGoTo, this.onDisconnect);
let button = document.getElementById("button");
button.onclick = this.pressButton.bind(this);
document.getElementById("name").innerText = this.tournament.name;
document.getElementById("nb_participants").innerText = this.tournament.nb_participants;
document.getElementById("nb_participants").innerText = this.tournament.participantList.length;
document.getElementById("expected_nb_participants").innerText = this.tournament.nb_participants;
document.getElementById("round").innerText = this.tournament.round;
document.getElementById("state").innerText = this.tournament.state;
if (this.tournament.started === false)
button.disabled = false;
console.log(this.tournament);
this.chat = document.getElementById("chat");
console.log(this.tournament);
this.display_tree_tournament(this.tournament.nb_participants, this.tournament.participantList);
}
addChatMessage(message)
{
this.chat.innerText += message;
}
async display_tree_tournament(nb_participants, participants) {
const svg = document.getElementById('tree');
@ -137,15 +153,25 @@ export default class extends AbstractAuthenticatedView
<td id="nb_participants">Loading...</td>
</tr>
<tr>
<td>status</td>
<td>Expected number of participants</td>
<td id="expected_nb_participants">Loading...</td>
</tr>
<tr>
<td>state</td>
<td id="state">Loading...</td>
</tr>
</tbody>
</table>
<input type="button" id="button" value="Join tournament" disabled>
<<<<<<< HEAD
<br>
<svg id="tree" height="3000" width="3000">
</svg>
=======
<span id="display"></span>
<textarea id="chat" rows="4" cols="50" readonly>
</textarea>
>>>>>>> 55d29d5763f22f136d2b4bae6464463acb514a5c
`;
}
}