update: friends list + button

This commit is contained in:
Maieul BOYER 2026-01-16 16:47:41 +01:00 committed by Nigel
parent 590604b385
commit c5eea6e29e
8 changed files with 156 additions and 76 deletions

View file

@ -37,6 +37,7 @@
<a href="/ttt" class="hover:bg-gray-700 rounded-md px-3 py-2">⭕ Tic-Tac-Toe</a>
<a href="/pong" class="hover:bg-gray-700 rounded-md px-3 py-2">▮•▮ Ping Pong</a>
<a href="/tour" class="hover:bg-gray-700 rounded-md px-3 py-2">🏆 Tournaments</a>
<a href="/friends" class="hover:bg-gray-700 rounded-md px-3 py-2">😎 Friends</a>
<a href="/logout" class="hover:bg-gray-700 rounded-md px-3 py-2">🚪 Logout</a>
</nav>
</aside>

View file

@ -1,7 +1,7 @@
import { showError } from "@app/toast";
import client from "@app/api";
import cookie from "js-cookie";
import { ensureWindowState, isNullish } from "@app/utils";
import { ensureWindowState, isNullish, updateFriendsList } from "@app/utils";
import { handleRoute, navigateTo } from "./routing";
cookie.remove("pkce");
@ -105,10 +105,11 @@ if (!window.__state._headerProfile) {
window.__state._reloadOnAuthChange ??= false;
if (!window.__state._reloadOnAuthChange) {
document.addEventListener("ft:userChange", () => {
document.addEventListener("ft:userChange", async () => {
// if the last forced auth change is less than 1000 sec old -> we do nothing
if (Date.now() - (window.__state.lastAuthChange ?? Date.now()) < 1000)
return;
await updateFriendsList();
handleRoute();
});
window.__state._reloadOnAuthChange = true;

View file

@ -39,6 +39,7 @@ let keysPressed: Record<string, boolean> = {};
declare module "ft_state" {
interface State {
chatSock?: Socket;
friendList: { id: string; name: string }[];
}
}
@ -82,18 +83,20 @@ const sendButton = document.getElementById("b-send") as HTMLButtonElement;
const sendtextbox = document.getElementById(
"t-chat-window",
) as HTMLButtonElement;
const systemWindow = document.getElementById("chat-system-box") as HTMLDivElement;
const systemWindow = document.getElementById(
"chat-system-box",
) as HTMLDivElement;
function chatKeyToggle() {
let anti_flicker_control = false;
const chat_hide_key = 'escape';
const chat_display_key = 'f2';
const home_display_key = 'f8';
const chat_hide_key = "escape";
const chat_display_key = "f2";
const home_display_key = "f8";
document.addEventListener("keydown", (event) => {
if (event.repeat && keysPressed[chat_hide_key] === true) {
anti_flicker_control = true;
return ;
};
return;
}
keysPressed[event.key.toLowerCase()] = true;
});
document.addEventListener("keyup", (event) => {
@ -102,34 +105,33 @@ function chatKeyToggle() {
anti_flicker_control = false;
}
});
setInterval( () => {
if(keysPressed[chat_hide_key] === true) {
overlay.classList.remove("opacity-60");
chatBox.classList.add("hidden");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
profilList?.classList.add("hidden");
windowStateHidden();
setInterval(() => {
if (keysPressed[chat_hide_key] === true) {
overlay.classList.remove("opacity-60");
chatBox.classList.add("hidden");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = "";
profilList?.classList.add("hidden");
windowStateHidden();
}
if (keysPressed[chat_display_key] === true) {
anti_flicker_control = false;
chatBox.classList.remove("hidden");
overlay.classList.add("opacity-60");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
let socket = window.__state.chatSock;
if (!socket) return;
connected(socket);
sendtextbox.focus();
windowStateVisable();
anti_flicker_control = false;
chatBox.classList.remove("hidden");
overlay.classList.add("opacity-60");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = "";
let socket = window.__state.chatSock;
if (!socket) return;
connected(socket);
sendtextbox.focus();
windowStateVisable();
}
if (keysPressed[home_display_key] === true) {
navigateTo('/app/');
if (keysPressed[home_display_key] === true) {
navigateTo("/app/");
quitChat();
}
}, 1000/10);
};
}, 1000 / 10);
}
function initChatSocket() {
let socket = getSocket();
@ -152,9 +154,10 @@ function initChatSocket() {
!profilList ||
!sendButton ||
!sendtextbox ||
!systemWindow
) return showError("fatal error");
!systemWindow
)
return showError("fatal error");
// Listen for the 'connect' event
socket.on("connect", async () => {
await waitSocketConnected(socket);
@ -202,10 +205,10 @@ function initChatSocket() {
if (socket) {
connected(socket);
}
if (chatWindow && data.message.destination === "") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🔵';
chatMessageIn!.textContent = "🔵";
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
@ -214,7 +217,7 @@ function initChatSocket() {
if (chatWindow && data.message.destination === "privateMsg") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🔴';
chatMessageIn!.textContent = "🔴";
const messageElement = document.createElement("div-private");
messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
@ -223,7 +226,7 @@ function initChatSocket() {
if (chatWindow && data.message.destination === "inviteMsg") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🟢';
chatMessageIn!.textContent = "🟢";
const messageElement = document.createElement("div-private");
const chatWindow = document.getElementById(
"t-chatbox",
@ -236,14 +239,13 @@ function initChatSocket() {
if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.appendChild(messageElement);
systemWindow.lastElementChild?.scrollIntoView({ block: "end" });
}
});
@ -278,9 +280,9 @@ function initChatSocket() {
if (blockUserBtn) {
let message = "";
if (data.userState === "block") {
(message = "un-block");
message = "un-block";
} else {
(message = "block");
message = "block";
}
blockUserBtn.textContent = message;
}
@ -294,17 +296,15 @@ function initChatSocket() {
const htmlBaliseRegex = /<a\b[^>]*>[\s\S]*?<\/a>/;
const htmlBaliseMatch = message.match(htmlBaliseRegex);
if (htmlBaliseMatch)
addInviteMessage(message);
else
addMessage(message);
if (htmlBaliseMatch) addInviteMessage(message);
else addMessage(message);
});
//receives broadcast of the next GAME
socket.on("nextGame", (message: string) => {
openMessagePopup(message);
});
//receives broadcast of the next GAME
socket.on("tourStatus", (message: string) => {
openMessagePopup(message);
@ -391,20 +391,20 @@ sendButton?.addEventListener("click", () => {
}
break;
case "@pong":
case "@pong":
if (msgCommand[1] === "") {
navigateTo("/app/pong/games");
quitChat();
}
}
break;
case "@ttt":
case "@ttt":
if (msgCommand[1] === "") {
navigateTo("/app/ttt/games");
quitChat();
}
}
break;
case "@guest":
if (!userId) {
return;
@ -505,7 +505,6 @@ clearText?.addEventListener("click", () => {
bquit?.addEventListener("click", () => {
quitChat();
});
myGames?.addEventListener("click", () => {
@ -527,7 +526,7 @@ sendtextbox.addEventListener("keydown", (event) => {
}
});
chatButton!.addEventListener("click",() => {
chatButton!.addEventListener("click", () => {
if (chatBox.classList.contains("hidden")) {
chatBox.classList.toggle("hidden");
overlay.classList.add("opacity-60");
@ -536,14 +535,14 @@ chatButton!.addEventListener("click",() => {
if (!socket) return;
connected(socket);
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
sendtextbox.focus();
chatMessageIn!.textContent = "";
sendtextbox.focus();
} else {
chatBox.classList.toggle("hidden");
overlay.classList.remove("opacity-60");
windowStateHidden();
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
chatMessageIn!.textContent = "";
}
});

View file

@ -1,5 +1,8 @@
import client from "@app/api";
import type { ClientProfil } from "../types_front";
import { Socket } from "socket.io-client";
import { showError, showSuccess } from "@app/toast";
import { getFriendList, updateFriendsList } from "@app/utils";
/**
* function listens for a click on the TTT game History button
@ -8,20 +11,25 @@ import { Socket } from "socket.io-client";
**/
export function actionBtnFriend(profile: ClientProfil, senderSocket: Socket) {
setTimeout(() => {
const friend = document.querySelector("#btn-friend");
friend?.addEventListener("click", () => {
if (friend.textContent = "friend") {
friend.textContent = "not-friend"
console.log('friend');
}
else {
friend.textContent = "not-friend"
console.log('Not a friend');
}
});
}, 0)
};
setTimeout(() => {
const friend = document.querySelector("#btn-friend");
friend?.addEventListener("click", async () => {
let friendList = getFriendList();
if (!friendList.some(v => v.id === profile.userID!)) {
let req = await client.addFriend({ user: profile.userID! });
if (req.kind === 'success')
showSuccess('Successfully added a new Friend')
else
showError('Failed to add a new Friend');
}
else {
let req = await client.removeFriend({ user: profile.userID! });
if (req.kind === 'success')
showSuccess('Successfully removed a Friend')
else
showError('Failed to remove a Friend');
}
await updateFriendsList();
});
}, 0)
};

View file

@ -0,0 +1,9 @@
<div class="fixed inset-0 flex items-center justify-center bg-[#43536b]">
<div
class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-gray-200 w-212.5 p-6 rounded-xl shadow-2xl text-center z-50">
<h2 class="text-2xl font-bold text-center mb-4 text-gray-700">
FriendList
</h2>
<div id="friendList" class="max-w-3xl mx-auto space-y-2 max-h-[50dvh] overflow-scroll"></div>
</div>
</div>

View file

@ -0,0 +1,41 @@
import { addRoute, navigateTo, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
import page from './friendList.html?raw';
import { getFriendList, isNullish, updateFriendsList } from "@app/utils";
import client from "@app/api";
import { updateUser } from "@app/auth";
import { showError } from "@app/toast";
async function friends(_url: string, args: RouteHandlerParams): Promise<RouteHandlerReturn> {
setTitle("Tic Tac Toe Games");
let user = await updateUser();
if (isNullish(user)) {
return { html: '<span> You aren\'t logged in </span>', postInsert: () => { showError("You must be logged in !"); navigateTo("/") } };
}
await updateFriendsList();
let friendList = getFriendList();
friendList.sort();
let friendsElem = friendList.map(g => {
let e = document.createElement('div');
e.className = 'grid grid-cols-[1fr_auto_1fr] items-center bg-zinc-800 rounded-lg px-4 py-3';
e.innerHTML = `
<div class="text-center font-semibold text-white">${g.name}</div>
<a href="/ttt/games/${g.id}" class="text-center">TTT Games</a>
<a href="/pong/games/${g.id}" class="text-center">Pong Games</a>
`;
return e;
}).filter(v => !isNullish(v));
return {
html: page, postInsert: async (app) => {
if (!app) return;
const friendsBox = app.querySelector<HTMLDivElement>("#friendList");
if (!friendsBox) return;
friendsElem.forEach(c => friendsBox.appendChild(c));
}
};
}
addRoute('/friends', friends);

View file

@ -1,6 +1,5 @@
import { setTitle, handleRoute } from '@app/routing';
import './root/root.ts'
import '../chat/chat.ts'
import './pong/pong.ts'
import './login/login.ts'
import './signin/signin.ts'
@ -10,6 +9,7 @@ import './logout/logout.ts'
import './pongHistory/pongHistory.ts'
import './tttHistory/tttHistory.ts'
import './tourHistory/tourHistory.ts'
import './friendList/friendList.ts'
// ---- Initial load ----
setTitle("");

View file

@ -1,9 +1,11 @@
import client from "./api";
export function escapeHTML(str: string): string {
const p = document.createElement("p");
p.appendChild(document.createTextNode(str));
return p.innerHTML;
}
export function isNullish<T>(v: T | undefined | null): v is (null | undefined) {
export function isNullish<T>(v: T | undefined | null): v is null | undefined {
return v === null || v === undefined;
}
@ -11,3 +13,22 @@ export function isNullish<T>(v: T | undefined | null): v is (null | undefined) {
export function ensureWindowState() {
window.__state = window.__state ?? {};
}
export async function updateFriendsList() {
window.__state = window.__state ?? {};
window.__state.friendList ??= [];
try {
let req = await client.listFriend();
if (req.kind === "success") {
window.__state.friendList = req.payload.friends;
}
} catch (e: unknown) { }
}
export function getFriendList() {
ensureWindowState();
window.__state.friendList ??= [];
return window.__state.friendList;
}