Compare commits
No commits in common. "master" and "eplace-step1-2-trounoir" have entirely different histories.
master
...
eplace-ste
8 changed files with 40 additions and 287 deletions
10
README.md
10
README.md
|
|
@ -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.
|
|
||||||
|
|
||||||
|
|
@ -1,26 +1,16 @@
|
||||||
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 {
|
import { initCanvas } from "../rooms/canvas/utils";
|
||||||
getPlacementData,
|
import { getCanvas } from "../rooms/canvas/index";
|
||||||
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";
|
||||||
|
|
@ -30,7 +20,7 @@ await initSocket();
|
||||||
await subscribe(room, "pixel-update");
|
await subscribe(room, "pixel-update");
|
||||||
// Main event
|
// Main event
|
||||||
socket.on("message", async (response) => {
|
socket.on("message", async (response) => {
|
||||||
console.debug("Received server event on message: ");
|
console.debug("Received server evemt: ");
|
||||||
console.debug(response);
|
console.debug(response);
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
console.error("Got server error: " + response.error.json.message);
|
console.error("Got server error: " + response.error.json.message);
|
||||||
|
|
@ -46,66 +36,11 @@ socket.on("message", async (response) => {
|
||||||
await fetchRoomConfig(room);
|
await fetchRoomConfig(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pixels = await getCanvas(room);
|
const canvasResp = getCanvas(room);
|
||||||
|
|
||||||
if (!pixels) {
|
if (!canvasResp || canvasResp.pixels) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initCanvas(await getCurrentRoomConfig(room), pixels);
|
initCanvas(getCurrentRoomConfig(), canvasResp.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 };
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import { authedAPIRequest } from "../../utils/auth";
|
import { authedAPIRequest } from "../../utils/auth";
|
||||||
|
|
||||||
/**
|
// get the canvas of a room and deserialize it
|
||||||
* @param {string} room
|
async function getCanvas(room) {
|
||||||
* @return {Promise<Object?>} The response Json object
|
|
||||||
*/
|
|
||||||
async function fetchCanvas(room) {
|
|
||||||
if (!room) {
|
if (!room) {
|
||||||
console.error("Cannot fetch canvas of an undefined room");
|
console.error("Cannot fetch canvas of an undefined room");
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -14,151 +11,25 @@ async function fetchCanvas(room) {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response || !response.ok) {
|
if (!response.ok) {
|
||||||
// console.error(
|
console.error("Could not retrieve room canvas: " + response.statusText);
|
||||||
// "Could not retrieve room canvas: " + response && response.statusText
|
console.debug(await response.text());
|
||||||
// ? 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await response.json();
|
const res = await response.json();
|
||||||
|
|
||||||
console.debug(`Retrieved canvas for room ${room}:`);
|
console.debug(`Retrieved canvas for room ${room}:`);
|
||||||
// console.debug(res);
|
console.debug(res);
|
||||||
|
|
||||||
return 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
|
// subscribe to the stream of a room
|
||||||
function subscribeToRoom() {}
|
function subscribeToRoom() {}
|
||||||
|
|
||||||
// get the pixel info of a room
|
// get the pixel info of a room
|
||||||
async function getPixelInfo(room, posX, posY) {
|
function getPixelInfo() {}
|
||||||
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
|
||||||
async function placePixel(room, posX, posY, color) {
|
function placePixel() {}
|
||||||
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 };
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,13 @@ import { resetValues } from "./canvas/utils";
|
||||||
// - updateRoom (update a room's configuration)
|
// - updateRoom (update a room's configuration)
|
||||||
// - deleteRoom (delete a room)
|
// - deleteRoom (delete a room)
|
||||||
|
|
||||||
let roomConfig = null;
|
const roomsConfig = {};
|
||||||
|
|
||||||
function setCurrentRoomConfig(cfg) {
|
function setCurrentRoomConfig(room, cfg) {
|
||||||
roomConfig = cfg;
|
roomsConfig[room] = cfg;
|
||||||
}
|
}
|
||||||
async function getCurrentRoomConfig(room) {
|
function getCurrentRoomConfig(room) {
|
||||||
if (!roomConfig) {
|
return roomsConfig[room];
|
||||||
await fetchRoomConfig(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
return roomConfig;
|
|
||||||
}
|
}
|
||||||
async function joinRoom(room) {
|
async function joinRoom(room) {
|
||||||
// socket.on('connection', (sockett) => {
|
// socket.on('connection', (sockett) => {
|
||||||
|
|
@ -46,12 +42,8 @@ async function joinRoom(room) {
|
||||||
async function listRooms() {
|
async function listRooms() {
|
||||||
const response = await authedAPIRequest("/rooms/", { method: "GET" });
|
const response = await authedAPIRequest("/rooms/", { method: "GET" });
|
||||||
|
|
||||||
if (!response || !response.ok) {
|
if (!response.ok) {
|
||||||
console.error(
|
console.error("Could not retrieve rooms list: " + response.statusText);
|
||||||
"Could not retrieve rooms list: " + response && response.statusText
|
|
||||||
? response.statusText
|
|
||||||
: "null",
|
|
||||||
);
|
|
||||||
console.debug(await response.text());
|
console.debug(await response.text());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -85,12 +77,8 @@ async function fetchRoomConfig(room) {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response || !response.ok) {
|
if (!response.ok) {
|
||||||
console.error(
|
console.error("Could not retrieve room config: " + response.statusText);
|
||||||
"Could not retrieve room config" + response && response.statusText
|
|
||||||
? response.statusText
|
|
||||||
: "null",
|
|
||||||
);
|
|
||||||
console.debug(await response.text());
|
console.debug(await response.text());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -103,10 +91,9 @@ async function fetchRoomConfig(room) {
|
||||||
setCurrentRoomConfig(res);
|
setCurrentRoomConfig(res);
|
||||||
|
|
||||||
// Update HTML
|
// Update HTML
|
||||||
document.getElementById("room-name").innerText = res.metadata.name;
|
const roomNameElt = document.getElementById("room-name");
|
||||||
document.getElementById("room-description").innerText =
|
|
||||||
res.metadata.description;
|
roomNameElt.innerText = res.metadata.name;
|
||||||
document.getElementById("room-description").style.display = "block";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
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) {
|
||||||
|
|
@ -12,12 +11,8 @@ async function getStudent(login) {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response || !response.ok) {
|
if (!response.ok) {
|
||||||
console.error(
|
console.error("Could not retrieve student: " + response.statusText);
|
||||||
"Could not retrieve student: " + response && response.statusText
|
|
||||||
? response.statusText
|
|
||||||
: " no status text",
|
|
||||||
);
|
|
||||||
console.debug(await response.text());
|
console.debug(await response.text());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +23,8 @@ async function getStudent(login) {
|
||||||
console.debug(res);
|
console.debug(res);
|
||||||
|
|
||||||
// Update HTML
|
// Update HTML
|
||||||
displayStudentProfile(res.avatarURL, res.login, res.guild, res.quote);
|
// const roomNameElt = document.getElementById("room-name");
|
||||||
|
// 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
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue