given files

This commit is contained in:
Gu://em_ 2026-05-15 11:08:23 +02:00
parent 9bf88844e9
commit a2c31f873d
48 changed files with 10458 additions and 0 deletions

View file

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>E/PLACE</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<script type="module" src="index.js"></script>
</head>
<body></body>
</html>

View file

@ -0,0 +1,2 @@
// FIXME: This file should handle the auth redirection
// Get the code from the URL parameters and redirect to the relevant page

View file

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Debug Page</title>
</head>
<body>
<div class="AlertContainer" id="alert-container"></div>
<div class="container">
<h1>Debug Dashboard</h1>
<section class="card">
<h2>Local Storage Tokens</h2>
<div class="token-section">
<h3>Access Token</h3>
<p id="token" class="token-text">{{ token }}</p>
<h3>Refresh Token</h3>
<p id="refresh_token" class="token-text">{{ refresh_token }}</p>
</div>
<div class="token-actions button-group">
<button id="deleteTokenBtn">Delete Access Token</button>
<button id="deleteRefreshTokenBtn">Delete Refresh Token</button>
<button id="clearTokens">Clear All Tokens</button>
</div>
</section>
<section class="card">
<h2>User Profile</h2>
<div class="StudentProfile">
<img src="" class="Avatar" id="profile-info-avatar" alt="User Avatar" />
<div class="TextContainer">
<span class="Login" id="profile-info-login">no login</span>
<span class="Quote" id="profile-info-quote"></span>
</div>
</div>
<div class="profile-actions button-group">
<button id="launchOIDC">Launch OIDC</button>
<button id="displayProfile">Display Profile Info</button>
<button id="hideProfile">Clear Profile Info</button>
</div>
</section>
<section class="card">
<h2>Error Simulation</h2>
<div class="error-actions button-group">
<button id="errorBtn">Generate Error Response</button>
<button id="invalidToken">Generate Invalid Token</button>
<button id="expiredTokenBtn">Generate Expired Token</button>
</div>
</section>
</div>
</body>
</html>

122
src/pages/debug/debug.js Normal file
View file

@ -0,0 +1,122 @@
import $ from "jquery";
import debugHtml from "./debug.html";
import { displayStudentProfile, expired } from "./utils";
import { createAlert } from "../../utils/notify";
const { authedAPIRequest, authenticate } = await import("../../utils/auth.js");
const authed = authedAPIRequest ?? (() => {});
function clearLocalStorage() {
localStorage.removeItem("token");
localStorage.removeItem("refresh_token");
}
function clearUserProfile(log = true) {
$("#profile-info-login").text("No login");
$("#profile-info-quote").text("No quote");
log && createAlert("Debug", "Clear user profile info", "success");
}
function refreshLocalStorage() {
$("#token").text(localStorage.getItem("token") ?? "N/A");
$("#refresh_token").text(localStorage.getItem("refresh_token") ?? "N/A");
}
async function refreshProfile() {
await displayStudentProfile()
.then(() => {
createAlert("Debug", "Display profile succeed", "success");
})
.catch((_error) => {
createAlert("Debug", "Cannot display profile", "error");
clearUserProfile(false);
});
}
async function refresh() {
refreshLocalStorage();
await refreshProfile();
}
(() => {
if (import.meta.env.MODE !== "debug") {
return;
}
$.get(debugHtml, function (response) {
$("body").html(response);
refreshLocalStorage();
}).fail(function (_xhr, _status, error) {
console.error("Error fetching debug HTML:", error);
});
$(document).on("click", "#launchOIDC", async function () {
clearLocalStorage();
if (await authenticate()) {
createAlert("Debug", "OIDC succeed", "error");
} else {
createAlert("Debug", "OIDC failed", "error");
}
await refresh();
});
$(document).on("click", "#displayProfile", refreshProfile);
$(document).on("click", "#hideProfile", clearUserProfile);
$(document).on("click", "#errorBtn", async function () {
await authed("/tests/error", { method: "GET" });
createAlert("Debug", "Error response generated", "success");
await refresh();
});
$(document).on("click", "#invalidToken", async function () {
await authedAPIRequest("/tests/invalid-token", { method: "POST" });
createAlert("Debug", "Invalid token response generated", "success");
await refresh();
});
$(document).on("click", "#expiredTokenBtn", async function () {
expired();
let res = await authedAPIRequest("/tests/valid", { method: "GET" });
res = await res.json();
if (res.response === "A valid response") {
createAlert(
"Debug",
"Token has been refreshed and the request has been re-send",
"success",
);
} else {
createAlert("Debug", "An error occured", "error");
}
await refresh();
});
$(document).on("click", "#deleteTokenBtn", async function () {
localStorage.removeItem("token");
createAlert("Debug", "Token removed from local storage", "success");
await refresh();
});
$(document).on("click", "#deleteRefreshTokenBtn", async function () {
localStorage.removeItem("refresh_token");
createAlert(
"Debug",
"Refresh token removed from local storage",
"success",
);
await refresh();
});
$(document).on("click", "#clearTokens", async function () {
createAlert(
"Debug",
"Token and refresh token removed from local storage",
"success",
);
clearLocalStorage();
await refresh();
});
refresh();
})();

128
src/pages/debug/index.css Normal file
View file

@ -0,0 +1,128 @@
:root {
--bg-page: #f0f2f5;
--bg-card: #5A6991;
--bg-token: #e8eaed;
--bg-profile: #e8eaed;
--border-card: #d1d5db;
--text-heading: #111827;
--text-label: #6b7280;
--text-body: #1f2937;
--text-quote: #6b7280;
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.08);
--btn-bg: #455069;
--btn-bg-hover: #111827;
--btn-text: #ffffff;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-page: #0f1117;
--bg-card: #1a1d27;
--bg-token: #2a2d3a;
--bg-profile: #2a2d3a;
--border-card: #2e3348;
--text-heading: #f0f2f5;
--text-label: #8b92a5;
--text-body: #e2e5ed;
--text-quote: #8b92a5;
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.4);
--btn-bg: #3B4257;
--btn-bg-hover: #51596E;
--btn-text: #e2e5ed;
}
}
body {
background-color: var(--bg-page);
color: var(--text-body);
transition: background-color 0.2s, color 0.2s;
}
.container {
width: 800px;
}
.card {
background-color: var(--bg-card);
border: 1px solid var(--border-card);
border-radius: 10px;
padding: 20px;
margin: 20px;
box-shadow: var(--shadow-card);
}
.card h2 {
margin-top: 0;
margin-bottom: 15px;
font-size: 1.5em;
color: var(--text-heading);
}
.token-section h3 {
margin-bottom: 5px;
font-size: 1.1em;
color: var(--text-body);
}
.token-text {
background-color: var(--bg-token);
padding: 10px;
border-radius: 5px;
word-break: break-all;
color: var(--text-body);
}
.StudentProfile {
display: flex;
align-items: center;
gap: 15px;
width: 30%;
margin: 10px 0 30px;
border-radius: 5px;
padding: 7px 10px;
background-color: var(--bg-profile);
}
.Avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 1px solid var(--border-card);
}
.TextContainer {
display: flex;
flex-direction: column;
}
.Login {
font-weight: bold;
font-size: 1.1em;
color: var(--text-body);
}
.Quote {
font-style: italic;
color: var(--text-quote);
}
.button-group {
display: flex;
justify-content: space-around;
}
button {
background-color: var(--btn-bg);
color: var(--btn-text);
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 0.9em;
cursor: pointer;
}
button:hover {
background-color: var(--btn-bg-hover);
}

57
src/pages/debug/utils.js Normal file
View file

@ -0,0 +1,57 @@
// FIXME: File that provide utils function for the debug page
import $ from "jquery";
import jwt_decode from "jwt-decode";
import { createAlert } from "../../utils/notify";
export async function displayStudentProfile() {
const token = localStorage.getItem("token");
const decoded = jwt_decode(token);
const _uid = decoded.uid;
// You have to write a request to fetch your informations
const request_result = null;
if (request_result === null) {
createAlert(
"debug",
"Fetch not implemented in the pages/debug/utils.js file",
"error",
);
}
const student_resources = await request_result?.json();
$("#profile-info-avatar").attr(
"src",
student_resources.avatarURL ?? "/default-avatar.png",
);
$("#profile-info-login").text(student_resources.login);
$("#profile-info-quote").text(student_resources.quote);
}
export function expired() {
const token = localStorage.getItem("token");
const splited_token = token.split(".");
let base64 = splited_token[1].replace(/-/g, "+").replace(/_/g, "/");
while (base64.length % 4 !== 0) {
base64 += "=";
}
const parts = JSON.parse(atob(base64));
parts["exp"] = 0;
const recodedValue = btoa(JSON.stringify(parts))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
splited_token[1] = recodedValue;
const expiredToken = splited_token.join(".");
localStorage.setItem("token", expiredToken);
}

