given files
This commit is contained in:
parent
9bf88844e9
commit
a2c31f873d
48 changed files with 10458 additions and 0 deletions
7
src/rooms/canvas/index.js
Normal file
7
src/rooms/canvas/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// FIXME: This file should handle the room canvas API
|
||||
// Link buttons to their respective functions
|
||||
// Functions may include:
|
||||
// - getCanvas (get the canvas of a room and deserialize it)
|
||||
// - subscribeToRoom (subscribe to the stream of a room)
|
||||
// - getPixelInfo (get the pixel info of a room)
|
||||
// - placePixel (place a pixel in a room)
|
||||
467
src/rooms/canvas/utils.js
Normal file
467
src/rooms/canvas/utils.js
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
// This file handles the room canvas DOM manipulation
|
||||
// Functions includes:
|
||||
// - initCanvas (initialize the canvas)
|
||||
// - renderCanvasUpdate (render a canvas update)
|
||||
// - getPlacementData (get the necessary data to place a pixel)
|
||||
// - toggleTooltip (toggle the tooltip and display the pixel's information)
|
||||
|
||||
import $ from "jquery";
|
||||
|
||||
const canvasContainer = $("#canvas-container")?.[0];
|
||||
const canvas = $("#canvas")?.[0];
|
||||
const canvasCtx = canvas.getContext("2d");
|
||||
const selector = $("#selector")?.[0];
|
||||
|
||||
const positionTooltip = $("#position-tooltip")?.[0];
|
||||
const tooltip = $("#tooltip")?.[0];
|
||||
const colorPicker = $("#color-picker")?.[0];
|
||||
const colorWheelContainer = $("#color-wheel-container")?.[0];
|
||||
const colorWheel = $("#color-wheel")?.[0];
|
||||
|
||||
/**
|
||||
* Global variables
|
||||
*/
|
||||
let board, palette, selectedColorIdx;
|
||||
let animation;
|
||||
|
||||
const zoomSpeed = 1 / 25;
|
||||
let zoom = 2.5;
|
||||
|
||||
let x, y;
|
||||
let cx = 0;
|
||||
let cy = 0;
|
||||
let target = { x: 0, y: 0 };
|
||||
let isDrag = false;
|
||||
|
||||
/**
|
||||
* Returns the necessary data to place a pixel.
|
||||
*
|
||||
* Get the placement data, i.e. the color the user has selected and the
|
||||
* coordinates of the pixel he is focusing on.
|
||||
*
|
||||
* @returns {{color: number, posX: number, posX: number}} the data
|
||||
*/
|
||||
export const getPlacementData = () => ({
|
||||
color: selectedColorIdx,
|
||||
posX: target.x,
|
||||
posY: target.y,
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the currently focused pixel's information and display it in the tooltip.
|
||||
*
|
||||
* @param {boolean} [state=state]
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export const toggleTooltip = async (state = false) => {
|
||||
tooltip.style.display = state ? "flex" : "none";
|
||||
|
||||
if (state) {
|
||||
// FIXME: You should implement or call a function to get the pixel's information
|
||||
// and display it. Make use of target.x and target.y to get the pixel's position.
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the target position according to the top left corner of the canvas.
|
||||
*
|
||||
* @param {*} event
|
||||
*
|
||||
* @returns {x: number, y: number} the target position
|
||||
*/
|
||||
const calculateTarget = (event) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
const scaleY = canvas.height / rect.height;
|
||||
const canvasLeft = rect.left + window.pageXOffset;
|
||||
const canvasTop = rect.top + window.pageYOffset;
|
||||
|
||||
return {
|
||||
x: Math.floor(
|
||||
((event?.pageX ?? window.innerWidth / 2) - canvasLeft) * scaleX,
|
||||
),
|
||||
y: Math.floor(
|
||||
((event?.pageY ?? window.innerHeight / 2) - canvasTop) * scaleY,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the position tooltip according to the event.
|
||||
*
|
||||
* @param {*} event
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const positionUpdate = (event) => positionDisplay(calculateTarget(event));
|
||||
|
||||
/**
|
||||
* Update the tooltip's position.
|
||||
*
|
||||
* @param {{x: number, y: number}} target
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const positionDisplay = ({ x, y }) => {
|
||||
positionTooltip.innerText = `X=${x} Y=${y}`;
|
||||
canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`;
|
||||
|
||||
// We add the canvas.width * zoom to make cx and cy positive
|
||||
let selectorX = cx + canvas.width * zoom;
|
||||
let selectorY = cy + canvas.height * zoom;
|
||||
|
||||
// Make odd canvas align
|
||||
if (canvas.width % 2 !== 0) {
|
||||
selectorX += zoom / 2;
|
||||
selectorY += zoom / 2;
|
||||
}
|
||||
|
||||
// Find the translate
|
||||
selectorX %= zoom;
|
||||
selectorY %= zoom;
|
||||
|
||||
// Center selector on the pixel
|
||||
selectorX -= zoom / 2;
|
||||
selectorY -= zoom / 2;
|
||||
|
||||
selector.style.transform = `translate(${selectorX}px, ${selectorY}px) scale(${zoom})`;
|
||||
};
|
||||
|
||||
// Toggle the color wheel on click on the color picker
|
||||
colorPicker.addEventListener("click", () => {
|
||||
const state = colorWheelContainer.style.display;
|
||||
|
||||
colorWheelContainer.style.display =
|
||||
!state || state === "none" ? "block" : "none";
|
||||
});
|
||||
|
||||
/**
|
||||
* Transform #RRGGBB to 0xBBGGRRAA. Hexadecimal color to 32 bits integer.
|
||||
*
|
||||
* @param {string} hex
|
||||
*
|
||||
* @returns {number} the 32 bits color
|
||||
*/
|
||||
const transformHexTo32Bits = (hex) => {
|
||||
const reverse = hex.substring(1).match(/.{2}/g).reverse().join("");
|
||||
|
||||
return parseInt(`0xFF${reverse}`, 16);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the canvas.
|
||||
*
|
||||
* @param {number[]} pixels
|
||||
* @param {string[]} colors
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
const renderCanvas = (pixels, colors) => {
|
||||
const img = new ImageData(canvas.width, canvas.height);
|
||||
const data = new Uint32Array(img.data.buffer);
|
||||
|
||||
board = pixels;
|
||||
palette = colors;
|
||||
for (let i = 0; i < pixels.length; i++) {
|
||||
data[i] = transformHexTo32Bits(colors[pixels[i]]);
|
||||
}
|
||||
|
||||
canvasCtx.putImageData(img, 0, 0);
|
||||
canvasCtx.imageSmoothingEnabled = false;
|
||||
canvas.style.imageRendering = "pixelated";
|
||||
|
||||
// Remove all the colors from the color wheel
|
||||
while (colorWheel.firstChild) {
|
||||
colorWheel.removeChild(colorWheel.firstChild);
|
||||
}
|
||||
|
||||
// Add the colors to the color wheel
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
const btn = document.createElement("button");
|
||||
|
||||
colorWheel.appendChild(btn);
|
||||
|
||||
btn.addEventListener("click", () => {
|
||||
selectedColorIdx = i;
|
||||
colorPicker.style.color = colors[i];
|
||||
colorPicker.style.border = `${colors[i]} 0.1rem solid`;
|
||||
});
|
||||
|
||||
btn.style.backgroundColor = colors[i];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the canvas with the given room configuration and pixels.
|
||||
*
|
||||
* @param {*} roomConfig
|
||||
* @param {number[]} pixels
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export const initCanvas = (roomConfig, pixels) => {
|
||||
const canvasDimensions = roomConfig.metadata.canvasDimensions;
|
||||
|
||||
canvas.width = canvasDimensions;
|
||||
canvas.height = canvasDimensions;
|
||||
|
||||
positionDisplay({ x: canvasDimensions / 2, y: canvasDimensions / 2 });
|
||||
selectedColorIdx = 0;
|
||||
|
||||
const roomColors = roomConfig.settings.roomColors.split(",");
|
||||
|
||||
colorPicker.style.color = roomColors[0];
|
||||
colorPicker.style.border = `${roomColors[0]} 0.1rem solid`;
|
||||
|
||||
renderCanvas(pixels, roomColors);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the canvas update, i.e. update the pixel at the given coordinates.
|
||||
*
|
||||
* @param {string} color
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export const renderCanvasUpdate = (color, x, y) => {
|
||||
const img = new ImageData(canvas.width, canvas.height);
|
||||
const data = new Uint32Array(img.data.buffer);
|
||||
|
||||
board[y * canvas.width + x] = color;
|
||||
for (let i = 0; i < board.length; i++) {
|
||||
data[i] = transformHexTo32Bits(palette[board[i]]);
|
||||
}
|
||||
|
||||
canvasCtx.putImageData(img, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the values of the canvas, i.e. the zoom, the coordinates and the
|
||||
* display position.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export const resetValues = () => {
|
||||
zoom = 2.5;
|
||||
x = 0;
|
||||
y = 0;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
isDrag = false;
|
||||
|
||||
positionDisplay({ x, y });
|
||||
colorWheelContainer.style.display = "none";
|
||||
toggleTooltip(false);
|
||||
};
|
||||
|
||||
// Handle scroll on canvas
|
||||
document.addEventListener("wheel", (e) => {
|
||||
// Make sure we're scrolling on the canvas or the body and not the UI
|
||||
if (e.target !== canvas && e.target !== canvasContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(animation);
|
||||
toggleTooltip(false);
|
||||
|
||||
const delta = Math.sign(e.deltaY) * zoomSpeed;
|
||||
const zoomFactor = 1 + delta;
|
||||
const oldZoom = zoom;
|
||||
const newZoom = Math.max(2.5, Math.min(40, oldZoom * zoomFactor));
|
||||
|
||||
// Get the position of the mouse relative to the canvas
|
||||
const mouseX = e.clientX - window.innerWidth / 2;
|
||||
const mouseY = e.clientY - window.innerHeight / 2;
|
||||
|
||||
// Calculate the new center point based on the mouse position
|
||||
const newCx = mouseX - (mouseX - cx) * (newZoom / oldZoom);
|
||||
const newCy = mouseY - (mouseY - cy) * (newZoom / oldZoom);
|
||||
|
||||
if (newZoom !== oldZoom) {
|
||||
zoom = newZoom;
|
||||
cx = newCx;
|
||||
cy = newCy;
|
||||
positionUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle click and drag on canvas
|
||||
document.addEventListener("mousedown", (e) => {
|
||||
// Make sure we're clicking on the canvas or the body and not the UI
|
||||
if (e.target !== canvas && e.target !== canvasContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Ignore if right click
|
||||
if (e.button === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(animation);
|
||||
|
||||
isDrag = false;
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
|
||||
document.addEventListener("mousemove", mouseMove);
|
||||
});
|
||||
|
||||
// Smooth animation
|
||||
function easeOutQuart(t, b, c, d) {
|
||||
t /= d;
|
||||
t--;
|
||||
return -c * (t * t * t * t - 1) + b;
|
||||
}
|
||||
|
||||
// Handle when the user releases the mouse
|
||||
document.addEventListener("mouseup", (e) => {
|
||||
document.removeEventListener("mousemove", mouseMove);
|
||||
|
||||
// Make sure we're clicking on the canvas or the body and not the UI
|
||||
if (e.target !== canvas && e.target !== canvasContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Get the tile position
|
||||
target = calculateTarget(e);
|
||||
|
||||
// Make sure we're clicking on the canvas
|
||||
if (
|
||||
target.x >= 0 &&
|
||||
target.x < canvas.width &&
|
||||
target.y >= 0 &&
|
||||
target.y < canvas.height
|
||||
) {
|
||||
// We want to differentiate between a click and a drag
|
||||
// If it is a click, we want to move the camera to the clicked tile
|
||||
|
||||
// We wait to see if the position changed
|
||||
// If it did not, we consider it a click
|
||||
if (!isDrag) {
|
||||
const duration = 1000;
|
||||
const startZoom = zoom;
|
||||
const endZoom = Math.max(15, Math.min(40, zoom));
|
||||
|
||||
// Get the position of the click in relation to the center of the screen
|
||||
const clickX = e.clientX - window.innerWidth / 2;
|
||||
const clickY = e.clientY - window.innerHeight / 2;
|
||||
const canvaswidthzoom = canvas.width * startZoom;
|
||||
const canvasheightzoom = canvas.height * startZoom;
|
||||
const startx = (cx + canvaswidthzoom / 2) / startZoom;
|
||||
const starty = (cy + canvasheightzoom / 2) / startZoom;
|
||||
const endx = startx - clickX / startZoom;
|
||||
const endy = starty - clickY / startZoom;
|
||||
const endCx = endx * endZoom - (canvas.width / 2) * endZoom;
|
||||
const endCy = endy * endZoom - (canvas.height / 2) * endZoom;
|
||||
const startCx = cx;
|
||||
const startCy = cy;
|
||||
const startTime = Date.now();
|
||||
|
||||
// If the distance is small enough, we just warp to it
|
||||
if (
|
||||
Math.abs(endCx - startCx) < 10 &&
|
||||
Math.abs(endCy - startCy) < 10
|
||||
) {
|
||||
cx = endCx;
|
||||
cy = endCy;
|
||||
zoom = endZoom;
|
||||
canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`;
|
||||
} else {
|
||||
clearInterval(animation);
|
||||
|
||||
animation = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
if (elapsed >= duration) {
|
||||
clearInterval(animation);
|
||||
return;
|
||||
}
|
||||
|
||||
const t = elapsed / duration;
|
||||
|
||||
zoom = easeOutQuart(t, startZoom, endZoom - startZoom, 1);
|
||||
cx = easeOutQuart(t, startCx, endCx - startCx, 1);
|
||||
cy = easeOutQuart(t, startCy, endCy - startCy, 1);
|
||||
|
||||
positionUpdate();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle the tooltip if it is a click
|
||||
toggleTooltip(!isDrag);
|
||||
|
||||
// Update the position of the tooltip
|
||||
positionDisplay(target);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle mouse move
|
||||
const mouseMove = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
toggleTooltip(false);
|
||||
positionUpdate();
|
||||
|
||||
const dx = e.clientX - x;
|
||||
const dy = e.clientY - y;
|
||||
|
||||
// For a big enough delta, we consider it a drag
|
||||
if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) {
|
||||
isDrag = true;
|
||||
}
|
||||
|
||||
x = e.clientX;
|
||||
y = e.clientY;
|
||||
cx += dx;
|
||||
cy += dy;
|
||||
|
||||
canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`;
|
||||
};
|
||||
|
||||
export const displayLeaderboard = (guilds) => {
|
||||
console.log(guilds);
|
||||
const list = document.getElementById("leaderboard-list");
|
||||
|
||||
for (const { name, points } of guilds) {
|
||||
console.log(`${name} has ${points}`);
|
||||
const existingItem = [
|
||||
...list.querySelectorAll(".LeaderboardItem"),
|
||||
].find((el) => el.dataset.guild === name);
|
||||
|
||||
if (existingItem) {
|
||||
existingItem.querySelector(".GuildPoints").textContent = points;
|
||||
existingItem.dataset.points = points;
|
||||
} else {
|
||||
const li = document.createElement("li");
|
||||
|
||||
li.className = "LeaderboardItem";
|
||||
li.dataset.guild = name;
|
||||
li.dataset.points = points;
|
||||
li.innerHTML = `
|
||||
<span class="Rank"></span>
|
||||
<span class="GuildName">${name}</span>
|
||||
<span class="GuildPoints">${points}</span>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
const items = [...list.querySelectorAll(".LeaderboardItem")].sort(
|
||||
(a, b) => b.dataset.points - a.dataset.points,
|
||||
);
|
||||
|
||||
list.innerHTML = "";
|
||||
items.slice(0, 5).forEach((item, index) => {
|
||||
item.className = `LeaderboardItem rank-${index + 1}`;
|
||||
item.querySelector(".Rank").textContent = index + 1;
|
||||
list.appendChild(item);
|
||||
});
|
||||
};
|
||||
4
src/rooms/chat/index.js
Normal file
4
src/rooms/chat/index.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// FIXME: This file should handle the room's chat subscription
|
||||
// Functions may include:
|
||||
// - subscribeToRoomChat (subscribe to the chat of a room)
|
||||
// - sendChatMessage (send a chat message)
|
||||
6
src/rooms/chat/utils.js
Normal file
6
src/rooms/chat/utils.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// FIXME: This file should handle the room's chat DOM manipulation
|
||||
// Link buttons to their respective functions
|
||||
// Handle the chat input form and its submission
|
||||
// Functions may include:
|
||||
// - displayChatMessage (display a chat message in the DOM)
|
||||
// - displayUserEvents (display a user event in the DOM)
|
||||
10
src/rooms/index.js
Normal file
10
src/rooms/index.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// FIXME: This file should handle the rooms API
|
||||
// Functions may include:
|
||||
// - fetchRoomConfig (get the configuration of a room)
|
||||
// - setCurrentRoomConfig (set the current room configuration and update the DOM accordingly)
|
||||
// - getCurrentRoomConfig (get the current room configuration)
|
||||
// - joinRoom (join a room by its slug)
|
||||
// - listRooms (list all the rooms available)
|
||||
// - createRoom (create a room)
|
||||
// - updateRoom (update a room's configuration)
|
||||
// - deleteRoom (delete a room)
|
||||
6
src/rooms/utils.js
Normal file
6
src/rooms/utils.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// FIXME: This file should handle the rooms DOM manipulation
|
||||
// Link buttons to their respective functions
|
||||
// Functions may include:
|
||||
// - showModal (add a form modal to the DOM)
|
||||
// - createRoomObject (create a room in the DOM)
|
||||
// - displayRoomsList (display the rooms list in the DOM)
|
||||
Loading…
Add table
Add a link
Reference in a new issue