given files
This commit is contained in:
parent
9bf88844e9
commit
a2c31f873d
48 changed files with 10458 additions and 0 deletions
12
src/pages/complete/epita/index.html
Normal file
12
src/pages/complete/epita/index.html
Normal 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>
|
||||
2
src/pages/complete/epita/index.js
Normal file
2
src/pages/complete/epita/index.js
Normal 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
|
||||
58
src/pages/debug/debug.html
Normal file
58
src/pages/debug/debug.html
Normal 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
122
src/pages/debug/debug.js
Normal 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
128
src/pages/debug/index.css
Normal 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
57
src/pages/debug/utils.js
Normal 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
78
src/pages/index.css
Normal 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
239
src/pages/index.html
Normal 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
6
src/pages/index.js
Normal 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
1080
src/pages/styles.less
Normal file
File diff suppressed because it is too large
Load diff
83
src/pages/utils.js
Normal file
83
src/pages/utils.js
Normal 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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue