🏗️」 wip(front): profile page

This commit is contained in:
y-syo
2025-10-19 09:29:33 +02:00
parent f4afb7fdb5
commit ed2aa325e5
11 changed files with 448 additions and 128 deletions

View File

@ -42,8 +42,8 @@
<div class="flex px-4 items-center content-center space-x-2"> <div class="flex px-4 items-center content-center space-x-2">
<button id="profile-button" class="taskbar-button flex flex-row justify-center items-center"><img class="object-scale-down mr-2 h-5 w-5" src="https://api.kanel.ovh/id?id=65" /> start</button> <button id="profile-button" class="taskbar-button flex flex-row justify-center items-center"><img class="object-scale-down mr-2 h-5 w-5" src="https://api.kanel.ovh/id?id=65" /> start</button>
<div class="text-neutral-700 dark:text-neutral-400">|</div> <div class="text-neutral-700 dark:text-neutral-400">|</div>
<a class="taskbar-button" href="https://rusty.42angouleme.fr/">rusty</a> <a target="_blank" class="taskbar-button" href="https://rusty.42angouleme.fr/">rusty</a>
<a class="taskbar-button" href="/tetris" data-link>tetris</a> <a target="_blank" class="taskbar-button" href="https://dn720004.ca.archive.org/0/items/2009-tetris-variant-concepts_202201/2009%20Tetris%20Design%20Guideline.pdf">tetris-guideline.pdf</a>
</div> </div>
<div class="reverse-border m-1.5 h-8/10 content-center"> <div class="reverse-border m-1.5 h-8/10 content-center">
<span id="taskbar-clock" class="text- neutral-900 dark:text-white px-4">12:37</span> <span id="taskbar-clock" class="text- neutral-900 dark:text-white px-4">12:37</span>

View File

