Compare commits

..

9 commits

Author SHA1 Message Date
Guillem George
206de2c05e Added README 2026-05-16 21:30:07 +02:00
Guillem George
77a6d7fa6c oh la la 2026-05-16 20:29:28 +02:00
Guillem George
34e5527244 non mais srx 2026-05-16 20:15:03 +02:00
Guillem George
b7d988048b manger 2026-05-16 19:02:11 +02:00
Guillem George
3914fbdba8 nbvgdfsgrcolulwqhr4wkjbv 2026-05-16 18:55:37 +02:00
Guillem George
e84731e8de pieds 2026-05-16 18:51:01 +02:00
Guillem George
1e4cd71e06 bk 2026-05-16 18:18:56 +02:00
Guillem George
3d1a427dcf amongous 2026-05-16 17:37:42 +02:00
Guillem George
c6f5ab3438 c trop bieeeen 2026-05-16 17:18:47 +02:00
8 changed files with 188 additions and 37 deletions

View file

@ -0,0 +1,10 @@
# E/Place
> **Note** This is a school project, therefore it probably won't interest you if you are looking for something useful.
## Overview
The goal of this rush was to implement a client for a r/place-like canvas. For those who don't know what I'm taliking about, r/place was an event on reddit where you had a world map where you could place a pixel of color every 5 minutes where you wanted.
This is basically the same thing: It first authenticates the user via OpenID, then connects to the server using Sockets.IO and a REST API simultaneously. Then it allows the user to place pixels, choose the color, view other people pixels, create or join rooms or customize profile, all with real time map update.
We had approximately 2 days to implement the client. The server socket, the API, and the OpenID server were already present, as well as the base HTML/CSS files and the madatory structure.

View file

@ -1,16 +1,26 @@
import { initSocket, socket, subscribe } from "../utils/streams"; import { initSocket, socket, subscribe } from "../utils/streams";
import { calculateLayout } from "./utils"; import { calculateLayout } from "./utils";
import { authenticate, refreshToken } from "../utils/auth"; import { authenticate, refreshToken } from "../utils/auth";
import { initCanvas, renderCanvasUpdate } from "../rooms/canvas/utils"; import {
import { getCanvas } from "../rooms/canvas/index"; getPlacementData,
initCanvas,
renderCanvasUpdate,
} from "../rooms/canvas/utils";
import { getCanvas, placePixel } from "../rooms/canvas/index";
import { fetchRoomConfig, getCurrentRoomConfig } from "../rooms/index"; import { fetchRoomConfig, getCurrentRoomConfig } from "../rooms/index";
// Initialize the layout // Initialize the layout
calculateLayout(); calculateLayout();
// Auth
await authenticate(); await authenticate();
////////////////////////////////////
// Sockets
//
let room = window.location.pathname.split("/")[1]; let room = window.location.pathname.split("/")[1];
const update_waitlist = [];
let initialized = false;
if (!room) { if (!room) {
room = "epi-place"; room = "epi-place";
@ -42,7 +52,16 @@ socket.on("message", async (response) => {
return; return;
} }
initCanvas(await getCurrentRoomConfig(), pixels); initCanvas(await getCurrentRoomConfig(room), pixels);
initialized = true;
while (update_waitlist.length > 0) {
const obj = update_waitlist.pop();
if (obj) {
renderCanvasUpdate(obj.color, obj.posX, obj.posY);
}
}
console.debug("Loaded canvas"); console.debug("Loaded canvas");
}); });
@ -63,15 +82,30 @@ socket.on("pixel-update", async (msg) => {
color, color,
} = msg.result.data.json; } = msg.result.data.json;
const cfg = await getCurrentRoomConfig(); if (!initialized) {
update_waitlist.push({ posX, posY, color });
if (!cfg || !cfg.settings || !cfg.settings.roomColors) {
console.error(
"Internal error: Cannot access config after retrieving it",
);
console.debug(cfg);
return; return;
} }
renderCanvasUpdate(color, posX, posY); renderCanvasUpdate(color, posX, posY);
}); });
////////////////////////////////////
// Buttons
//
async function placePixelButton() {
const { color, posX, posY } = getPlacementData();
await placePixel(room, posX, posY, color);
}
////////////////////////////////////
// HTML
//
const placeButtonElt = document.getElementById("color-place-button");
placeButtonElt.addEventListener("click", () => {
placePixelButton();
});
export { room };

View file

@ -15,12 +15,16 @@ async function fetchCanvas(room) {
}); });
if (!response || !response.ok) { if (!response || !response.ok) {
// console.error(
// "Could not retrieve room canvas: " + response && response.statusText
// ? response.statusText
// : "no more informations",
// );
console.error( console.error(
"Could not retrieve room canvas: " + response "Could not retrieve room canvas: nique ta mère la moulinette",
? response.statusText
: "null",
); );
console.debug(await response.text());
// console.debug(await response.text());
return null; return null;
} }
@ -80,9 +84,81 @@ async function getCanvas(room) {
function subscribeToRoom() {} function subscribeToRoom() {}
// get the pixel info of a room // get the pixel info of a room
function getPixelInfo() {} async function getPixelInfo(room, posX, posY) {
if (!room) {
console.error("Cannot place pixel on an undefined room");
return null;
}
const params = new URLSearchParams({
posX,
posY,
});
const response = await authedAPIRequest(
"/rooms/" + room + "/canvas/pixels" + "?" + params,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
if (!response || !response.ok) {
console.error(
"Could not retrieve pixel infos: " + response && response.statusText
? response.statusText
: "no more informations",
);
// console.debug(await response.text());
return null;
}
const res = await response.json();
return res;
}
// place a pixel in a room // place a pixel in a room
function placePixel() {} async function placePixel(room, posX, posY, color) {
if (!room) {
console.error("Cannot place pixel on an undefined room");
return null;
}
const response = await authedAPIRequest(
"/rooms/" + room + "/canvas/pixels",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
posX,
posY,
color,
}),
},
);
if (!response || !response.ok) {
console.error(
"Could not place pixel on map: " + response && response.statusText
? response.statusText
: "no more informations",
);
// console.debug(await response.text());
return null;
}
const res = await response.json();
console.debug(
`Placed pixel on map at ${posX}:${posY} (${color}) in room ${room}:`,
);
// console.debug(res);
return res;
}
export { getCanvas, subscribeToRoom, getPixelInfo, placePixel }; export { getCanvas, subscribeToRoom, getPixelInfo, placePixel };

View file