78
src/pages/index.css Normal file
View file

@ -0,0 +1,78 @@
@import url(http://fonts.googleapis.com/css?family=Barlow:800);
@import url(http://fonts.googleapis.com/css?family=Montserrat:400,700);
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif, Barlow;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
justify-content: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
margin: 0;
}
h2 {
margin: 0;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
input {
border: none;
}
.token-text {
max-width: 10ch;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

239
src/pages/index.html Normal file
View file

@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>E/PLACE</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link
rel="stylesheet"
type="text/css"
media="screen"
href="index.css"
/>
<link rel="stylesheet/less" type="text/css" href="styles.less" />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/less"></script>
<script type="module">
if (import.meta.env.MODE === "debug") {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.media = "screen";
link.href = "debug/index.css";
document.head.appendChild(link);
}
const script = document.createElement("script");
if (import.meta.env.MODE !== "debug") {
script.src = "./index.js";
} else {
script.src = "./debug/debug.js";
}
script.type = "module";
document.head.appendChild(script);
</script>
</head>
<body>
<div class="App">
<div class="AlertContainer" id="alert-container"></div>
<div class="Container" id="container">
<div class="RoomList Hidden" id="left-container">
<div class="Header">
<h1 class="Title">E/PLACE</h1>
<button class="CloseButton" id="close-left">
<i class="fa-solid fa-backward"></i>
</button>
</div>
<div class="ListContainer">
<div class="ListSearchBar">
<div class="InputContainer">
<input
aria-placeholder="Search a room..."
placeholder="Search a room..."
class="Input"
id="search-input"
/>
<div class="Divider"></div>
<button class="RoomButton" id="create-room">
<i class="fa-solid fa-square-plus"></i>
</button>
<button class="RoomButton" id="refresh-rooms">
<i class="fa-solid fa-rotate"></i>
</button>
</div>
<div class="FilterContainer">
<span class="FilterText">Filters:</span>
<div class="FilterHeader">
<button class="Filter" id="filter-name">
<span>Name</span>
<i class="fa-solid fa-sort-down"></i>
</button>
<button class="Filter" id="filter-owner">
<span>Owned by you</span>
<i class="fa-solid fa-plus"></i>
</button>
<button class="Filter" id="filter-public">
<span>Public</span>
<i class="fa-solid fa-minus"></i>
</button>
<button class="Filter" id="filter-private">
<span>Private</span>
<i class="fa-solid fa-minus"></i>
</button>
</div>
</div>
</div>
<div class="RoomsContainer" id="rooms-container"></div>
</div>
<div class="StudentProfile">
<img src="" class="Avatar" id="profile-info-avatar" />
<div class="TextContainer">
<span class="Login" id="profile-info-login"></span>
<span class="Quote" id="profile-info-quote"></span>
</div>
<button class="ModifyProfileButton" id="profile-update">
<i class="fa-solid fa-pencil"></i>
</button>
</div>
</div>
<div class="RoomCanvas">
<div class="Header">
<div class="Header-left">
<button class="HeaderButton" id="MenuButton">
Menu
</button>
</div>
<div class="Header-center">
<div class="TextContainer">
<h2 class="Title" id="room-name">
{{room_name}}
</h2>
<span class="Description" id="room-description"
>{{room_description}}</span
>
</div>
<div class="HeaderDivider"></div>
<div class="ButtonContainer">
<button class="RoomButton" id="edit-room">
<i class="fa-solid fa-pencil"></i>
</button>
<button class="RoomButton" id="delete-room">
<i class="fa-solid fa-trash-can"></i>
</button>
</div>
</div>
<div class="Header-right">
<button class="HeaderButton" id="ChatButton">
Chat
</button>
</div>
</div>
<div class="GuildLeaderboard" id="guild-leaderboard">
<div class="LeaderboardHeader">
<i class="fa-solid fa-trophy"></i>
<span class="LeaderboardTitle">GUILDS</span>
</div>
<ol class="LeaderboardList" id="leaderboard-list"></ol>
</div>
<div class="CanvasContainer" id="canvas-container">
<canvas class="Canvas" id="canvas"></canvas>
<img
class="Selector"
id="selector"
src="/selector.svg"
/>
<div class="Tooltip" id="tooltip">
<div class="Header">
<div class="PlacedByInfo">
<img
src="{{student_avatar}}"
class="Avatar"
id="tooltip-info-avatar"
/>
<div class="Profile">
<span
class="Login"
id="tooltip-info-login"
>{{student_login}}</span
>
<span
class="Guild"
id="tooltip-info-guild"
>{{student_guild}}</span
>
<span
class="Quote"
id="tooltip-info-quote"
>{{student_quote}}</span
>
</div>
</div>
<div class="TextContainer">
<span id="tooltip-date"
>{{date_placed}}</span
>
<span id="tooltip-time"
>{{time_placed}}</span
>
</div>
</div>
<div class="ButtonContainer">
<button class="ColorPicker" id="color-picker">
<i class="fas fa-eye-dropper"></i>
</button>
<button
class="PlaceButton"
id="color-place-button"
>
PLACE
</button>
</div>
</div>
</div>
<div class="PositionTooltip" id="position-tooltip">
<span>X=0</span>
<span>Y=0</span>
</div>
<div class="ColorWheelContainer" id="color-wheel-container">
<div class="ColorWheel" id="color-wheel"></div>
</div>
</div>
<div class="RoomChat" id="right-container">
<div class="ChatContainer">
<div class="Header">
<button class="CloseButton" id="close-right">
<i class="fa-solid fa-forward"></i>
</button>
</div>
<div
class="ChatMessageContainer"
id="chat-message-container"
></div>
<form
class="InputContainer"
id="chat-input-form"
autocomplete="off"
>
<input
aria-placeholder="Type your message here..."
placeholder="Type your message here..."
class="Input"
id="message-content"
name="message-content"
autocomplete="off"
/>
<div class="ChatTimeout">
<i class="fa-solid fa-stopwatch"></i>
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>

6
src/pages/index.js Normal file
View file

@ -0,0 +1,6 @@
// FIXME: This is the entry point of the application, write your code here
import { calculateLayout } from "./utils";
// Initialize the layout
calculateLayout();

1080
src/pages/styles.less Normal file

File diff suppressed because it is too large Load diff

83
src/pages/utils.js Normal file
View file

@ -0,0 +1,83 @@
/**
* Global variables
*/
let [leftSize, rightSize] = [
localStorage.getItem("leftSize") ?? 0,
localStorage.getItem("rightSize") ?? 0,
];
const parentContainer = document.getElementById("container");
const leftContainer = document.getElementById("left-container");
const closeLeftButton = document.getElementById("close-left");
const rightContainer = document.getElementById("right-container");
const closeRightButton = document.getElementById("close-right");
const roomButton = document.getElementById("MenuButton");
const chatButton = document.getElementById("ChatButton");
leftContainer.classList.toggle("Hidden", !leftSize);
/**
* Calculate the layout of the home page.
*
* Initialize the layout of the home page, the left and right sidebars, the
* rest of the script adds event listeners to the elements in the layout so
* that the layout follows the mouse cursor when clicked and dragged.
*
* @returns {void}
**/
export const calculateLayout = () => {
const parentContainerSize = `${4.5 - leftSize - rightSize}fr`;
// left and right are reversed because of the grid layout
parentContainer.style.gridTemplateColumns = `${leftSize}fr ${parentContainerSize} ${rightSize}fr`;
leftContainer.style.opacity = leftSize;
rightContainer.style.opacity = rightSize;
};
closeLeftButton.addEventListener("click", () => {
leftSize = 1 - leftSize;
localStorage.setItem("leftSize", leftSize);
calculateLayout();
setTimeout(
() => {
leftContainer.classList.toggle("Hidden", true);
},
leftSize ? 0 : 300,
);
});
closeRightButton.addEventListener("click", () => {
rightSize = 1 - rightSize;
localStorage.setItem("rightSize", rightSize);
calculateLayout();
setTimeout(
() => {
rightContainer.classList.toggle("Hidden", true);
},
rightSize ? 0 : 300,
);
});
roomButton.addEventListener("click", () => {
leftSize = 1;
localStorage.setItem("leftSize", leftSize);
calculateLayout();
setTimeout(() => {
leftContainer.classList.toggle("Hidden", false);
}, 300);
});
chatButton.addEventListener("click", () => {
rightSize = 1;
localStorage.setItem("rightSize", rightSize);
calculateLayout();
setTimeout(() => {
rightContainer.classList.toggle("Hidden", false);
}, 300);
});