🏗️」 wip: kinda working friends menu

This commit is contained in:
2025-10-19 14:58:05 +02:00
12 changed files with 586 additions and 194 deletions

View File

@ -42,8 +42,8 @@
<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>
<div class="text-neutral-700 dark:text-neutral-400">|</div>
<a 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://rusty.42angouleme.fr/">rusty</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 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>

View File

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

View File

@ -1,6 +1,6 @@
import { oneko } from "./oneko.ts";
import Profile from "./views/Profile.ts";
let profile_view = new Profile;
import ProfileMenu from "./views/ProfileMenu.ts";
let profile_view = new ProfileMenu;
export async function isLogged(): Promise<boolean> {
let uuid_req = await fetch("http://localhost:3001/me", {
@ -40,7 +40,9 @@ const routes = [
{ path: "/login", view: () => import("./views/LoginPage.ts") },
{ path: "/register", view: () => import("./views/RegisterPage.ts") },
{ path: "/friends", view: () => import("./views/Friends.ts") }
{ path: "/friends", view: () => import("./views/Friends.ts") },
{ path: "/profile", view: () => import("./views/Profile.ts") },
{ path: "/settings", view: () => import("./views/Settings.ts") },
];
const router = async () => {
@ -97,7 +99,8 @@ document.addEventListener("DOMContentLoaded", () => {
oneko();
function updateClock() {
function updateClock()
{
const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const clock = document.getElementById("taskbar-clock");
@ -113,3 +116,5 @@ function updateClock() {
setInterval(updateClock, 5000);
updateClock();
oneko();

View File

@ -18,20 +18,23 @@ export function setOnekoState(state: string) {
default:
oneko_state = 0;
}
return;
}
export function setOnekoOffset() {
if (oneko_state == 1)
if (oneko_state != 0)
{
offsetX = document.getElementById("window").offsetLeft + 44;
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;
mousePosY = y + offsetY;
return;
}
export function oneko() {
@ -39,7 +42,7 @@ export function oneko() {
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return;
if (isReducedMotion) return ;
const nekoEl = document.createElement("div");
@ -141,15 +144,15 @@ export function oneko() {
{
mousePosX = event.clientX;
mousePosY = event.clientY;
}
}
});
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
if (!nekoEl.isConnected) {
return;

View File

@ -1,12 +1,13 @@
import Aview from "./Aview.ts"
import { setOnekoState } from "../oneko.ts"
import { dragElement } from "./drag.ts";
import { isLogged, navigationManager } from "../main.ts"
export default class extends Aview {
constructor() {
super();
this.setTitle("Friends list");
this.setTitle("friends list");
setOnekoState("default");
}
@ -26,22 +27,47 @@ export default class extends Aview {
<form method="dialog" 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="new-friend" placeholder="new friend" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<button id="add-friends-button" type="submit" class="default-button w-full">add friend</button>
<button id="rm-friends-button" class="default-button w-full">remove friend</button>
<p id="add-friend-err" class="hidden text-red-700 dark:text-red-500"></p>
<p id="add-friend-msg" class="hidden text-gray-900 dark:text-white text-lg"></p>
</form>
<p id="friends_n" class="hidden text-gray-900 dark:text-white text-lg"></p>
<p id="friends-error-message" class="hidden text-red-700 dark:text-red-500"></p>
<ul id="friends_list" class="hidden text-gray-900 dark:text-white text-lg">
</ul>
<p id="friend-msg" class="hidden text-gray-900 dark:text-white text-lg"></p>
<div class="flex flex-row space-x-4 w-full min-w-145">
<ul id="friends_list" class="reverse-border space-y-2 hidden text-gray-900 dark:text-white text-lg overflow-scroll h-48 w-full">
</ul>
</div>
</div>
</div>
`;
}
async run() {
if (!await isLogged())
navigationManager("/");
dragElement(document.getElementById("window"));
let uuid: String;
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
const n_friends = (document.getElementById("friends_n") as HTMLParagraphElement);
const friends_error_message = (document.getElementById("friends-error-message") as HTMLParagraphElement);
const friends_message = (document.getElementById("friends-msg") as HTMLParagraphElement)
const friends_list = (document.getElementById("friends_list") as HTMLUListElement);
const new_friend = (document.getElementById("new-friend") as HTMLInputElement);
const add_friend_err = (document.getElementById("add-friend-err") as HTMLParagraphElement);
const add_friend_msg = (document.getElementById("add-friend-msg") as HTMLParagraphElement);
async function removeFriend(name) {
const data_req = await fetch("http://localhost:3002/users/" + uuid + "/friends/" + name, {
method: "DELETE",
credentials: "include",
});
if (data_req.status === 200) {
console.log("friends removed success fully");
} else {
console.log("could not remove friend");
}
}
const list_friends = async () => {
const data_req = await fetch("http://localhost:3002/users/" + uuid + "/friends/count", {
@ -54,7 +80,7 @@ export default class extends Aview {
let data = await data_req.json();
if (data.n_friends > 0) {
const list_req = await fetch("http://localhost:3002/users/" + uuid + "/friends?iStart=0&iEnd=20", {
const list_req = await fetch("http://localhost:3002/users/" + uuid + "/friends?iStart=0&iEnd=2147483647", {
method: "GET",
headers: {
"Content-Type": "application/json",
@ -70,8 +96,21 @@ export default class extends Aview {
let list = (await list_req.json()).friends as JSON;
for (let i = 0; i < data.n_friends; i++) {
let new_friends = document.createElement('li')
new_friends.innerHTML = "- " + list[i].friendName;
let new_friends = document.createElement('li');
const span = document.createElement('span');
span.textContent = list[i].friendName;
const but = document.createElement('button');
but.textContent = "-";
but.className = "bg-red-500 text-white px-2 py-0 rounded hover:bg-red-600";
but.onclick = function() {
removeFriend(list[i].friendName);
list_friends()
}
new_friends.appendChild(span);
new_friends.appendChild(but);
friends_list.appendChild(new_friends);
}
} else {
@ -80,16 +119,13 @@ export default class extends Aview {
friends_error_message.classList.remove("hidden");
}
} else {
friends_error_message.innerHTML = "failed to fetch friends";
friends_error_message.classList.remove("hidden");
friends_message.innerHTML = "you have no friends D:";
friends_message.classList.remove("hidden");
}
}
const add_friend = async () => {
const new_friend = (document.getElementById("new-friend") as HTMLInputElement);
const add_friend_err = (document.getElementById("add-friend-err") as HTMLParagraphElement);
const add_friend_msg = (document.getElementById("add-friend-msg") as HTMLParagraphElement);
const data_req = await fetch("http://localhost:3002/users/" + uuid + "/friends/" + new_friend.value, {
method: "POST",
credentials: "include",
@ -105,39 +141,13 @@ export default class extends Aview {
add_friend_err.classList.remove("hidden");
if (!add_friend_msg.classList.contains("hidden"))
add_friend_msg.classList.add("hidden")
list_friends()
return
}
list_friends()
new_friend.value = '';
}
const rm_friend = async () => {
const new_friend = (document.getElementById("new-friend") as HTMLInputElement);
const add_friend_err = (document.getElementById("add-friend-err") as HTMLParagraphElement);
const add_friend_msg = (document.getElementById("add-friend-msg") as HTMLParagraphElement);
const data_req = await fetch("http://localhost:3002/users/" + uuid + "/friends/" + new_friend.value, {
method: "DELETE",
credentials: "include",
});
let data = await data_req.json()
if (data_req.status === 200) {
add_friend_msg.innerHTML = data.msg;
add_friend_msg.classList.remove("hidden");
if (!add_friend_err.classList.contains("hidden"))
add_friend_err.classList.add("hidden")
} else {
add_friend_err.innerHTML = data.error;
add_friend_err.classList.remove("hidden");
if (!add_friend_msg.classList.contains("hidden"))
add_friend_msg.classList.add("hidden")
}
list_friends()
}
dragElement(document.getElementById("window"));
const n_friends = (document.getElementById("friends_n") as HTMLParagraphElement);
const friends_error_message = (document.getElementById("friends-error-message") as HTMLParagraphElement);
const friends_list = (document.getElementById("friends_list") as HTMLUListElement);
try {
const data_req = await fetch("http://localhost:3002/users/" + uuid + "/friends/count", {
method: "GET",
@ -158,6 +168,5 @@ export default class extends Aview {
friends_error_message.classList.remove("hidden");
}
document.getElementById("add-friends-button")?.addEventListener("click", add_friend);
document.getElementById("rm-friends-button")?.addEventListener("click", rm_friend);
}
}

View File

@ -4,7 +4,7 @@ import { dragElement } from "./drag.js"
import { setOnekoState, setBallPos, setOnekoOffset } from "../oneko.ts"
export default class extends Aview {
running: boolean;
constructor()
@ -27,7 +27,7 @@ export default class extends Aview {
</div>
</div>
<div id="main-div" class="bg-neutral-200 dark:bg-neutral-800 text-center p-10 space-y-4 reverse-border">
<div id="player-inputs" class="flex flex-col space-y-4">
<div class="flex flex-row">
@ -46,6 +46,7 @@ export default class extends Aview {
async run() {
dragElement(document.getElementById("window"));
let uuid: string;
let start: number = 0;
let elapsed: number;
@ -103,7 +104,7 @@ export default class extends Aview {
ballSpeedY = ballSpeed * Math.sin(theta);
}
function moveBall() {
async function moveBall() {
let length = Math.sqrt(ballSpeedX * ballSpeedX + ballSpeedY * ballSpeedY);
let scale = ballSpeed / length;
ballX += (ballSpeedX * scale) * elapsed;
@ -142,6 +143,17 @@ export default class extends Aview {
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
@ -225,13 +237,13 @@ export default class extends Aview {
}
}
const gameLoop = (timestamp: number) => {
const gameLoop = async (timestamp: number) => {
elapsed = (timestamp - start) / 1000;
start = timestamp;
if (game_playing)
{
movePaddles();
moveBall();
await moveBall();
}
draw();
if (!game_playing)
@ -257,13 +269,26 @@ export default class extends Aview {
p2_input.value = "Player 2";
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
p1_input.value = "Player 1";
document.getElementById("game-start")?.addEventListener("click", () => {
p1_name = p1_input.value;
p2_name = p2_input.value;
p1_name = p1_input.value.length > 16 ? p1_input.value.substring(0, 16) + "." : p1_input.value;
p2_name = p2_input.value.length > 16 ? p2_input.value.substring(0, 16) + "." : p2_input.value;
document.getElementById("player-inputs").remove();
canvas = document.createElement("canvas");
@ -272,7 +297,7 @@ export default class extends Aview {
document.getElementById("main-div").prepend(canvas);
ctx = canvas.getContext("2d");
ctx = canvas.getContext("2d", {alpha: false});
ctx.canvas.width = 600;
ctx.canvas.height = 600;

View File

@ -24,14 +24,30 @@ export default class extends Aview {
</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">
<h1 class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome back ! please login.</h1>
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<input type="password" id="password" placeholder="password" 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>
</br>
<button id="login-button" type="submit" class="default-button w-full">login</button>
</form>
<div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 reverse-border flex flex-col items-center">
<form method="dialog" class="space-y-4">
<h1 class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome back ! please login.</h1>
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<input type="password" id="password" placeholder="password" 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>
</br>
<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>
`;
}
@ -49,7 +65,6 @@ export default class extends Aview {
credentials: "include",
body: JSON.stringify({ user: username, password: password }),
});
const data = await data_req.json();
if (data_req.status === 200)
{
@ -58,6 +73,7 @@ export default class extends Aview {
}
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").classList.remove("hidden");
}

View File

@ -1,29 +1,38 @@
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="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 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]">profile.ts</span>
<div>
<button> - </button>
<button> □ </button>
<a href="/" data-link> × </a>
</div>
<div class="flex flex-col items-center">
<div id="profile-items" class="flex flex-col items-center">
</div>
<div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
<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 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 class="flex flex-row space-x-4 w-full min-w-145">
<ul id="profile-scorelist" class="reverse-border bg-neutral-300 dark:bg-neutral-900 h-48 w-full overflow-scroll no-scrollbar">
</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>
@ -31,43 +40,15 @@ export default class extends Aview {
`;
}
open: boolean = false;
async run() {
if (!await isLogged())
navigationManager("/");
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();
dragElement(document.getElementById("window"));
let uuid: String;
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
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];
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}`, {
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
method: "GET",
credentials: "include",
});
@ -78,28 +59,56 @@ export default class extends Aview {
}
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 nametag = main.appendChild(document.createElement("span"));
const matches_req = await fetch(`http://localhost:3002/users/${uuid}/matchHistory?iStart=0&iEnd=${matchCount.n_matches}`, {
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");
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.classList.add("text-neutral-900", "dark:text-white");*/
//console.log(document.getElementById("menu-logout"));
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");
});
}
winrate.innerHTML = `
<div> wins: ${userdata.wins} </div>
<div> losses: ${userdata.losses} </div>
<div> winrate: ${Math.round(userdata.wins / (userdata.wins + userdata.losses) * 100)} % </div>
`;
winrate.classList.add("text-neutral-900", "dark:text-white", "grow", "content-center");
}
}

View File

@ -0,0 +1,87 @@
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>
<a class="menu-default-button inline-flex items-center justify-center" href="/friends" data-link>friends</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 { dragElement } from "./drag.ts";
import { setOnekoState } from "../oneko.ts"
import { isLogged, navigationManager } from "../main.ts"
import { dragElement } from "./drag.ts";
export default class extends Aview {
@ -24,14 +24,30 @@ export default class extends Aview {
</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">
<p class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome ! please register.</p>
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<input type="password" id="password" placeholder="password" 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>
</br>
<button id="register-button" type="submit" class="default-button w-full">register</button>
</form>
<div class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 reverse-border flex flex-col items-center">
<form method="dialog" class="space-y-4">
<p class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome ! please register.</p>
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
<input type="password" id="password" placeholder="password" 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>
</br>
<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>
`;
}

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 { dragElement } from "./drag.js";
import { setOnekoState, setBallPos, setOnekoOffset } from "../oneko.ts"
export default class extends Aview {
running: boolean;
@ -7,6 +8,7 @@ export default class extends Aview {
constructor() {
super();
this.setTitle("tetris (local match)");
setOnekoState("tetris");
this.running = true;
}
@ -211,14 +213,14 @@ export default class extends Aview {
};
const COLORS = [
"#000000", // placeholder for 0
"#00ffff", // I - cyan
"#0000ff", // J - blue
"#ff7f00", // L - orange
"#ffff00", // O - yellow
"#00ff00", // S - green
"#800080", // T - purple
"#ff0000", // Z - red
[ "#000000", "#000000" ] , // placeholder for 0
[ "#00d2e1", "#0080a8" ], // I - cyan
[ "#0092e9", "#001fbf" ], // J - blue
[ "#e79700", "#c75700" ], // L - orange
[ "#d8c800", "#8f7700" ], // O - yellow
[ "#59e000", "#038b00" ], // S - green
[ "#de1fdf", "#870087" ], // T - purple
[ "#f06600", "#c10d07" ], // Z - red
];
class Piece {
@ -274,33 +276,42 @@ export default class extends Aview {
canvas: HTMLCanvasElement | null;
holdCanvas: HTMLCanvasElement | null;
queueCanvas: HTMLCanvasElement | null;
ctx: CanvasRenderingContext2D;
holdCtx: CanvasRenderingContext2D;
queueCtx: CanvasRenderingContext2D;
ctx: CanvasRenderingContext2D | null;
holdCtx: CanvasRenderingContext2D | null;
queueCtx: CanvasRenderingContext2D | null;
piece: Piece | null = null;
holdPiece: Piece | null = null;
canHold: boolean = true;
nextQueue: string[] = [];
score = 0;
level = 1;
lines = 0;
dropInterval = 1000;
lastDrop = 0;
isGameOver = false;
isPaused = false;
score: number = 0;
level: number = 1;
lines: number = 0;
dropInterval: number = 1000;
lastDrop: number = 0;
isLocking: boolean = false;
lockRotationCount: number = 0;
lockLastRotationCount: number = 0;
isGameOver: boolean = false;
isPaused: boolean = false;
constructor(canvasId: string) {
const el = document.getElementById(
canvasId,
) as HTMLCanvasElement | null;
this.canvas = el;
if (!this.canvas)
throw console.error("no canvas :c");
this.canvas.width = COLS * BLOCK;
this.canvas.height = ROWS * BLOCK;
const ctx = this.canvas.getContext("2d");
this.ctx = ctx;
if (!this.ctx)
throw console.error("no ctx D:");
this.holdCanvas = document.getElementById("hold");
this.queueCanvas = document.getElementById("queue");
this.holdCanvas = document.getElementById("hold") as HTMLCanvasElement;
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.queueCtx = this.queueCanvas.getContext("2d");
@ -337,6 +348,7 @@ export default class extends Aview {
[this.piece, this.holdPiece] = [this.holdPiece, this.piece];
if (!this.piece) this.spawnPiece();
if (!this.piece) return;
this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2);
this.piece.y = -2;
@ -352,7 +364,6 @@ export default class extends Aview {
if (this.nextQueue.length < 7) this.fillBag();
const type = this.nextQueue.shift()!;
this.piece = new Piece(type);
// If spawn collides immediately -> game over
if (this.collides(this.piece)) {
this.isGameOver = true;
}
@ -374,12 +385,11 @@ export default class extends Aview {
let y: number = 0;
while (true) {
for (const cell of piece.getCells()) {
console.log(cell.y + y);
if (
cell.y + y >= ROWS ||
(cell.y + y >= 0 && this.board[cell.y + y][cell.x])
)
return y - 1;
return y - 1;
}
y++;
@ -388,11 +398,12 @@ export default class extends Aview {
lockPiece() {
if (!this.piece) return;
this.isLocking = false;
let isValid: boolean = false;
for (const cell of this.piece.getCells()) {
if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS)
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;
@ -427,6 +438,8 @@ export default class extends Aview {
rotatePiece(dir: "cw" | "ccw") {
if (!this.piece) return;
if (this.isLocking && this.lockRotationCount < 15)
this.lockRotationCount++;
// Try rotation with wall kicks
const originalIndex = this.piece.rotationIndex;
if (dir === "cw") this.piece.rotateCW();
@ -446,6 +459,7 @@ export default class extends Aview {
if (!this.piece) return;
this.piece.x += dx;
this.piece.y += dy;
if (this.collides(this.piece)) {
this.piece.x -= dx;
this.piece.y -= dy;
@ -470,6 +484,26 @@ export default class extends Aview {
}
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() {
window.addEventListener("keydown", (e) => {
@ -482,8 +516,18 @@ export default class extends Aview {
if (this.isPaused) return;
if (e.key === "ArrowLeft") this.movePiece(-1, 0);
else if (e.key === "ArrowRight") this.movePiece(1, 0);
if (e.key === "ArrowLeft")
{
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.code === "Space") {
e.preventDefault();
@ -504,18 +548,38 @@ export default class extends Aview {
loop(timestamp: number) {
if (!this.lastDrop) this.lastDrop = timestamp;
if (!this.isPaused && !this.isGameOver) {
if (timestamp - this.lastDrop > this.dropInterval) {
if (!this.movePiece(0, 1)) this.lockPiece();
if (!this.isPaused)
{
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.draw();
if (this.isGameOver)
return;
requestAnimationFrame(this.loop.bind(this));
}
drawGrid() {
const ctx = this.ctx;
if (!ctx || !this.canvas)
return;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.strokeStyle = "#222";
for (let r = 0; r <= ROWS; r++) {
@ -537,7 +601,7 @@ export default class extends Aview {
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const val = this.board[r][c];
if (val) this.fillBlock(c, r, COLORS[val]);
if (val) this.fillBlock(c, r, COLORS[val], this.ctx);
else this.clearBlock(c, r);
}
}
@ -547,16 +611,16 @@ export default class extends Aview {
if (!this.piece) return;
for (const cell of this.piece.getCells())
if (cell.y >= 0) this.fillBlock(cell.x, cell.y, COLORS[cell.val]);
if (cell.y >= 0) this.fillBlock(cell.x, cell.y, COLORS[cell.val], this.ctx);
let offset: number = this.getGhostOffset(this.piece);
for (const cell of this.piece.getCells())
if (cell.y + offset >= 0)
if (cell.y + offset >= 0 && offset > 0)
this.fillGhostBlock(cell.x, cell.y + offset, COLORS[cell.val]);
}
drawHold() {
if (!this.holdPiece) return;
if (!this.holdPiece || !this.holdCtx) return;
this.holdCtx.clearRect(0, 0, 200, 200);
let y: number = 0;
@ -564,9 +628,8 @@ export default class extends Aview {
let x: number = 0;
for (const val of row) {
if (val) {
this.holdCtx.fillStyle = this.canHold
? COLORS[this.holdPiece.findColorIndex()]
: "gray";
this.fillBlock(x, y, this.canHold ? COLORS[this.holdPiece.findColorIndex()] : ["#8c8c84", "#393934"], this.holdCtx);
/*this.holdCtx.fillStyle = ;
this.holdCtx.fillRect(
x * BLOCK +
1 +
@ -575,7 +638,7 @@ export default class extends Aview {
y * BLOCK + 1 + 20,
BLOCK - 2,
BLOCK - 2,
);
);*/
}
x++;
}
@ -584,9 +647,9 @@ export default class extends Aview {
}
drawQueue() {
if (!this.queueCtx) return ;
this.queueCtx.clearRect(0, 0, 500, 500);
let placement: number = 0;
console.log(this.nextQueue.slice(0, 5));
for (const nextPiece of this.nextQueue.slice(0, 5)) {
let y: number = 0;
for (const row of TETROMINOES[nextPiece][0]) {
@ -596,7 +659,7 @@ export default class extends Aview {
this.queueCtx.fillStyle =
COLORS[
["I", "J", "L", "O", "S", "T", "Z"].indexOf(nextPiece) + 1
];
][0];
this.queueCtx.fillRect(
x * BLOCK +
1 +
@ -619,23 +682,87 @@ export default class extends Aview {
}
}
fillBlock(x: number, y: number, color: string) {
const ctx = this.ctx;
ctx.fillStyle = color;
ctx.fillRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
adjustColor(hex: string, amount: number): string {
let color = hex.startsWith('#') ? hex.slice(1) : hex;
const num = parseInt(color, 16);
let r = (num >> 16) + amount;
let g = ((num >> 8) & 0x00FF) + amount;
let b = (num & 0x0000FF) + amount;
r = Math.max(Math.min(255, r), 0);
g = Math.max(Math.min(255, g), 0);
b = Math.max(Math.min(255, b), 0);
return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, '0')}`;
}
fillGhostBlock(x: number, y: number, color: string) {
fillBlock(x: number, y: number, color: string[], ctx) {
if (!ctx) return;
//const ctx = this.ctx;
//ctx.fillStyle = color;
const grad = ctx.createLinearGradient(x * BLOCK, y * BLOCK, x * BLOCK, y * BLOCK + BLOCK);
grad.addColorStop(0, color[0]);
grad.addColorStop(1, color[1]);
ctx.fillStyle = grad;
ctx.fillRect(x * BLOCK + 4, y * BLOCK + 4, BLOCK - 4, BLOCK - 4);
const X = x * BLOCK;
const Y = y * BLOCK;
const W = BLOCK;
const H = BLOCK;
const S = 4;
ctx.lineWidth = S;
ctx.beginPath();
ctx.strokeStyle = color[0];
ctx.moveTo(X, Y + S / 2);
ctx.lineTo(X + W, Y + S / 2);
ctx.moveTo(X + S / 2, Y);
ctx.lineTo(X + S / 2, Y + H);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = this.adjustColor(color[1], -20);
ctx.moveTo(X, Y + H - S / 2);
ctx.lineTo(X + W, Y + H - S / 2);
ctx.moveTo(X + W - S / 2, Y);
ctx.lineTo(X + W - S / 2, Y + H);
ctx.stroke();
}
fillGhostBlock(x: number, y: number, color: string[]) {
if (!this.ctx) return;
const ctx = this.ctx;
ctx.strokeStyle = color;
ctx.strokeRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
const X = x * BLOCK;
const Y = y * BLOCK;
const W = BLOCK;
const H = BLOCK;
const S = 4;
ctx.lineWidth = S;
ctx.beginPath();
ctx.strokeStyle = this.adjustColor(color[0], -40);
ctx.moveTo(X, Y + S / 2);
ctx.lineTo(X + W, Y + S / 2);
ctx.moveTo(X + S / 2, Y);
ctx.lineTo(X + S / 2, Y + H);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = this.adjustColor(color[1], -60);
ctx.moveTo(X, Y + H - S / 2);
ctx.lineTo(X + W, Y + H - S / 2);
ctx.moveTo(X + W - S / 2, Y);
ctx.lineTo(X + W - S / 2, Y + H);
ctx.stroke();
//ctx.strokeRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
}
clearBlock(x: number, y: number) {
if (!this.ctx) return;
const ctx = this.ctx;
ctx.clearRect(x * BLOCK + 1, y * BLOCK + 1, BLOCK - 2, BLOCK - 2);
}
drawHUD() {
if (!this.ctx || !this.canvas) return;
const ctx = this.ctx;
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(4, 4, 120, 60);
@ -675,6 +802,7 @@ export default class extends Aview {
}
draw() {
if (!this.ctx || !this.canvas) return;
// clear everything
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
@ -698,6 +826,7 @@ export default class extends Aview {
this.drawBoard();
this.drawPiece();
this.drawHUD();
this.drawQueue();
}
}