@ -1,4 +1,13 @@
@import "tailwindcss"; @import "tailwindcss";
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
}
@font-face { @font-face {
font-family: Kubasta; font-family: Kubasta;

View File

@ -1,6 +1,6 @@
import { oneko } from "./oneko.ts"; import { oneko } from "./oneko.ts";
import Profile from "./views/Profile.ts"; import ProfileMenu from "./views/ProfileMenu.ts";
let profile_view = new Profile; let profile_view = new ProfileMenu;
export async function isLogged(): Promise<boolean> { export async function isLogged(): Promise<boolean> {
let uuid_req = await fetch("http://localhost:3001/me", { let uuid_req = await fetch("http://localhost:3001/me", {
@ -40,6 +40,9 @@ const routes = [
{ path: "/login", view: () => import("./views/LoginPage.ts") }, { path: "/login", view: () => import("./views/LoginPage.ts") },
{ path: "/register", view: () => import("./views/RegisterPage.ts") }, { path: "/register", view: () => import("./views/RegisterPage.ts") },
{ path: "/profile", view: () => import("./views/Profile.ts") },
{ path: "/settings", view: () => import("./views/Settings.ts") },
]; ];
const router = async () => { const router = async () => {
@ -97,8 +100,6 @@ document.addEventListener("DOMContentLoaded", () => {
router(); router();
}); });
oneko();
function updateClock() function updateClock()
{ {
const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
@ -116,3 +117,5 @@ function updateClock()
setInterval(updateClock, 5000); setInterval(updateClock, 5000);
updateClock(); updateClock();
oneko();

View File

@ -18,20 +18,23 @@ export function setOnekoState(state: string) {
default: default:
oneko_state = 0; oneko_state = 0;
} }
return;
} }
export function setOnekoOffset() { export function setOnekoOffset() {
if (oneko_state == 1) if (oneko_state != 0)
{ {
offsetX = document.getElementById("window").offsetLeft + 44; offsetX = document.getElementById("window").offsetLeft + 44;
offsetY = document.getElementById("window").offsetTop + 44 + 24; offsetY = document.getElementById("window").offsetTop + 44 + 24;
console.log(offsetX, offsetY);
} }
return;
} }
export function setBallPos(x: number, y: number) { export function setBallPos(x: number, y: number)
{
mousePosX = x + offsetX; mousePosX = x + offsetX;
mousePosY = y + offsetY; mousePosY = y + offsetY;
return;
} }
export function oneko() { export function oneko() {
@ -39,7 +42,7 @@ export function oneko() {
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true; window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return; if (isReducedMotion) return ;
const nekoEl = document.createElement("div"); const nekoEl = document.createElement("div");
@ -141,15 +144,15 @@ export function oneko() {
{ {
mousePosX = event.clientX; mousePosX = event.clientX;
mousePosY = event.clientY; mousePosY = event.clientY;
} }
}); });
window.requestAnimationFrame(onAnimationFrame); window.requestAnimationFrame(onAnimationFrame);
} }
let lastFrameTimestamp; let lastFrameTimestamp: number;
function onAnimationFrame(timestamp) { function onAnimationFrame(timestamp: number) {
// Stops execution if the neko element is removed from DOM // Stops execution if the neko element is removed from DOM
if (!nekoEl.isConnected) { if (!nekoEl.isConnected) {
return; return;

View File

@ -46,6 +46,7 @@ export default class extends Aview {
async run() { async run() {
dragElement(document.getElementById("window")); dragElement(document.getElementById("window"));
let uuid: string;
let start: number = 0; let start: number = 0;
let elapsed: number; let elapsed: number;
@ -103,7 +104,7 @@ export default class extends Aview {
ballSpeedY = ballSpeed * Math.sin(theta); ballSpeedY = ballSpeed * Math.sin(theta);
} }
function moveBall() { async function moveBall() {
let length = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY); let length = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY);
let scale = ballSpeed / length; let scale = ballSpeed / length;
ballX += (ballSpeedX * scale) * elapsed; ballX += (ballSpeedX * scale) * elapsed;
@ -142,6 +143,17 @@ export default class extends Aview {
if (p1_score === 3 || p2_score === 3) if (p1_score === 3 || p2_score === 3)
{ {
console.log(isLogged());
if (await isLogged())
{
let uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
fetch(`http://localhost:3002/users/${uuid}/matchHistory`, {
method: "POST",
headers: { "Content-Type": "application/json", },
credentials: "include",
body: JSON.stringify({ "opponent": p2_name, "myScore": p1_score, "opponentScore": p2_score })
});
}
// ------------------------------------------------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------------------------------------------------
// //
// insert the fetch to the ScoreStore api here // insert the fetch to the ScoreStore api here
@ -225,13 +237,13 @@ export default class extends Aview {
} }
} }
const gameLoop = (timestamp: number) => { const gameLoop = async (timestamp: number) => {
elapsed = (timestamp - start) / 1000; elapsed = (timestamp - start) / 1000;
start = timestamp; start = timestamp;
if (game_playing) if (game_playing)
{ {
movePaddles(); movePaddles();
moveBall(); await moveBall();
} }
draw(); draw();
if (!game_playing) if (!game_playing)
@ -257,13 +269,26 @@ export default class extends Aview {
p2_input.value = "Player 2"; p2_input.value = "Player 2";
if (await isLogged()) if (await isLogged())
p1_input.value = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2]; {
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "GET",
credentials: "include",
});
if (userdata_req.status == 404)
{
console.error("invalid user");
return ;
}
let userdata = await userdata_req.json();
p1_input.value = userdata.displayName;
}
else else
p1_input.value = "Player 1"; p1_input.value = "Player 1";
document.getElementById("game-start")?.addEventListener("click", () => { document.getElementById("game-start")?.addEventListener("click", () => {
p1_name = p1_input.value; p1_name = p1_input.value.length > 16 ? p1_input.value.substring(0, 16) + "." : p1_input.value;
p2_name = p2_input.value; p2_name = p2_input.value.length > 16 ? p2_input.value.substring(0, 16) + "." : p2_input.value;
document.getElementById("player-inputs").remove(); document.getElementById("player-inputs").remove();
canvas = document.createElement("canvas"); canvas = document.createElement("canvas");
@ -272,7 +297,7 @@ export default class extends Aview {
document.getElementById("main-div").prepend(canvas); document.getElementById("main-div").prepend(canvas);
ctx = canvas.getContext("2d"); ctx = canvas.getContext("2d", {alpha: false});
ctx.canvas.width = 600; ctx.canvas.width = 600;
ctx.canvas.height = 600; ctx.canvas.height = 600;

View File

@ -24,14 +24,30 @@ export default class extends Aview {
</div> </div>
</div> </div>
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border"> <div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 reverse-border flex flex-col items-center">
<h1 class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome back ! please login.</h1> <form method="dialog" class="space-y-4">
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input> <h1 class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome back ! please login.</h1>
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input> <input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p> <input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
</br> <p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p>
<button id="login-button" type="submit" class="default-button w-full">login</button> </br>
</form> <button id="login-button" type="submit" class="default-button w-full">login</button>
</form>
<hr class="my-4 w-64 reverse-border">
<div class="flex flex-col space-y-4 w-full">
<a target="_blank" href="http://localhost:3001/login/google" class="default-button inline-flex items-center justify-center w-full">
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg" height=20 width=20 class="mr-2 justify-self-start" />
login with John Google
</a>
<a target="_blank" href="http://localhost:3001/login/google" class="default-button inline-flex items-center justify-center w-full">
<img src="https://rusty.42angouleme.fr/assets/favicon-bb06adc80c8495db.ico" height=20 width=20 class="mr-2 justify-self-start" />
login with Rusty
</a>
</div>
</div>
</div> </div>
`; `;
} }
@ -49,7 +65,6 @@ export default class extends Aview {
credentials: "include", credentials: "include",
body: JSON.stringify({ user: username, password: password }), body: JSON.stringify({ user: username, password: password }),
}); });
const data = await data_req.json();
if (data_req.status === 200) if (data_req.status === 200)
{ {
@ -58,6 +73,7 @@ export default class extends Aview {
} }
else if (data_req.status === 400) else if (data_req.status === 400)
{ {
const data = await data_req.json();
document.getElementById("login-error-message").innerHTML = "error: " + data.error; document.getElementById("login-error-message").innerHTML = "error: " + data.error;
document.getElementById("login-error-message").classList.remove("hidden"); document.getElementById("login-error-message").classList.remove("hidden");
} }

View File

@ -1,29 +1,38 @@
import Aview from "./Aview.ts" import Aview from "./Aview.ts"
import { dragElement } from "./drag.ts";
import { setOnekoState } from "../oneko.ts"
import { isLogged, navigationManager } from "../main.ts" import { isLogged, navigationManager } from "../main.ts"
export default class extends Aview { export default class extends Aview {
constructor() constructor()
{ {
super(); super();
this.setTitle("profile"); this.setTitle("profile");
setOnekoState("default");
} }
async getHTML() { async getHTML() {
return ` return `
<div id="main-window" class="default-border shadow-2x1 bg-neutral-200 dark:bg-neutral-800"> <div id="window" class="absolute default-border">
<div class="flex flex-row items-stretch"> <div id="window-header" class="bg-linear-to-r from-orange-200 to-orange-300 flex flex-row min-w-75 justify-between px-2">
<div class="inline-block bg-linear-to-b from-orange-200 to-orange-300 min-h-84 w-6 relative"> <span class="font-[Kubasta]">profile.ts</span>
<!--div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">knl_meowscendence</div--> <div>
<div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">girls kissing :3</div> <button> - </button>
<button> □ </button>
<a href="/" data-link> × </a>
</div> </div>
</div>
<div class="flex flex-col items-center"> <div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
<div id="profile-items" class="flex flex-col items-center"> <div class="flex flex-col space-y-4 w-full">
<div id="profile-profile" class="default-border h-24 flex flex-row place-content-stretch content-center items-center space-x-6 pr-4">
</div> </div>
<div id="menu-bottom-div" class="hidden mt-auto flex flex-col items-center"> <div class="flex flex-row space-x-4 w-full min-w-145">
<hr class="my-2 w-32 reverse-border"> <ul id="profile-scorelist" class="reverse-border bg-neutral-300 dark:bg-neutral-900 h-48 w-full overflow-scroll no-scrollbar">
<button id="menu-logout" class="menu-default-button">logout</button> </ul>
<div id="graph-ig-idk-im-scared" class="reverse-border bg-neutral-300 dark:bg-neutral-900 h-48 w-full">
</div>
</div> </div>
</div> </div>
</div> </div>
@ -31,43 +40,15 @@ export default class extends Aview {
`; `;
} }
open: boolean = false; async run() {
if (!await isLogged())
navigationManager("/");
async run() { dragElement(document.getElementById("window"));
let uuid: String; let uuid: String;
if (this.open) uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
{
this.open = false;
document.getElementById("taskbar-menu").innerHTML = "";
return ;
}
this.open = true;
document.getElementById("taskbar-menu").innerHTML = await this.getHTML();
async function getMainHTML() { const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
if (!(await isLogged()))
{
document.getElementById("menu-bottom-div").classList.add("hidden");
return `
<a class="menu-default-button inline-flex items-center justify-center" href="/login" data-link>login</a>
<a class="menu-default-button inline-flex items-center justify-center" href="/register" data-link>register</a>
`;
}
document.getElementById("menu-bottom-div").classList.remove("hidden");
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
return `
<span class="menu-default-label inline-flex items-center justify-center">hi, ${uuid} !</span>
<hr class="my-2 w-32 reverse-border">
<button class="menu-default-button">profile</button>
<button class="menu-default-button">settings</button>
`;
}
document.getElementById("profile-items").innerHTML = await getMainHTML();
/*const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "GET", method: "GET",
credentials: "include", credentials: "include",
}); });
@ -78,28 +59,56 @@ export default class extends Aview {
} }
let userdata = await userdata_req.json(); let userdata = await userdata_req.json();
console.log(userdata_req);*/ const matchCount_req = await fetch(`http://localhost:3002/users/${uuid}/matchHistory/count`, {
method: "GET",
credentials: "include",
});
const matchCount = await matchCount_req.json();
/*const main = document.getElementById("profile-profile"); const matches_req = await fetch(`http://localhost:3002/users/${uuid}/matchHistory?iStart=0&iEnd=${matchCount.n_matches}`, {
const nametag = main.appendChild(document.createElement("span")); method: "GET",
credentials: "include",
});
const matches = await matches_req.json();
nametag.innerHTML = `Hiiiiii ${userdata.displayName} ! :D`; const main = document.getElementById("profile-scorelist");
if (!main)
return console.error("what");
console.log(matches);
if (matches.matchHistory)
{
for (let match of matches.matchHistory)
{
const newEntry = document.createElement("li");
newEntry.classList.add("m-2", "default-button", "bg-neutral-200", "dark:bg-neutral-800", "text-neutral-900", "dark:text-white");
newEntry.innerHTML = match.score.p1Score > match.score.p2Score ? `${match.score.p1} - winner` : `${match.score.p2} - winner`;
main.insertBefore(newEntry, main.firstChild);
console.log(match.tx);
}
}
const profile = document.getElementById("profile-profile");
if (!profile) return;
const picture = profile.appendChild(document.createElement("img"));
picture.src = "https://api.kanel.ovh/pp";
picture.classList.add("text-neutral-900", "dark:text-white", "center", "h-18", "w-18", "mx-3");
const nametag = profile.appendChild(document.createElement("div"));
nametag.innerHTML = `
<div class="text-lg">Hi ${userdata.displayName} ! :D</div>
<div class="italic">${uuid}<div>
`;
nametag.classList.add("text-neutral-900", "dark:text-white"); nametag.classList.add("text-neutral-900", "dark:text-white");
const winrate = main.appendChild(document.createElement("div")); const winrate = profile.appendChild(document.createElement("div"));
winrate.innerHTML = `wins: ${userdata.wins} | losses: ${userdata.losses} | winrate: ${userdata.wins / (userdata.wins + userdata.losses)}`; winrate.innerHTML = `
winrate.classList.add("text-neutral-900", "dark:text-white");*/ <div> wins: ${userdata.wins} </div>
//console.log(document.getElementById("menu-logout")); <div> losses: ${userdata.losses} </div>
document.getElementById("menu-logout").addEventListener("click", async () => { <div> winrate: ${Math.round(userdata.wins / (userdata.wins + userdata.losses) * 100)} % </div>
let req = await fetch("http://localhost:3001/logout", { `;
method: "GET", winrate.classList.add("text-neutral-900", "dark:text-white", "grow", "content-center");
credentials: "include", }
});
if (req.status === 200)
this.run();
else
console.error("logout failed");
});
}
} }

View File

@ -0,0 +1,86 @@
import Aview from "./Aview.ts"
import { isLogged, navigationManager } from "../main.ts"
export default class extends Aview {
async getHTML() {
return `
<div id="main-window" class="default-border shadow-2x1 bg-neutral-200 dark:bg-neutral-800">
<div class="flex flex-row items-stretch">
<div class="inline-block bg-linear-to-b from-orange-200 to-orange-300 min-h-84 w-6 relative">
<!--div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">knl_meowscendence</div-->
<div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">girls kissing :3</div>
</div>
<div class="flex flex-col items-center">
<div id="profile-items" class="flex flex-col items-center">
</div>
<div id="menu-bottom-div" class="hidden mt-auto flex flex-col items-center">
<hr class="my-2 w-32 reverse-border">
<button id="menu-logout" class="menu-default-button">logout</button>
</div>
</div>
</div>
</div>
`;
}
open: boolean = false;
async run() {
let uuid: String;
if (this.open)
{
this.open = false;
document.getElementById("taskbar-menu").innerHTML = "";
return ;
}
this.open = true;
document.getElementById("taskbar-menu").innerHTML = await this.getHTML();
async function getMainHTML() {
if (!(await isLogged()))
{
document.getElementById("menu-bottom-div").classList.add("hidden");
return `
<a class="menu-default-button inline-flex items-center justify-center" href="/login" data-link>login</a>
<a class="menu-default-button inline-flex items-center justify-center" href="/register" data-link>register</a>
`;
}
document.getElementById("menu-bottom-div").classList.remove("hidden");
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "GET",
credentials: "include",
});
if (userdata_req.status == 404)
{
console.error("invalid user");
return ;
}
let userdata = await userdata_req.json();
return `
<span class="menu-default-label inline-flex items-center justify-center">hi, ${ userdata.displayName.length > 8 ? userdata.displayName.substring(0, 8) + "." : userdata.displayName } !</span>
<hr class="my-2 w-32 reverse-border">
<a class="menu-default-button inline-flex items-center justify-center" href="/profile" data-link>profile</a>
<a class="menu-default-button inline-flex items-center justify-center" href="/settings" data-link>settings</a>
`;
}
document.getElementById("profile-items").innerHTML = await getMainHTML();
document.getElementById("menu-logout").addEventListener("click", async () => {
let req = await fetch("http://localhost:3001/logout", {
method: "GET",
credentials: "include",
});
if (req.status === 200)
this.run();
else
console.error("logout failed");
});
}
}

View File

@ -1,7 +1,7 @@
import Aview from "./Aview.ts" import Aview from "./Aview.ts"
import { dragElement } from "./drag.ts";
import { setOnekoState } from "../oneko.ts" import { setOnekoState } from "../oneko.ts"
import { isLogged, navigationManager } from "../main.ts" import { isLogged, navigationManager } from "../main.ts"
import { dragElement } from "./drag.ts";
export default class extends Aview { export default class extends Aview {
@ -24,14 +24,30 @@ export default class extends Aview {
</div> </div>
</div> </div>
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border"> <div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 reverse-border flex flex-col items-center">
<p class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome ! please register.</p> <form method="dialog" class="space-y-4">
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input> <p class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome ! please register.</p>
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input> <input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p> <input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
</br> <p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p>
<button id="register-button" type="submit" class="default-button w-full">register</button> </br>
</form> <button id="register-button" type="submit" class="default-button w-full">register</button>
</form>
<hr class="my-4 w-64 reverse-border">
<div class="flex flex-col space-y-4 w-full">
<a target="_blank" href="http://localhost:3001/register/google" class="default-button inline-flex items-center justify-center w-full">
<img src="https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg" height=20 width=20 class="mr-2 justify-self-start" />
register with John Google
</a>
<a target="_blank" href="https://rusty.42angouleme.fr/issues/all" class="default-button inline-flex items-center justify-center w-full">
<img src="https://rusty.42angouleme.fr/assets/favicon-bb06adc80c8495db.ico" height=20 width=20 class="mr-2 justify-self-start" />
register with Rusty
</a>
</div>
</div>
</div> </div>
`; `;
} }

View File

@ -0,0 +1,84 @@
import Aview from "./Aview.ts"
import { dragElement } from "./drag.ts";
import { setOnekoState } from "../oneko.ts"
import { isLogged, navigationManager } from "../main.ts"
export default class extends Aview {
constructor()
{
super();
this.setTitle("profile");
setOnekoState("default");
}
async getHTML() {
return `
<div id="window" class="absolute default-border">
<div id="window-header" class="bg-linear-to-r from-orange-200 to-orange-300 flex flex-row min-w-75 justify-between px-2">
<span class="font-[Kubasta]">settings.ts</span>
<div>
<button> - </button>
<button> □ </button>
<a href="/" data-link> × </a>
</div>
</div>
<div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
<input type="text" id="displayName-input" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<button id="displayName-button" type="submit" class="default-button w-full">change display name</button>
<button id="deleteAccount-button" type="submit" class="default-button w-full">delete your account</button>
</div>
</div>
`;
}
async run() {
if (!await isLogged())
navigationManager("/");
dragElement(document.getElementById("window"));
let uuid: String;
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "GET",
credentials: "include",
});
if (userdata_req.status == 404) {
console.error("invalid user");
return;
}
let userdata = await userdata_req.json();
(document.getElementById("displayName-input") as HTMLInputElement).placeholder = userdata.displayName;
(document.getElementById("displayName-input") as HTMLInputElement).value = userdata.displayName;
document.getElementById("displayName-button")?.addEventListener("click", async () => {
const changeDisplayName_req = await fetch(`http://localhost:3002/users/${uuid}/displayName`, {
method: "PATCH",
headers: { "Content-Type": "application/json", },
credentials: "include",
body: JSON.stringify({ displayName: (document.getElementById("displayName-input") as HTMLInputElement).value })
});
if (changeDisplayName_req.status == 200) {
// idk display success
}
else {
// display error ig, uuuh it's in await changeDisplayName.json().error
}
});
document.getElementById("deleteAccount-button")?.addEventListener("click", async () => {
const delete_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "DELETE",
credentials: "include",
});
if (delete_req.status == 200)
navigationManager("/");
else
console.error("xd"); // should never happen, wtf
});
}
}

View File

@ -1,5 +1,6 @@
import Aview from "./Aview.ts"; import Aview from "./Aview.ts";
import { dragElement } from "./drag.js"; import { dragElement } from "./drag.js";
import { setOnekoState, setBallPos, setOnekoOffset } from "../oneko.ts"
export default class extends Aview { export default class extends Aview {
running: boolean; running: boolean;
@ -7,6 +8,7 @@ export default class extends Aview {
constructor() { constructor() {
super(); super();
this.setTitle("tetris (local match)"); this.setTitle("tetris (local match)");
setOnekoState("tetris");
this.running = true; this.running = true;
} }
@ -274,33 +276,42 @@ export default class extends Aview {
canvas: HTMLCanvasElement | null; canvas: HTMLCanvasElement | null;
holdCanvas: HTMLCanvasElement | null; holdCanvas: HTMLCanvasElement | null;
queueCanvas: HTMLCanvasElement | null; queueCanvas: HTMLCanvasElement | null;
ctx: CanvasRenderingContext2D; ctx: CanvasRenderingContext2D | null;
holdCtx: CanvasRenderingContext2D; holdCtx: CanvasRenderingContext2D | null;
queueCtx: CanvasRenderingContext2D; queueCtx: CanvasRenderingContext2D | null;
piece: Piece | null = null; piece: Piece | null = null;
holdPiece: Piece | null = null; holdPiece: Piece | null = null;
canHold: boolean = true; canHold: boolean = true;
nextQueue: string[] = []; nextQueue: string[] = [];
score = 0; score: number = 0;
level = 1; level: number = 1;
lines = 0; lines: number = 0;
dropInterval = 1000; dropInterval: number = 1000;
lastDrop = 0; lastDrop: number = 0;
isGameOver = false; isLocking: boolean = false;
isPaused = false; lockRotationCount: number = 0;
lockLastRotationCount: number = 0;
isGameOver: boolean = false;
isPaused: boolean = false;
constructor(canvasId: string) { constructor(canvasId: string) {
const el = document.getElementById( const el = document.getElementById(
canvasId, canvasId,
) as HTMLCanvasElement | null; ) as HTMLCanvasElement | null;
this.canvas = el; this.canvas = el;
if (!this.canvas)
throw console.error("no canvas :c");
this.canvas.width = COLS * BLOCK; this.canvas.width = COLS * BLOCK;
this.canvas.height = ROWS * BLOCK; this.canvas.height = ROWS * BLOCK;
const ctx = this.canvas.getContext("2d"); const ctx = this.canvas.getContext("2d");
this.ctx = ctx; this.ctx = ctx;
if (!this.ctx)
throw console.error("no ctx D:");
this.holdCanvas = document.getElementById("hold"); this.holdCanvas = document.getElementById("hold") as HTMLCanvasElement;
this.queueCanvas = document.getElementById("queue"); this.queueCanvas = document.getElementById("queue") as HTMLCanvasElement;
if (!this.holdCanvas || !this.queueCanvas)
throw console.error("no canvas :c");
this.holdCtx = this.holdCanvas.getContext("2d"); this.holdCtx = this.holdCanvas.getContext("2d");
this.queueCtx = this.queueCanvas.getContext("2d"); this.queueCtx = this.queueCanvas.getContext("2d");
@ -337,6 +348,7 @@ export default class extends Aview {
[this.piece, this.holdPiece] = [this.holdPiece, this.piece]; [this.piece, this.holdPiece] = [this.holdPiece, this.piece];
if (!this.piece) this.spawnPiece(); if (!this.piece) this.spawnPiece();
if (!this.piece) return;
this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2); this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2);
this.piece.y = -2; this.piece.y = -2;
@ -352,7 +364,6 @@ export default class extends Aview {
if (this.nextQueue.length < 7) this.fillBag(); if (this.nextQueue.length < 7) this.fillBag();
const type = this.nextQueue.shift()!; const type = this.nextQueue.shift()!;
this.piece = new Piece(type); this.piece = new Piece(type);
// If spawn collides immediately -> game over
if (this.collides(this.piece)) { if (this.collides(this.piece)) {
this.isGameOver = true; this.isGameOver = true;
} }
@ -374,12 +385,11 @@ export default class extends Aview {
let y: number = 0; let y: number = 0;
while (true) { while (true) {
for (const cell of piece.getCells()) { for (const cell of piece.getCells()) {
console.log(cell.y + y);
if ( if (
cell.y + y >= ROWS || cell.y + y >= ROWS ||
(cell.y + y >= 0 && this.board[cell.y + y][cell.x]) (cell.y + y >= 0 && this.board[cell.y + y][cell.x])
) )
return y - 1; return y - 1;
} }
y++; y++;
@ -388,11 +398,12 @@ export default class extends Aview {
lockPiece() { lockPiece() {
if (!this.piece) return; if (!this.piece) return;
this.isLocking = false;
let isValid: boolean = false; let isValid: boolean = false;
for (const cell of this.piece.getCells()) { for (const cell of this.piece.getCells()) {
if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS) if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS)
this.board[cell.y][cell.x] = cell.val; this.board[cell.y][cell.x] = cell.val;
if (cell.y < 20) isValid = true; if (cell.y > 0) isValid = true;
} }
if (!isValid) this.isGameOver = true; if (!isValid) this.isGameOver = true;
@ -427,6 +438,8 @@ export default class extends Aview {
rotatePiece(dir: "cw" | "ccw") { rotatePiece(dir: "cw" | "ccw") {
if (!this.piece) return; if (!this.piece) return;
if (this.isLocking && this.lockRotationCount < 15)
this.lockRotationCount++;
// Try rotation with wall kicks // Try rotation with wall kicks
const originalIndex = this.piece.rotationIndex; const originalIndex = this.piece.rotationIndex;
if (dir === "cw") this.piece.rotateCW(); if (dir === "cw") this.piece.rotateCW();
@ -446,6 +459,7 @@ export default class extends Aview {
if (!this.piece) return; if (!this.piece) return;
this.piece.x += dx; this.piece.x += dx;
this.piece.y += dy; this.piece.y += dy;
if (this.collides(this.piece)) { if (this.collides(this.piece)) {
this.piece.x -= dx; this.piece.x -= dx;
this.piece.y -= dy; this.piece.y -= dy;
@ -470,6 +484,26 @@ export default class extends Aview {
} }
keys: Record<string, boolean> = {}; keys: Record<string, boolean> = {};
direction: number = 0;
inputDelay = 200;
inputTimestamp = Date.now();
move: boolean = false;
inputManager() {
if (this.move || Date.now() > this.inputTimestamp + this.inputDelay)
{
if (this.keys["ArrowLeft"] && !this.keys["ArrowRight"])
this.movePiece(-1, 0);
else if (!this.keys["ArrowLeft"] && this.keys["ArrowRight"])
this.movePiece(1, 0);
else if (this.keys["ArrowLeft"] && this.keys["ArrowRight"])
this.movePiece(this.direction, 0);
this.move = false;
}
/*if (this.keys["ArrowDown"])
this.softDrop();*/
}
registerListeners() { registerListeners() {
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
@ -482,8 +516,18 @@ export default class extends Aview {
if (this.isPaused) return; if (this.isPaused) return;
if (e.key === "ArrowLeft") this.movePiece(-1, 0); if (e.key === "ArrowLeft")
else if (e.key === "ArrowRight") this.movePiece(1, 0); {
this.inputTimestamp = Date.now();
this.direction = -1;//this.movePiece(-1, 0);
this.move = true;
}
else if (e.key === "ArrowRight")
{
this.inputTimestamp = Date.now();
this.direction = 1;//this.movePiece(1, 0);
this.move = true;
}
else if (e.key === "ArrowDown") this.softDrop(); else if (e.key === "ArrowDown") this.softDrop();
else if (e.code === "Space") { else if (e.code === "Space") {
e.preventDefault(); e.preventDefault();
@ -504,9 +548,25 @@ export default class extends Aview {
loop(timestamp: number) { loop(timestamp: number) {
if (!this.lastDrop) this.lastDrop = timestamp; if (!this.lastDrop) this.lastDrop = timestamp;
if (!this.isPaused && !this.isGameOver) { if (!this.isPaused && !this.isGameOver)
if (timestamp - this.lastDrop > this.dropInterval) { {
if (!this.movePiece(0, 1)) this.lockPiece(); this.inputManager();
if (this.isLocking ? timestamp - this.lastDrop > 500 : timestamp - this.lastDrop > this.dropInterval)
{
if (this.isLocking && this.lockRotationCount == this.lockLastRotationCount)
this.lockPiece();
this.lockLastRotationCount = this.lockRotationCount;
if (!this.movePiece(0, 1))
{
if (!this.isLocking)
{
this.lockRotationCount = 0;
this.lockLastRotationCount = 0;
this.isLocking = true;
}
}
else if (this.isLocking)
this.lockRotationCount = 0;
this.lastDrop = timestamp; this.lastDrop = timestamp;
} }
} }
@ -516,6 +576,8 @@ export default class extends Aview {
drawGrid() { drawGrid() {
const ctx = this.ctx; const ctx = this.ctx;
if (!ctx || !this.canvas)
return;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.strokeStyle = "#222"; ctx.strokeStyle = "#222";
for (let r = 0; r <= ROWS; r++) { for (let r = 0; r <= ROWS; r++) {
@ -556,7 +618,7 @@ export default class extends Aview {
} }
drawHold() { drawHold() {
if (!this.holdPiece) return; if (!this.holdPiece || !this.holdCtx) return;
this.holdCtx.clearRect(0, 0, 200, 200); this.holdCtx.clearRect(0, 0, 200, 200);
let y: number = 0; let y: number = 0;
@ -567,6 +629,7 @@ export default class extends Aview {
this.holdCtx.fillStyle = this.canHold this.holdCtx.fillStyle = this.canHold
? COLORS[this.holdPiece.findColorIndex()] ? COLORS[this.holdPiece.findColorIndex()]
: "gray"; : "gray";
console.log(this.holdCtx.fillStyle);
this.holdCtx.fillRect( this.holdCtx.fillRect(
x * BLOCK + x * BLOCK +
1 + 1 +
@ -584,9 +647,9 @@ export default class extends Aview {
} }
drawQueue() { drawQueue() {
if (!this.queueCtx) return ;
this.queueCtx.clearRect(0, 0, 500, 500); this.queueCtx.clearRect(0, 0, 500, 500);
let placement: number = 0; let placement: number = 0;
console.log(this.nextQueue.slice(0, 5));
for (const nextPiece of this.nextQueue.slice(0, 5)) { for (const nextPiece of this.nextQueue.slice(0, 5)) {
let y: number = 0; let y: number = 0;
for (const row of TETROMINOES[nextPiece][0]) { for (const row of TETROMINOES[nextPiece][0]) {
@ -620,22 +683,26 @@ export default class extends Aview {
} }
fillBlock(x: number, y: number, color: string) { fillBlock(x: number, y: number, color: string) {
if (!this.ctx) return;
const ctx = this.ctx; const ctx = this.ctx;
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.fillRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2); ctx.fillRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
} }
fillGhostBlock(x: number, y: number, color: string) { fillGhostBlock(x: number, y: number, color: string) {
if (!this.ctx) return;
const ctx = this.ctx; const ctx = this.ctx;
ctx.strokeStyle = color; ctx.strokeStyle = color;
ctx.strokeRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2); ctx.strokeRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
} }
clearBlock(x: number, y: number) { clearBlock(x: number, y: number) {
if (!this.ctx) return;
const ctx = this.ctx; const ctx = this.ctx;
ctx.clearRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2); ctx.clearRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
} }
drawHUD() { drawHUD() {
if (!this.ctx || !this.canvas) return;
const ctx = this.ctx; const ctx = this.ctx;
ctx.fillStyle = "rgba(0,0,0,0.6)"; ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(4, 4, 120, 60); ctx.fillRect(4, 4, 120, 60);
@ -675,6 +742,7 @@ export default class extends Aview {
} }
draw() { draw() {
if (!this.ctx || !this.canvas) return;
// clear everything // clear everything
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
@ -698,6 +766,7 @@ export default class extends Aview {
this.drawBoard(); this.drawBoard();
this.drawPiece(); this.drawPiece();
this.drawHUD(); this.drawHUD();
this.drawQueue();
} }
} }