Compare commits

..

No commits in common. "master" and "eplace-step1-2-genre" have entirely different histories.

8 changed files with 59 additions and 452 deletions

View file

@ -1,10 +0,0 @@
# 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,111 +1,24 @@
import { initSocket, socket, subscribe } from "../utils/streams"; import { initSocket, subscribe } from "../utils/streams";
import { calculateLayout } from "./utils"; import { calculateLayout } from "./utils";
import { authenticate, refreshToken } from "../utils/auth"; import { authenticate } from "../utils/auth";
import { import { fetchRoomConfig } from "../rooms";
getPlacementData,
initCanvas,
renderCanvasUpdate,
} from "../rooms/canvas/utils";
import { getCanvas, placePixel } from "../rooms/canvas/index";
import { fetchRoomConfig, getCurrentRoomConfig } from "../rooms/index";
// Initialize the layout // Initialize the layout
calculateLayout(); calculateLayout();
// Auth async () => {
await authenticate(); // ? Not sure
if (!(await authenticate())) {
////////////////////////////////////
// Sockets
//
let room = window.location.pathname.split("/")[1];
const update_waitlist = [];
let initialized = false;
if (!room) {
room = "epi-place";
}
await initSocket();
await subscribe(room, "pixel-update");
// Main event
socket.on("message", async (response) => {
console.debug("Received server event on message: ");
console.debug(response);
if (response.error) {
console.error("Got server error: " + response.error.json.message);
if (response.error.json.data.httpStatus === 401) {
await refreshToken();
}
return; return;
} }
if (response.result.type === "started") { let room = window.location.pathname.split("/")[1];
await fetchRoomConfig(room);
if (room === "") {
room = "epi-place";
} }
const pixels = await getCanvas(room); initSocket();
subscribe(room, "pixel-update");
if (!pixels) { fetchRoomConfig(room);
return; };
}
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");
});
socket.on("pixel-update", async (msg) => {
// console.debug("Received server event on pixel-update: ");
// console.debug(msg);
if (msg.error) {
console.error("Got server error: " + msg.error.json.message);
return;
}
// console.debug("Here is msg.data")
// console.debug(msg.result.data.json)
const {
// roomSlug,
posX,
posY,
color,
} = msg.result.data.json;
if (!initialized) {
update_waitlist.push({ posX, posY, color });
return;
}
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

@ -1,164 +1,7 @@
import { authedAPIRequest } from "../../utils/auth"; // FIXME: This file should handle the room canvas API
// Link buttons to their respective functions
/** // Functions may include:
* @param {string} room // - getCanvas (get the canvas of a room and deserialize it)
* @return {Promise<Object?>} The response Json object // - subscribeToRoom (subscribe to the stream of a room)
*/ // - getPixelInfo (get the pixel info of a room)
async function fetchCanvas(room) { // - placePixel (place a pixel in a room)
if (!room) {
console.error("Cannot fetch canvas of an undefined room");
return null;
}
const response = await authedAPIRequest("/rooms/" + room + "/canvas", {
method: "GET",
});
if (!response || !response.ok) {
// console.error(
// "Could not retrieve room canvas: " + response && response.statusText
// ? response.statusText
// : "no more informations",
// );
console.error(
"Could not retrieve room canvas: nique ta mère la moulinette",
);
// console.debug(await response.text());
return null;
}
const res = await response.json();
console.debug(`Retrieved canvas for room ${room}:`);
// console.debug(res);
return res;
}
// Splits a string into fixed length substrings
String.prototype.chunk = function (size) {
return [].concat.apply(
[],
this.split("").map(function (x, i) {
return i % size ? [] : this.slice(i, i + size);
}, this),
);
};
// get the canvas of a room and deserialize it
async function getCanvas(room) {
const raw_pixels = await fetchCanvas(room);
if (!raw_pixels || !raw_pixels.pixels) {
console.error("Aborting canvas deserialization");
return null;
}
// Convert to a an array of strings representing binary (beurk)
let raw_binary_str = "";
for (let i = 0; i < raw_pixels.pixels.length; i++) {
raw_binary_str = raw_binary_str.concat(
raw_pixels.pixels.charCodeAt(i).toString(2).padStart(8, "0"),
);
}
// console.debug(raw_binary_str)
// Chunk it to an array of fixed-length string (5)
let pixel_array = raw_binary_str.chunk(5);
if (pixel_array[pixel_array.length - 1].length < 5) {
pixel_array.pop();
}
// console.debug(pixel_array)
// Convert into numbers
pixel_array = pixel_array.map((pixel) => parseInt(pixel, 2));
// console.debug(pixel_array)
return pixel_array;
}
// subscribe to the stream of a room
function subscribeToRoom() {}
// get the pixel info of a room
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
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 };

View file

@ -6,8 +6,6 @@
// - 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];
@ -41,7 +39,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, posY: number}} the data * @returns {{color: number, posX: number, posX: number}} the data
*/ */
export const getPlacementData = () => ({ export const getPlacementData = () => ({
color: selectedColorIdx, color: selectedColorIdx,
@ -60,22 +58,8 @@ export const toggleTooltip = async (state = false) => {
tooltip.style.display = state ? "flex" : "none"; tooltip.style.display = state ? "flex" : "none";
if (state) { if (state) {
// Get pixel // FIXME: You should implement or call a function to get the pixel's information
const room = window.location.pathname.split("/")[1] || "epi-place"; // and display it. Make use of target.x and target.y to get the pixel's position.
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

@ -1,6 +1,4 @@
import { authedAPIRequest } from "../utils/auth"; import { authedAPIRequest } from "../utils/auth";
import { subscribe } from "../utils/streams";
import { resetValues } from "./canvas/utils";
// FIXME: This file should handle the rooms API // FIXME: This file should handle the rooms API
// Functions may include: // Functions may include:
// - fetchRoomConfig (get the configuration of a room) // - fetchRoomConfig (get the configuration of a room)
@ -11,111 +9,38 @@ import { resetValues } from "./canvas/utils";
// - createRoom (create a room) // - createRoom (create a room)
// - updateRoom (update a room's configuration) // - updateRoom (update a room's configuration)
// - deleteRoom (delete a room) // - deleteRoom (delete a room)
//
let roomConfig = null; // joinRoom notes
// io.on('connection', (socket) => {
// // join the room named 'some room'
// socket.join('some room');
function setCurrentRoomConfig(cfg) { // // broadcast to all connected clients in the room
roomConfig = cfg; // io.to('some room').emit('hello', 'world');
}
async function getCurrentRoomConfig(room) {
if (!roomConfig) {
await fetchRoomConfig(room);
}
return roomConfig; // // broadcast to all connected clients except those in the room
} // io.except('some room').emit('hello', 'world');
async function joinRoom(room) {
// socket.on('connection', (sockett) => {
// sockett.join(room);
// broadcast to all connected clients in the room
// socket.to('some room').emit('hello', 'world');
// broadcast to all connected clients except those in the room
// socket.except('some room').emit('hello', 'world');
// });
resetValues(); // // leave the room
subscribe(room); // socket.leave('some room');
} // });
//
// async function leaveRoom(room) {
// socket.on('connection', (socket) => {
// socket.leave(room);
// })
// }
async function listRooms() {
const response = await authedAPIRequest("/rooms/", { method: "GET" });
if (!response || !response.ok) {
console.error(
"Could not retrieve rooms list: " + response && response.statusText
? response.statusText
: "null",
);
console.debug(await response.text());
return;
}
const res = await response.json();
console.debug("Retrieved rooms list");
console.debug(res);
// Update HTML
// const roomNameElt = document.getElementById("room-name");
// roomNameElt.innerText = res.metadata.name;
}
// async function createRoom() {
// }
// async function updateRoom() {
// }
// async function deleteRoom() {
// }
async function fetchRoomConfig(room) { async function fetchRoomConfig(room) {
if (!room) { const response = await authedAPIRequest("/rooms/" + room + "/config");
console.error("Cannot fetch an undefined room");
return;
}
const response = await authedAPIRequest("/rooms/" + room + "/config", { if (!response.ok) {
method: "GET", console.error("Could not retrieve room config: " + response.statusText);
});
if (!response || !response.ok) {
console.error(
"Could not retrieve room config" + response && response.statusText
? response.statusText
: "null",
);
console.debug(await response.text()); console.debug(await response.text());
return; return;
} }
const res = await response.json(); const obj = await response.json();
console.debug(`Retrieved config for room ${room}:`); console.debug(`Retrieved config for room ${room}: ${obj}`);
console.debug(res);
setCurrentRoomConfig(res);
// Update HTML // Update HTML
document.getElementById("room-name").innerText = res.metadata.name;
document.getElementById("room-description").innerText =
res.metadata.description;
document.getElementById("room-description").style.display = "block";
} }
export { export { fetchRoomConfig };
fetchRoomConfig,
setCurrentRoomConfig,
getCurrentRoomConfig,
joinRoom,
listRooms,
// createRoom,
// updateRoom,
// deleteRoom
};

View file

@ -1,39 +1,5 @@
import { authedAPIRequest } from "../utils/auth"; // FIXME: This file should handle the students API
import { displayStudentProfile } from "./utils"; // Functions may include:
// - getStudent (get a student from the API by its uid or login)
//get a student from the API by its uid or login // - getUserUidFromToken (get the user's uid from the token in local storage)
async function getStudent(login) { // - updateStudent (update the student's profile through the API)
if (!login) {
console.error("Cannot fetch an undefined student");
return;
}
const response = await authedAPIRequest("/students/" + login, {
method: "GET",
});
if (!response || !response.ok) {
console.error(
"Could not retrieve student: " + response && response.statusText
? response.statusText
: " no status text",
);
console.debug(await response.text());
return;
}
const res = await response.json();
console.debug(`Retrieved student ${login}:`);
console.debug(res);
// Update HTML
displayStudentProfile(res.avatarURL, res.login, res.guild, res.quote);
}
//// get the user's uid from the token in local storage
async function getUserUidFromToken() {}
// update the student's profile through the API
async function updateStudent() {}
export { getStudent, getUserUidFromToken, updateStudent };

View file

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

View file

@ -20,12 +20,8 @@ async function sendRequest(endpoint, body) {
try { try {
response = await fetch(endpoint, request); response = await fetch(endpoint, request);
if (!response || !response.ok) { if (!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);
@ -143,19 +139,15 @@ async function authedAPIRequest(endpoint, options) {
// return null; // return null;
// } // }
// Deep copy req if (!options.headers) {
const request = JSON.parse(JSON.stringify(options)); options.headers = {};
if (!request.headers) {
request.headers = {};
} }
// Pcq ce PUTAIN de js est pas foutu de faire ce qu'on lui demande options.headers.Authorization = "Bearer " + localStorage.getItem("token");
request.headers.Authorization = "Bearer " + localStorage.getItem("token");
const full_endpoint = import.meta.env.VITE_URL + "/api" + endpoint; const full_endpoint = import.meta.env.VITE_URL + "/api" + endpoint;
const response = await fetch(full_endpoint, request); const response = await fetch(full_endpoint, options);
if (response.status === 401) { if (response.status === 401) {
const response_err = await response.text(); const response_err = await response.text();