@ -6,6 +6,8 @@
// - toggleTooltip (toggle the tooltip and display the pixel's information) // - toggleTooltip (toggle the tooltip and display the pixel's information)
import $ from "jquery"; import $ from "jquery";
import { getPixelInfo } from "./index";
import { getStudent } from "../../students";
const canvasContainer = $("#canvas-container")?.[0]; const canvasContainer = $("#canvas-container")?.[0];
const canvas = $("#canvas")?.[0]; const canvas = $("#canvas")?.[0];
@ -39,7 +41,7 @@ let isDrag = false;
* Get the placement data, i.e. the color the user has selected and the * Get the placement data, i.e. the color the user has selected and the
* coordinates of the pixel he is focusing on. * coordinates of the pixel he is focusing on.
* *
* @returns {{color: number, posX: number, posX: number}} the data * @returns {{color: number, posX: number, posY: number}} the data
*/ */
export const getPlacementData = () => ({ export const getPlacementData = () => ({
color: selectedColorIdx, color: selectedColorIdx,
@ -58,8 +60,22 @@ export const toggleTooltip = async (state = false) => {
tooltip.style.display = state ? "flex" : "none"; tooltip.style.display = state ? "flex" : "none";
if (state) { if (state) {
// FIXME: You should implement or call a function to get the pixel's information // Get pixel
// and display it. Make use of target.x and target.y to get the pixel's position. const room = window.location.pathname.split("/")[1] || "epi-place";
const response = await getPixelInfo(room, target.x, target.y);
if (!response) {
return;
}
// Display
const date = new Date(response.timestamp);
document.getElementById("tooltip-date").innerText =
date.toLocaleDateString("fr-fr");
document.getElementById("tooltip-time").innerText =
date.toLocaleTimeString("fr-fr");
getStudent(response.placedByUid);
} }
}; };

View file

@ -17,9 +17,9 @@ let roomConfig = null;
function setCurrentRoomConfig(cfg) { function setCurrentRoomConfig(cfg) {
roomConfig = cfg; roomConfig = cfg;
} }
async function getCurrentRoomConfig() { async function getCurrentRoomConfig(room) {
if (!roomConfig) { if (!roomConfig) {
await fetchRoomConfig(); await fetchRoomConfig(room);
} }
return roomConfig; return roomConfig;
@ -48,7 +48,7 @@ async function listRooms() {
if (!response || !response.ok) { if (!response || !response.ok) {
console.error( console.error(
"Could not retrieve rooms list: " + response "Could not retrieve rooms list: " + response && response.statusText
? response.statusText ? response.statusText
: "null", : "null",
); );
@ -87,7 +87,7 @@ async function fetchRoomConfig(room) {
if (!response || !response.ok) { if (!response || !response.ok) {
console.error( console.error(
"Could not retrieve room config" + response "Could not retrieve room config" + response && response.statusText
? response.statusText ? response.statusText
: "null", : "null",
); );
@ -103,9 +103,10 @@ async function fetchRoomConfig(room) {
setCurrentRoomConfig(res); setCurrentRoomConfig(res);
// Update HTML // Update HTML
const roomNameElt = document.getElementById("room-name"); document.getElementById("room-name").innerText = res.metadata.name;
document.getElementById("room-description").innerText =
roomNameElt.innerText = res.metadata.name; res.metadata.description;
document.getElementById("room-description").style.display = "block";
} }
export { export {

View file

@ -1,4 +1,5 @@
import { authedAPIRequest } from "../utils/auth"; import { authedAPIRequest } from "../utils/auth";
import { displayStudentProfile } from "./utils";
//get a student from the API by its uid or login //get a student from the API by its uid or login
async function getStudent(login) { async function getStudent(login) {
@ -11,8 +12,12 @@ async function getStudent(login) {
method: "GET", method: "GET",
}); });
if (!response.ok) { if (!response || !response.ok) {
console.error("Could not retrieve student: " + response.statusText); console.error(
"Could not retrieve student: " + response && response.statusText
? response.statusText
: " no status text",
);
console.debug(await response.text()); console.debug(await response.text());
return; return;
} }
@ -23,8 +28,7 @@ async function getStudent(login) {
console.debug(res); console.debug(res);
// Update HTML // Update HTML
// const roomNameElt = document.getElementById("room-name"); displayStudentProfile(res.avatarURL, res.login, res.guild, res.quote);
// roomNameElt.innerText = res.metadata.name
} }
//// get the user's uid from the token in local storage //// get the user's uid from the token in local storage

View file

@ -1,5 +1,11 @@
// FIXME: This file should handle the students DOM manipulation // display the student's profile in the DOM
// Link buttons to their respective functions function displayStudentProfile(picture, login, guild, quote) {
// Functions may include: document.getElementById("tooltip-info-avatar").src = picture;
// - displayStudentProfile (display the student's profile in the DOM) document.getElementById("tooltip-info-login").innerText = login;
// - showModal (add a form modal to the DOM) document.getElementById("tooltip-info-guild").innerText = guild;
document.getElementById("tooltip-info-quote").innerText = quote;
}
// add a form modal to the DOM
function showModal() {}
export { displayStudentProfile, showModal };

View file

@ -20,8 +20,12 @@ async function sendRequest(endpoint, body) {
try { try {
response = await fetch(endpoint, request); response = await fetch(endpoint, request);
if (!response.ok) { if (!response || !response.ok) {
if (response && response.statusText) {
throw new Error(response.statusText); throw new Error(response.statusText);
} else {
throw new Error("No status text");
}
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);