From cfac59282da55f4791d6352f15887a15e9ff6ec5 Mon Sep 17 00:00:00 2001 From: Wolfboy248 <121288977+Wolfboy248@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:51:25 +0200 Subject: Games page, maplist page (#153) Co-authored-by: Wolfboy248 <105884620+Wolfboy248@users.noreply.github.com> --- frontend/src/App.css | 25 + frontend/src/App.js | 9 +- frontend/src/components/main.css | 2 +- frontend/src/components/main.js | 3 +- frontend/src/components/news.css | 29 + frontend/src/components/news.js | 21 + frontend/src/components/pages/game.js | 46 ++ frontend/src/components/pages/games.css | 99 +++ frontend/src/components/pages/games.js | 58 ++ frontend/src/components/pages/home.css | 90 +++ frontend/src/components/pages/home.js | 198 +++++ frontend/src/components/pages/maplist.css | 399 ++++++++++ frontend/src/components/pages/maplist.js | 844 +++++++++++++++++++++ frontend/src/components/pages/summary.css | 4 +- frontend/src/components/pages/summary.js | 20 +- frontend/src/components/record.css | 15 + frontend/src/components/record.js | 57 ++ frontend/src/components/sidebar.css | 21 +- frontend/src/components/sidebar.js | 42 +- .../src/fonts/BarlowSemiCondensed-SemiBold.ttf | Bin 0 -> 104016 bytes frontend/src/index.js | 2 - 21 files changed, 1949 insertions(+), 35 deletions(-) create mode 100644 frontend/src/components/news.css create mode 100644 frontend/src/components/news.js create mode 100644 frontend/src/components/pages/game.js create mode 100644 frontend/src/components/pages/games.css create mode 100644 frontend/src/components/pages/games.js create mode 100644 frontend/src/components/pages/home.css create mode 100644 frontend/src/components/pages/home.js create mode 100644 frontend/src/components/pages/maplist.css create mode 100644 frontend/src/components/pages/maplist.js create mode 100644 frontend/src/components/record.css create mode 100644 frontend/src/components/record.js create mode 100644 frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf (limited to 'frontend/src') diff --git a/frontend/src/App.css b/frontend/src/App.css index c96f197..c43738b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -4,6 +4,26 @@ body { margin: 0; } +.loader { + animation: loader 1.2s ease infinite; + background-size: 400% 300%; + background-image: linear-gradient(-90deg, #202232 0%, #202232 25%, #2a2c41 50%, #202232 75%, #202232 100%); +} + +@keyframes loader { + 0% { + background-position: 100% 20%; + } + + 50% { + background-position: 0% 50%; + } + + 100% { + background-position: 0% 50%; + } +} + @font-face { font-family: 'BarlowCondensed-Bold'; src: local('BarlowCondensed-Bold'), url(./fonts/BarlowCondensed-Bold.ttf) format('truetype'); @@ -17,4 +37,9 @@ body { @font-face { font-family: 'BarlowSemiCondensed-Regular'; src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-Regular.ttf) format('truetype'); +} + +@font-face { + font-family: 'BarlowSemiCondensed-SemiBold'; + src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-SemiBold.ttf) format('truetype'); } \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index 93652e6..b7bfeb3 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -8,10 +8,13 @@ import "./App.css"; import Summary from "./components/pages/summary.js" import Profile from "./components/pages/profile.js" import About from './components/pages/about.js'; +import Games from "./components/pages/games.js"; +import Maplist from './components/pages/maplist.js'; +import Home from "./components/pages/maplist.js"; +import Homepage from './components/pages/home.js'; export default function App() { - const [token, setToken] = React.useState(null); const [mod,setMod] = React.useState(false) React.useEffect(()=>{ @@ -25,7 +28,7 @@ export default function App() { - }> + }> }> }> }> @@ -36,6 +39,8 @@ export default function App() { }> }> }> + }> + }> }> diff --git a/frontend/src/components/main.css b/frontend/src/components/main.css index 48e6379..9e9be86 100644 --- a/frontend/src/components/main.css +++ b/frontend/src/components/main.css @@ -10,7 +10,7 @@ main { padding-right: 30px; - font-size: 40px; + /* font-size: 40px; */ font-family: BarlowSemiCondensed-Regular; color: #cdcfdf; diff --git a/frontend/src/components/main.js b/frontend/src/components/main.js index 18b0f5a..b359105 100644 --- a/frontend/src/components/main.js +++ b/frontend/src/components/main.js @@ -2,6 +2,7 @@ import React from 'react'; import "../App.css" import "./main.css"; +import { Link } from 'react-router-dom'; export default function Main(props) { @@ -9,7 +10,7 @@ export default function Main(props) { return (

{props.text}

-
+ ) } diff --git a/frontend/src/components/news.css b/frontend/src/components/news.css new file mode 100644 index 0000000..102e9ba --- /dev/null +++ b/frontend/src/components/news.css @@ -0,0 +1,29 @@ +.news-container { + background-color: #2A2D40; + border-radius: 24px; + font-size: 18px; + overflow: hidden; + margin-bottom: 10px; +} + +.news-title { + padding: 20px; + font-family: BarlowSemiCondensed-SemiBold; + font-size: 22px; +} + +.news-description-div { + margin: 0px 20px; + padding: 8px 0px; +} + +.news-title-header { + background-color: #2B2E46; + padding: 10px 0px; +} + +.news-container>span { + font-size: 18px; + font-family: BarlowSemiCondensed-Regular; + line-height: 0px; +} \ No newline at end of file diff --git a/frontend/src/components/news.js b/frontend/src/components/news.js new file mode 100644 index 0000000..93e6be0 --- /dev/null +++ b/frontend/src/components/news.js @@ -0,0 +1,21 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; + +import "./news.css" + +export default function News({newsInfo}) { + // const { token } = prop + const [news, setNews] = React.useState(null); + const location = useLocation(); + + return ( +
+
+ {newsInfo.title} +
+
+ {newsInfo.short_description} +
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/pages/game.js b/frontend/src/components/pages/game.js new file mode 100644 index 0000000..017760b --- /dev/null +++ b/frontend/src/components/pages/game.js @@ -0,0 +1,46 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; + +import "./games.css" + +export default function GameEntry({ gameInfo }) { + const [gameEntry, setGameEntry] = React.useState(null); + const location = useLocation(); + + const gameInfoCats = gameInfo.category_portals; + + useEffect(() => { + gameInfoCats.forEach(catInfo => { + const itemBody = document.createElement("div"); + const itemTitle = document.createElement("span"); + const spacing = document.createElement("br"); + const itemNum = document.createElement("span"); + + itemTitle.innerText = catInfo.category.name; + itemNum.innerText = catInfo.portal_count; + itemTitle.classList.add("games-page-item-body-item-title"); + itemNum.classList.add("games-page-item-body-item-num"); + itemBody.appendChild(itemTitle); + itemBody.appendChild(spacing); + itemBody.appendChild(itemNum); + itemBody.className = "games-page-item-body-item"; + + // itemBody.innerHTML = ` + // ${catInfo.category.name}
+ // ${catInfo.portal_count}` + + document.getElementById(`${gameInfo.id}`).appendChild(itemBody); + }); + }) + + return ( +
+
+
+ {gameInfo.name} +
+
+
+
+ ) +} diff --git a/frontend/src/components/pages/games.css b/frontend/src/components/pages/games.css new file mode 100644 index 0000000..ec57a71 --- /dev/null +++ b/frontend/src/components/pages/games.css @@ -0,0 +1,99 @@ +.games-page { + position: absolute; + left: 320px; + color: white; + width: calc(100% - 320px); + height: 100%; + font-family: BarlowSemiCondensed-Regular; + color: #ffffff; + overflow-y: scroll; + scrollbar-width: thin; +} + +.games-page-item-content { + position: absolute; + left: 50px; + width: calc(100% - 100px); +} + +.games-page-item-content a { + color: inherit; +} + +.games-page-header { + margin-top: 50px; + margin-left: 50px; +} + +span>b { + font-size: 56px; + font-family: BarlowCondensed-Bold; +} + +.loader-game { + width: 100%; + height: 256px; + border-radius: 24px; + overflow: hidden; + margin: 25px 0px; +} + +.games-page-item { + width: 100%; + height: 256px; + background: #202232; + border-radius: 24px; + overflow: hidden; + margin: 25px 0px; +} + +.games-page-item-header { + width: 100%; + height: 50%; + background-size: cover; + overflow: hidden; +} + +.games-page-item-header-img { + width: 100%; + height: 100%; + backdrop-filter: blur(4px); + filter: blur(4px); + background-size: cover; +} + +.games-page-item-header span>b { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + transform: translateY(-100%); +} + +.games-page-item-body { + display: flex; + justify-content: center; + align-items: center; + height: 50%; +} + +.games-page-item-body-item { + background: #2B2E46; + text-align: center; + width: max-content; + width: calc(100% - 24px); + height: 100px; + border-radius: 24px; + color: #CDCFDF; + margin: 12px; +} + +.games-page-item-body-item-title { + margin-top: 0px; + font-size: 26px; +} + +.games-page-item-body-item-num { + font-size: 50px; + font-family: BarlowCondensed-Bold; +} \ No newline at end of file diff --git a/frontend/src/components/pages/games.js b/frontend/src/components/pages/games.js new file mode 100644 index 0000000..8c27151 --- /dev/null +++ b/frontend/src/components/pages/games.js @@ -0,0 +1,58 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; + +import "./games.css" +import GameEntry from './game'; + +export default function Games(prop) { + const { token } = prop; + const [games, setGames] = useState([]); + const location = useLocation(); + + useEffect(() => { + const fetchGames = async () => { + try { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + setGames(data.data); + pageLoad(); + } catch (err) { + console.error("Error fetching games:", err); + } + }; + + fetchGames(); + + function pageLoad() { + const loaders = document.querySelectorAll(".loader"); + loaders.forEach((loader) => { + loader.style.display = "none"; + }); + } + }, [token]); + + return ( +
+
+ Games list +
+ +
+
+
+
+
+ {games.map((game, index) => ( + + ))} +
+
+
+
+ ); +} diff --git a/frontend/src/components/pages/home.css b/frontend/src/components/pages/home.css new file mode 100644 index 0000000..072b12b --- /dev/null +++ b/frontend/src/components/pages/home.css @@ -0,0 +1,90 @@ +* { + scrollbar-width: thin; +} + +.homepage-panel { + background-color: #202232; + margin: 10px 10px; + padding: 10px; + border-radius: 24px; + overflow: hidden; + flex: 1 1 100%; + align-items: stretch; + width: 100%; +} + +.homepage-panel-title-div { + background-color: #2B2E46; + width: fit-content; + padding: 5px 18px; + border-radius: 200px; + font-family: BarlowSemiCondensed-SemiBold; + font-size: 32px; + margin-bottom: 10px; +} + +.stats-div { + background-color: #2B2E46; + border-radius: 24px; + text-align: center; + display: grid; + padding: 10px 0px; + width: 100%; +} + +.stats-div span { + font-family: BarlowSemiCondensed-Regular; + font-size: 18px; +} + +.stats-div span>b { + font-family: BarlowSemiCondensed-SemiBold; + font-size: 42px; +} + +.record-title div { + --padding: 20px; + width: calc(100% - calc(var(--padding * 2))); + height: 32px; + border-radius: 200px; + font-size: 18px; + display: grid; + grid-template-columns: calc(20% - 3.6px) calc(25% - 4.5px) calc(15% - 2.7px) calc(15% - 2.7px) calc(25% - 4.5px); + text-align: center; + padding: 0px var(--padding); + vertical-align: middle; + align-items: center; + font-family: BarlowSemiCondensed-SemiBold; +} + +.record-title::after { + content: ""; + display: flex; + width: 100%; + height: 3px; + background-color: #2B2E46; + margin-bottom: 5px; +} + +.recommended-map-img { + width: 250px; + border-radius: 24px; + margin-bottom: 0; + border: 7px solid #2B2E46; +} + +.difficulty-bar-home { + width: 100%; + display: grid; + grid-template-columns: 20% 20% 20% 20% 20%; + align-items: center; + margin: 5px; + margin-top: 16px; +} + +.difficulty-point { + background: #2B2E46; + height: 3px; + margin: 5px; + border-radius: 10px; +} diff --git a/frontend/src/components/pages/home.js b/frontend/src/components/pages/home.js new file mode 100644 index 0000000..c1477ab --- /dev/null +++ b/frontend/src/components/pages/home.js @@ -0,0 +1,198 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; + +import "./home.css" +import News from '../news'; +import Record from '../record'; + +export default function Homepage(prop) { + const {token, setToken} = prop + const [home, setHome] = React.useState(null); + const location = useLocation(); + + useEffect(() => { + async function fetchMapImg() { + try { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + + const recommendedMapImg = document.querySelector("#recommendedMapImg"); + + recommendedMapImg.src = `${data.data[0].image}` + + const column1 = document.querySelector("#column1"); + const column2 = document.querySelector("#column2"); + + column2.style.height = column1.clientHeight + "px"; + } catch (error) { + console.log(error) + } + } + + fetchMapImg() + + const panels = document.querySelectorAll(".homepage-panel"); + panels.forEach(e => { + // this is cuz react is silly + if (e.innerHTML.includes('
')) { + return + } + const title = e.getAttribute("title"); + + const titleDiv = document.createElement("div"); + const titleSpan = document.createElement("span"); + + titleDiv.classList.add("homepage-panel-title-div") + + titleSpan.innerText = title + + titleDiv.appendChild(titleSpan) + e.insertBefore(titleDiv, e.firstChild) + }); + }) + + const newsList = [ + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + { + "title": "Portal Saved on Container Ride", + "short_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vehicula facilisis quam, non ultrices nisl aliquam at. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas." + }, + ] + + return ( +
+
+ Welcome back,
+ Krzyhau +
+ +
+
+ {/* Column 1 */} +
+
+
+ Overall rank
+ #69 +
+
+ Singleplayer
+ #10 (60/62) +
+
+ Overall rank
+ #69 (13/37) +
+
+
+
+
+ +
+ Container Ride + Your Record: 4 portals + World Record: 2 portals +
+
+
+
+
+
+
+
+
+
+
+
+
+ Place + Runner + Portals + Time + Date +
+
+
+ + + + + + + +
+
+
+ {/* Column 2 */} +
+
+
+ {newsList.map((newsList, index) => ( + + ))} +
+
+
+
+ + + +
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/pages/maplist.css b/frontend/src/components/pages/maplist.css new file mode 100644 index 0000000..a63c4ec --- /dev/null +++ b/frontend/src/components/pages/maplist.css @@ -0,0 +1,399 @@ +.maplist-page { + position: relative; + left: 350px; + height: 100vh; + color: #cdcfdf; + width: calc(100% - 380px); + font-family: BarlowSemiCondensed-Regular; + overflow-y: scroll; + overflow-x: hidden; + padding-right: 30px; +} + +a { + color: inherit; + width: fit-content; +} + +.maplist-page-content { + position: absolute; + left: 0px; + width: calc(100% - 50px); +} + +.maplist-page-header { + margin-top: 20px; + display: grid; + margin-bottom: 10px; +} + +.nav-btn { + height: 40px; + background-color: #2b2e46; + color: inherit; + font-size: 18px; + font-family: inherit; + border: none; + border-radius: 20px; + transition: background-color .1s; + cursor: default; + width: fit-content; +} + +.nav-btn>span { + padding: 0 8px 0 8px; +} + +.nav-btn:hover { + background-color: #202232; + cursor: pointer; +} + +.game { + width: 100%; + height: 192px; + background: #202232; + border-radius: 24px; + overflow: hidden; +} + +.game-header { + width: 100%; + height: 144px; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.game-header-text { + display: flex; + justify-content: center; + align-items: center; + position: absolute; +} + +.game-img { + width: 100%; + height: 100%; + background-size: cover; + filter: blur(4px); +} + +.game-header-text>span { + font-size: 42px; + font-weight: 500; + margin: 5px; +} + +.game-header-text span>b { + font-size: 96px; + font-weight: 600; +} + +.game-nav { + display: flex; + height: 48px; +} + +.game-nav-btn { + width: 100%; + height: 100%; + border: none; + border-radius: 0px; + color: inherit; + font-family: inherit; + font-size: 22px; + background: #2B2E46; + transition: background-color .1s; + margin: 0 1px; + display: flex; + justify-content: center; + align-items: center; +} + +.game-nav-btn:hover, .selected { + background-color: #202232; + cursor: pointer; +} + +.gameview-nav { + margin-top: 20px; + display: flex; + height: 56px; + border-radius: 24px; + overflow: hidden; + background-color: #202232; +} + +.maplist { + width: 100%; + margin-top: 20px; + margin-bottom: 40px; +} + +.chapter-name { + font-size: 30px; +} + +.chapter-page-div { + display: flex; + justify-content: right; + transform: translateY(-30px); +} + +.chapter-page-div button { + background-color: #00000000; + border: 0; + cursor: pointer; + height: 30px; + padding: 0; + width: 30px; +} + +.chapter-page-div span { + color: #cdcfdf; + font-family: BarlowSemiCondensed-Regular; + font-size: 20px; +} + +.maplist-maps { + display: grid; + grid-template-columns: 25% 25% 25% 25%; + margin-top: 10px; + transform: translateY(-30px); +} + +.maplist-item { + background: #202232; + border-radius: 24px; + overflow: hidden; + margin: 10px 10px; + /* padding: 10px 15px; */ + cursor: pointer; + user-select: none; +} + +.loader-map { + border-radius: 24px; + overflow: hidden; + margin: 10px 10px; + /* padding: 10px 15px; */ + user-select: none; + width: calc(100% - 20px); + height: calc(223px); +} + +.maplist-img-div { + height: 150px; + overflow: hidden; +} + +.maplist-img { + width: 100%; + height: 100%; + background-size: cover; + filter: blur(4px); + opacity: 0.7; +} + +.maplist-portalcount-div { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + height: 100%; + transform: translateY(-100%); + overflow: hidden; +} + +.maplist-title { + font-size: 22px; + text-align: center; + width: 100%; + display: inherit; + padding: 5px 0px; + color: #CDCFDF; +} + +.maplist-portals { + margin-left: 5px; + font-size: 32px; +} + +.difficulty-div { + display: flex; + padding: 7px 10px; +} + +.difficulty-label { + font-size: 18px; +} + +.difficulty-bar { + width: 100%; + display: grid; + grid-template-columns: 20% 20% 20% 20% 20%; + align-items: center; + margin: 5px; +} + +.difficulty-point { + background: #2B2E46; + height: 3px; + margin: 5px; + border-radius: 10px; +} + +.stats { + margin-top: 30px; +} + +.portalcount-over-time-div { + width: 100%; + height: 450px; + position: relative; + background-color: #202232; + border-radius: 20px; +} + +.graph-title { + width: 100%; + display: inherit; + font-size: 24px; + margin-top: 5px; + text-align: center; + font-family: BarlowSemiCondensed-SemiBold; + padding-top: 7px; +} + +.portalcount-graph { + height: calc(100% - 30px); + width: calc(100% - 80px); +} + +.chart { + height: calc(100% - 80px); + width: 100%; + position: relative; + padding: 0px 0px; + scrollbar-width: thin; +} + +.line-chart { + list-style: none; + margin: 0; + padding: 0; + height: 100%; + border-bottom: 2px solid #2B2E46; +} + +.data-point { + background-color: #202232; + border: 4px solid #006FDE; + border-radius: 50%; + height: 6px; + position: absolute; + width: 6px; + bottom: calc(var(--y) - 4.5px); + left: calc(var(--x) - 6.5px); + transition: all 0.2s cubic-bezier(0.075, 0.82, 0.165, 1); + z-index: 1; + animation: point_intro 0.2s cubic-bezier(0.075, 0.82, 0.165, 1.8); + animation-fill-mode: backwards; +} + +.data-point:hover, .data-point-active { + background-color: #006FDE; + box-shadow: 0px 0px 10px #006FDE; +} + +.line-segment { + background-color: #006FDE; + bottom: var(--y); + height: 4px; + left: var(--x); + position: absolute; + transform: rotate(calc(var(--angle) * -1deg)); + width: calc(var(--hypotenuse) * 1px); + transform-origin: left bottom; + border-radius: 20px; + z-index: 1; + animation: line_intro 0.05s cubic-bezier(0, 1, 0.31, 0.96); + animation-fill-mode: backwards; +} + +#dataPointInfo { + position: absolute; + width: 400px; + height: 85px; + background: #202232; + box-shadow: 0px 4px 16px 0px #00000080; + transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); + z-index: 1000; + opacity: 0; + left: auto; + border-radius: 20px; + padding: 15px 7px; +} + +.section-header { + display: flex; + text-align: center; + font-family: BarlowSemiCondensed-SemiBold; + font-size: 18px; + height: 40%; + justify-content: space-evenly; + align-items: center; +} + +.section-header span, .section-data span { + flex: 1; +} + +.divider { + width: 100%; + height: 2px; + background-color: #2B2E46; + display: flex; + margin: 5px 0px 8px 0px; +} + +.section-data { + display: flex; + grid-template-columns: 25% 25% 25% 25%; + text-align: center; + background-color: #2B2E46; + height: 52%; + border-radius: 200px; + align-items: center; + justify-content: space-evenly; + flex-grow: 1; + font-family: BarlowSemiCondensed-Regular; + font-size: 18px; + padding: 0px 5px; +} + +@keyframes line_intro { + 0% { + width: 0; + } + 100% { + width: calc(var(--hypotenuse) * 1px); + } +} + +@keyframes point_intro { + 0% { + opacity: 0; + width: 0; + height: 0; + transform: translate(3px, -3px); + } + 100% { + width: 6px; + height: 6px; + transform: translate(0px, 0px); + opacity: 1; + } +} diff --git a/frontend/src/components/pages/maplist.js b/frontend/src/components/pages/maplist.js new file mode 100644 index 0000000..dca76d6 --- /dev/null +++ b/frontend/src/components/pages/maplist.js @@ -0,0 +1,844 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; +import { BrowserRouter as Router, Route, Routes, useNavigate } from 'react-router-dom'; + +import "./maplist.css" +import img5 from "../../imgs/5.png" +import img6 from "../../imgs/6.png" + +export default function Maplist(prop) { + const {token,setToken} = prop + const scrollRef = useRef(null) + const [games, setGames] = React.useState(null); + const location = useLocation(); + + let gameTitle; + let minPage; + let maxPage; + let currentPage; + let add = 0; + let gameState; + let catState = 0; + async function detectGame() { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + + const url = new URL(window.location.href) + + const params = new URLSearchParams(url.search) + gameState = parseFloat(params.get("game")) + + document.querySelector("#catPortalCount").innerText = data.data[gameState - 1].category_portals[0].portal_count; + + if (gameState == 1){ + gameTitle = data.data[0].name; + + maxPage = 9; + minPage = 1; + createCategories(1); + } else if (gameState == 2){ + gameTitle = data.data[1].name; + + maxPage = 16; + minPage = 10; + add = 10 + createCategories(2); + } + + let chapterParam = params.get("chapter") + + currentPage = minPage; + + if (chapterParam) { + currentPage = +chapterParam + add + } + + changePage(currentPage); + + // if (chapterParam) { + // document.querySelector("#pageNumbers").innerText = `${chapterParam - minPage + 1}/${maxPage - minPage + 1}` + // } + } + + function changeMaplistOrStatistics(index, name) { + const maplistBtns = document.querySelectorAll("#maplistBtn"); + maplistBtns.forEach((btn, i) => { + if (i == index) { + btn.className = "game-nav-btn selected" + + if (name == "maplist") { + document.querySelector(".stats").style.display = "none"; + document.querySelector(".maplist").style.display = "block"; + document.querySelector(".maplist").setAttribute("currentTab", "maplist"); + } else { + document.querySelector(".stats").style.display = "block"; + document.querySelector(".maplist").style.display = "none"; + + document.querySelector(".maplist-page").scrollTo({ top: 372, behavior: "smooth" }) + document.querySelector(".maplist").setAttribute("currentTab", "stats"); + } + } else { + btn.className = "game-nav-btn"; + } + }); + } + + async function createCategories(gameID) { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + let categoriesArr = data.data[gameID - 1].category_portals; + + const gameNav = document.querySelector(".game-nav"); + gameNav.innerHTML = ""; + categoriesArr.forEach((category) => { + createCategory(category); + }); + } + + let categoryNum = 0; + function createCategory(category) { + const gameNav = document.querySelector(".game-nav"); + + categoryNum++; + const gameNavBtn = document.createElement("button"); + if (categoryNum == 1) { + gameNavBtn.className = "game-nav-btn selected"; + } else { + gameNavBtn.className = "game-nav-btn"; + } + gameNavBtn.id = "catBtn" + gameNavBtn.innerText = category.category.name; + + gameNavBtn.addEventListener("click", (e) => { + changeCategory(category, e); + changePage(currentPage); + }) + + gameNav.appendChild(gameNavBtn); + } + + async function changeCategory(category, btn) { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + catState = category.category.id - 1; + console.log(catState) + const navBtns = document.querySelectorAll("#catBtn"); + navBtns.forEach((btns) => { + btns.classList.remove("selected"); + }); + + btn.srcElement.classList.add("selected"); + document.querySelector("#catPortalCount").innerText = category.portal_count; + } + + async function changePage(page) { + + const pageNumbers = document.querySelector("#pageNumbers"); + + pageNumbers.innerText = `${currentPage - minPage + 1}/${maxPage - minPage + 1}`; + + const maplistMaps = document.querySelector(".maplist-maps"); + maplistMaps.innerHTML = ""; + for (let index = 0; index < 8; index++) { + const loadingAnimation = document.createElement("div"); + loadingAnimation.classList.add("loader"); + loadingAnimation.classList.add("loader-map") + maplistMaps.appendChild(loadingAnimation); + } + const data = await fetchMaps(page); + const maps = data.data.maps; + const name = data.data.chapter.name; + + let chapterName = "Chapter"; + const chapterNumberOld = name.split(" - ")[0]; + let chapterNumber1 = chapterNumberOld.split("Chapter ")[1]; + if (chapterNumber1 == undefined) { + chapterName = "Course" + chapterNumber1 = chapterNumberOld.split("Course ")[1]; + } + const chapterNumber = chapterNumber1.toString().padStart(2, "0"); + const chapterTitle = name.split(" - ")[1]; + + const chapterNumberElement = document.querySelector(".chapter-num") + const chapterTitleElement = document.querySelector(".chapter-name") + chapterNumberElement.innerText = chapterName + " " + chapterNumber; + chapterTitleElement.innerText = chapterTitle; + + maplistMaps.innerHTML = ""; + maps.forEach(map => { + let portalCount; + if (map.category_portals[catState] != undefined) { + portalCount = map.category_portals[catState].portal_count; + } else { + portalCount = map.category_portals[0].portal_count; + } + addMap(map.name, portalCount, map.image, map.difficulty + 1, map.id); + }); + + const gameTitleElement = document.querySelector("#gameTitle"); + gameTitleElement.innerText = gameTitle; + + const url = new URL(window.location.href) + + const params = new URLSearchParams(url.search) + + let chapterParam = params.get("chapter") + + try { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + + const gameImg = document.querySelector(".game-img"); + + gameImg.style.backgroundImage = `url(${data.data[0].image})`; + + // const mapImg = document.querySelectorAll(".maplist-img"); + // mapImg.forEach((map) => { + // map.style.backgroundImage = `url(${data.data[0].image})`; + // }); + + } catch (error) { + console.log("error fetching games:", error); + } + + asignDifficulties(); + } + + async function addMap(mapName, mapPortalCount, mapImage, difficulty, mapID) { + // jesus christ + const maplistItem = document.createElement("div"); + const maplistTitle = document.createElement("span"); + const maplistImgDiv = document.createElement("div"); + const maplistImg = document.createElement("div"); + const maplistPortalcountDiv = document.createElement("div"); + const maplistPortalcount = document.createElement("span"); + const b = document.createElement("b"); + const maplistPortalcountPortals = document.createElement("span"); + const difficultyDiv = document.createElement("div"); + const difficultyLabel = document.createElement("span"); + const difficultyBar = document.createElement("div"); + const difficultyPoint1 = document.createElement("div"); + const difficultyPoint2 = document.createElement("div"); + const difficultyPoint3 = document.createElement("div"); + const difficultyPoint4 = document.createElement("div"); + const difficultyPoint5 = document.createElement("div"); + + maplistItem.className = "maplist-item"; + maplistTitle.className = "maplist-title"; + maplistImgDiv.className = "maplist-img-div"; + maplistImg.className = "maplist-img"; + maplistPortalcountDiv.className = "maplist-portalcount-div"; + maplistPortalcount.className = "maplist-portalcount"; + maplistPortalcountPortals.className = "maplist-portals"; + difficultyDiv.className = "difficulty-div"; + difficultyLabel.className = "difficulty-label"; + difficultyBar.className = "difficulty-bar"; + difficultyPoint1.className = "difficulty-point"; + difficultyPoint2.className = "difficulty-point"; + difficultyPoint3.className = "difficulty-point"; + difficultyPoint4.className = "difficulty-point"; + difficultyPoint5.className = "difficulty-point"; + + + maplistTitle.innerText = mapName; + difficultyLabel.innerText = "Difficulty: " + maplistPortalcountPortals.innerText = "portals" + b.innerText = mapPortalCount; + maplistImg.style.backgroundImage = `url(${mapImage})`; + difficultyBar.setAttribute("difficulty", difficulty) + maplistItem.setAttribute("id", mapID) + maplistItem.addEventListener("click", () => { + console.log(mapID) + window.location.href = "/maps/" + mapID + }) + + // appends + // maplist item + maplistItem.appendChild(maplistTitle); + maplistImgDiv.appendChild(maplistImg); + maplistImgDiv.appendChild(maplistPortalcountDiv); + maplistPortalcountDiv.appendChild(maplistPortalcount); + maplistPortalcount.appendChild(b); + maplistPortalcountDiv.appendChild(maplistPortalcountPortals); + maplistItem.appendChild(maplistImgDiv); + maplistItem.appendChild(difficultyDiv); + difficultyDiv.appendChild(difficultyLabel); + difficultyDiv.appendChild(difficultyBar); + difficultyBar.appendChild(difficultyPoint1); + difficultyBar.appendChild(difficultyPoint2); + difficultyBar.appendChild(difficultyPoint3); + difficultyBar.appendChild(difficultyPoint4); + difficultyBar.appendChild(difficultyPoint5); + + // display in place + const maplistMaps = document.querySelector(".maplist-maps"); + maplistMaps.appendChild(maplistItem); + } + + async function fetchMaps(chapterID) { + try{ + const response = await fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapterID}`, { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + return data; + } catch (err) { + console.log(err) + } + } + + // difficulty stuff + function asignDifficulties() { + const difficulties = document.querySelectorAll(".difficulty-bar"); + difficulties.forEach((difficultyElement) => { + let difficulty = difficultyElement.getAttribute("difficulty"); + if (difficulty == "1") { + difficultyElement.childNodes[0].style.backgroundColor = "#51C355"; + } else if (difficulty == "2") { + difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A"; + difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A"; + } else if (difficulty == "3") { + difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A"; + difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A"; + difficultyElement.childNodes[2].style.backgroundColor = "#8AC93A"; + } else if (difficulty == "4") { + difficultyElement.childNodes[0].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[1].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[2].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[3].style.backgroundColor = "#C35F51"; + } else if (difficulty == "5") { + difficultyElement.childNodes[0].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[1].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[2].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[3].style.backgroundColor = "#C35F51"; + difficultyElement.childNodes[4].style.backgroundColor = "#C35F51"; + } + }); + } + + const divRef = useRef(null); + + React.useEffect(() => { + + const lineChart = document.querySelector(".line-chart") + function createGraph() { + // max + let items = [ + { + record: "100", + date: new Date(2011, 4, 4), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "98", + date: new Date(2012, 6, 4), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "94", + date: new Date(2013, 0, 1), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "90", + date: new Date(2014, 0, 1), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "88", + date: new Date(2015, 6, 14), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "84", + date: new Date(2016, 8, 19), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "82", + date: new Date(2017, 3, 20), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "81", + date: new Date(2018, 2, 25), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "80", + date: new Date(2019, 3, 4), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "78", + date: new Date(2020, 11, 21), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "77", + date: new Date(2021, 10, 25), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "76", + date: new Date(2022, 4, 17), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "75", + date: new Date(2023, 9, 31), + map: "Container Ride", + first: "tiny zach" + }, + { + record: "74", + date: new Date(2024, 4, 4), + map: "Container Ride", + first: "tiny zach" + }, + ] + + function calculatePosition(date, startDate, endDate, maxWidth) { + const totalMilliseconds = endDate - startDate + 10000000000; + const millisecondsFromStart = date - startDate + 5000000000; + return (millisecondsFromStart / totalMilliseconds) * maxWidth + } + + const minDate = items.reduce((min, dp) => dp.date < min ? dp.date : min, items[0].date) + const maxDate = items.reduce((max, dp) => dp.date > max ? dp.date : max, items[0].date) + + const graph_width = document.querySelector(".portalcount-over-time-div").clientWidth + // console.log(graph_width) + + const uniqueYears = new Set() + items.forEach(dp => uniqueYears.add(dp.date.getFullYear())) + let minYear = Infinity; + let maxYear = -Infinity; + + items.forEach(dp => { + const year = dp.date.getFullYear(); + minYear = Math.min(minYear, year); + maxYear = Math.max(maxYear, year); + }); + + // Add missing years to the set + for (let year = minYear; year <= maxYear; year++) { + uniqueYears.add(year); + } + const uniqueYearsArr = Array.from(uniqueYears) + + items = items.map(dp => ({ + record: dp.record, + date: dp.date, + x: calculatePosition(dp.date, minDate, maxDate, lineChart.clientWidth), + map: dp.map, + first: dp.first + })) + + const yearInterval = lineChart.clientWidth / uniqueYears.size + for (let index = 1; index < (uniqueYears.size); index++) { + const placeholderlmao = document.createElement("div") + const yearSpan = document.createElement("span") + yearSpan.style.position = "absolute" + placeholderlmao.style.height = "100%" + placeholderlmao.style.width = "2px" + placeholderlmao.style.backgroundColor = "#00000080" + placeholderlmao.style.position = `absolute` + const thing = calculatePosition(new Date(uniqueYearsArr[index], 0, 0), minDate, maxDate, lineChart.clientWidth) + placeholderlmao.style.left = `${thing}px` + yearSpan.style.left = `${thing}px` + yearSpan.style.bottom = "-34px" + yearSpan.innerText = uniqueYearsArr[index] + yearSpan.style.fontFamily = "BarlowSemiCondensed-Regular" + yearSpan.style.fontSize = "22px" + yearSpan.style.opacity = "0.8" + lineChart.appendChild(yearSpan) + + } + + let maxPortals; + let minPortals; + let precision; + let multiplier = 1; + for (let index = 0; index < items.length; index++) { + precision = Math.floor((items[0].record - items[items.length - 1].record)) + if (precision > 20) { + precision = 20 + } + minPortals = Math.floor((items[items.length - 1].record) / 10) * 10 + if (index == 0) { + maxPortals = items[index].record - minPortals + } + } + function calculateMultiplier(value) { + while (value > precision) { + multiplier += 1; + value -= precision; + } + } + calculateMultiplier(items[0].record); + // if (items[0].record > 10) { + // multiplier = 2; + // } + + // Original cubic bezier control points + const P0 = { x: 0, y: 0 }; + const P1 = { x: 0.26, y: 1 }; + const P2 = { x: 0.74, y: 1 }; + const P3 = { x: 1, y: 0 }; + + function calculateIntermediateControlPoints(t, P0, P1, P2, P3) { + const x = (1 - t) ** 3 * P0.x + + 3 * (1 - t) ** 2 * t * P1.x + + 3 * (1 - t) * t ** 2 * P2.x + + t ** 3 * P3.x; + + const y = (1 - t) ** 3 * P0.y + + 3 * (1 - t) ** 2 * t * P1.y + + 3 * (1 - t) * t ** 2 * P2.y + + t ** 3 * P3.y; + + return { x, y }; + } + + + let delay = 0; + for (let index = 0; index < items.length; index++) { + let chart_height = 340; + const item = items[index]; + delay += 0.05; + // console.log(lineChart.clientWidth) + + // maxPortals++; + // maxPortals++; + + let point_height = (chart_height / maxPortals) + + for (let index = 0; index < (maxPortals / multiplier); index++) { + // console.log((index + 1) * multiplier) + let current_portal_count = (index + 1); + + const placeholderDiv = document.createElement("div") + const numPortalsText = document.createElement("span") + const numPortalsTextBottom = document.createElement("span") + numPortalsText.innerText = (current_portal_count * multiplier) + minPortals + numPortalsTextBottom.innerText = minPortals + placeholderDiv.style.position = "absolute" + numPortalsText.style.position = "absolute" + numPortalsTextBottom.style.position = "absolute" + numPortalsText.style.left = "-37px" + numPortalsText.style.opacity = "0.2" + numPortalsTextBottom.style.opacity = "0.2" + numPortalsText.style.fontFamily = "BarlowSemiCondensed-Regular" + numPortalsTextBottom.style.fontFamily = "BarlowSemiCondensed-Regular" + numPortalsText.style.fontSize = "22px" + numPortalsTextBottom.style.left = "-37px" + numPortalsTextBottom.style.fontSize = "22px" + numPortalsTextBottom.style.fontWeight = "400" + numPortalsText.style.color = "#CDCFDF" + numPortalsTextBottom.style.color = "#CDCFDF" + numPortalsText.style.fontFamily = "inherit" + numPortalsTextBottom.style.fontFamily = "inherit" + numPortalsText.style.textAlign = "right" + numPortalsTextBottom.style.textAlign = "right" + numPortalsText.style.width = "30px" + numPortalsTextBottom.style.width = "30px" + placeholderDiv.style.bottom = `${(point_height * current_portal_count * multiplier) - 2}px` + numPortalsText.style.bottom = `${(point_height * current_portal_count * multiplier) - 2 - 9}px` + numPortalsTextBottom.style.bottom = `${0 - 2 - 8}px` + placeholderDiv.id = placeholderDiv.style.bottom + placeholderDiv.style.width = "100%" + placeholderDiv.style.height = "2px" + placeholderDiv.style.backgroundColor = "#2B2E46" + placeholderDiv.style.zIndex = "0" + + if (index == 0) { + lineChart.appendChild(numPortalsTextBottom) + } + lineChart.appendChild(numPortalsText) + lineChart.appendChild(placeholderDiv) + } + + const li = document.createElement("li"); + const lineSeg = document.createElement("div"); + const dataPoint = document.createElement("div"); + + li.style = `--y: ${point_height * (item.record - minPortals) - 3}px; --x: ${item.x}px`; + lineSeg.className = "line-segment"; + dataPoint.className = "data-point"; + + if (items[index + 1] !== undefined) { + const hypotenuse = Math.sqrt( + Math.pow(items[index + 1].x - items[index].x, 2) + + Math.pow((point_height * items[index + 1].record) - point_height * item.record, 2) + ); + const angle = Math.asin( + ((point_height * item.record) - (point_height * items[index + 1].record)) / hypotenuse + ); + + lineSeg.style = `--hypotenuse: ${hypotenuse}; --angle: ${angle * (-180 / Math.PI)}`; + const t0 = index / items.length; + const t1 = (index + 1) / items.length + + const P0t0 = calculateIntermediateControlPoints(t0, P0, P1, P2, P3); + const P1t1 = calculateIntermediateControlPoints(t1, P0, P1, P2, P3); + const bezierStyle = `cubic-bezier(${P0t0.x.toFixed(3)}, ${P0t0.y.toFixed(3)}, ${P1t1.x.toFixed(3)}, ${P1t1.y.toFixed(3)})` + lineSeg.style.animationTimingFunction = bezierStyle + lineSeg.style.animationDelay = delay + "s" + } + dataPoint.style.animationDelay = delay + "s" + + let isHoveringOverData = false; + let isDataActive = false; + document.querySelector("#dataPointInfo").style.left = item.x + "px"; + document.querySelector("#dataPointInfo").style.bottom = (point_height * item.record -3) + "px"; + dataPoint.addEventListener("mouseenter", (e) => { + isDataActive = true; + isHoveringOverData = true; + const dataPoints = document.querySelectorAll(".data-point") + dataPoints.forEach(point => { + point.classList.remove("data-point-active") + }); + dataPoint.classList.add("data-point-active") + document.querySelector("#dataPointRecord").innerText = item.record; + document.querySelector("#dataPointMap").innerText = item.map; + document.querySelector("#dataPointDate").innerText = item.date.toLocaleDateString("en-GB"); + document.querySelector("#dataPointFirst").innerText = item.first; + if ((lineChart.clientWidth - 400) < item.x) { + document.querySelector("#dataPointInfo").style.left = item.x - 400 + "px"; + } else { + document.querySelector("#dataPointInfo").style.left = item.x + "px"; + } + if ((lineChart.clientHeight - 115) < (point_height * (item.record - minPortals) -3)) { + document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) - 115 + "px"; + } else { + document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) + "px"; + } + document.querySelector("#dataPointInfo").style.opacity = "1"; + document.querySelector("#dataPointInfo").style.zIndex = "10"; + }); + document.querySelector("#dataPointInfo").addEventListener("mouseenter", (e) => { + isHoveringOverData = true; + }) + document.querySelector("#dataPointInfo").addEventListener("mouseleave", (e) => { + isHoveringOverData = false; + }) + document.addEventListener("mousedown", () => { + if (!isHoveringOverData) { + isDataActive = false + dataPoint.classList.remove("data-point-active") + document.querySelector("#dataPointInfo").style.opacity = "0"; + document.querySelector("#dataPointInfo").style.zIndex = "0"; + } + }) + dataPoint.addEventListener("mouseenter", (e) => { + isHoveringOverData = false; + }) + document.querySelector(".chart").addEventListener("mouseleave", () => { + isDataActive = false + dataPoint.classList.remove("data-point-active") + document.querySelector("#dataPointInfo").style.opacity = "0"; + document.querySelector("#dataPointInfo").style.zIndex = "0"; + }) + + li.appendChild(lineSeg); + li.appendChild(dataPoint); + lineChart.appendChild(li); + } + } + + createGraph() + + async function fetchGames() { + try { + const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { + headers: { + 'Authorization': token + } + }); + + const data = await response.json(); + + const gameImg = document.querySelector(".game-img"); + + gameImg.style.backgroundImage = `url(${data.data[0].image})`; + + // const mapImg = document.querySelectorAll(".maplist-img"); + // mapImg.forEach((map) => { + // map.style.backgroundImage = `url(${data.data[0].image})`; + // }); + + } catch (error) { + console.log("error fetching games:", error); + } + } + + detectGame(); + + const maplistImg = document.querySelector("#maplistImg"); + maplistImg.src = img5; + const statisticsImg = document.querySelector("#statisticsImg"); + statisticsImg.src = img6; + + fetchGames(); + + const handleResize = (entries) => { + for (let entry of entries) { + lineChart.innerHTML = "" + createGraph() + if (document.querySelector(".maplist").getAttribute("currentTab") == "stats") { + document.querySelector(".stats").style.display = "block" + } else { + document.querySelector(".stats").style.display = "none" + } + } + }; + + const resizeObserver = new ResizeObserver(handleResize); + + // if (scrollRef.current) { + // //hi + // if (new URLSearchParams(new URL(window.location.href).search).get("chapter")) { + // setTimeout(() => { + // scrollRef.current.scrollIntoView({ behavior: "smooth", block: "start" }) + // }, 200); + // } + + // } + + if (divRef.current) { + resizeObserver.observe(divRef.current); + } + + return () => { + if (divRef.current) { + resizeObserver.unobserve(divRef.current); + } + resizeObserver.disconnect(); + }; + + + }) + return ( +
+
+
+ +   +
+ +
+
+
+
+ 0 + portals +
+
+ +
+
+
+ +
+ + +
+ +
+
+ undefined
+ undefined + +
+ + 0/0 + +
+ +
+
+
+
+ +
+
+ Portal count over time
+ +
+
+
+
+
+ Date + Map + Record + First completion +
+
+
+ + + + Hello +
+
+
    + +
+
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/pages/summary.css b/frontend/src/components/pages/summary.css index c99a0bf..8c6ec35 100644 --- a/frontend/src/components/pages/summary.css +++ b/frontend/src/components/pages/summary.css @@ -35,8 +35,8 @@ border: none; transition: background-color .1s; } -#section1>div>.nav-button:nth-child(1){border-radius: 20px 0 0 20px;} -#section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} +/* #section1>div>.nav-button:nth-child(1){border-radius: 0px;}:nth-child(1){border-radius: 20px 0 0 20px;} +#section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} */ .nav-button>span{padding: 0 8px 0 8px;} .nav-button:hover{background-color: #202232;cursor: pointer;} diff --git a/frontend/src/components/pages/summary.js b/frontend/src/components/pages/summary.js index 0bd26af..9957c5d 100644 --- a/frontend/src/components/pages/summary.js +++ b/frontend/src/components/pages/summary.js @@ -1,5 +1,5 @@ -import React from 'react'; -import { useLocation } from "react-router-dom"; +import React, { useEffect } from 'react'; +import { useLocation, Link } from "react-router-dom"; import ReactMarkdown from 'react-markdown' import "./summary.css"; @@ -317,8 +317,18 @@ if(window.confirm(`Are you sure you want to remove post: ${post.title}?`)){ } - if(data!==null){ + +let current_chapter = data.map.chapter_name +let isCoop = false; +if (data.map.game_name == "Portal 2 - Cooperative") { + isCoop = true +} + +current_chapter = data.map.chapter_name.split(" ") +// current_chapter = current_chapter.split("-") +current_chapter = current_chapter[1] + return ( <> {token!==null?mod===true?:"":""} @@ -329,8 +339,8 @@ return (
- - + +
{data.map.map_name}
diff --git a/frontend/src/components/record.css b/frontend/src/components/record.css new file mode 100644 index 0000000..60d47ee --- /dev/null +++ b/frontend/src/components/record.css @@ -0,0 +1,15 @@ +.record-container { + --padding: 20px; + width: calc(100% - calc(var(--padding * 2))); + height: 42px; + background-color: #2B2E46; + border-radius: 200px; + font-size: 18px; + display: grid; + grid-template-columns: 20% 25% 15% 15% 25%; + text-align: center; + padding: 0px var(--padding); + vertical-align: middle; + align-items: center; + margin-bottom: 6px; +} \ No newline at end of file diff --git a/frontend/src/components/record.js b/frontend/src/components/record.js new file mode 100644 index 0000000..a01109c --- /dev/null +++ b/frontend/src/components/record.js @@ -0,0 +1,57 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useLocation, Link } from "react-router-dom"; + +import "./record.css" + +export default function Record({ name, place, portals, time, date }) { + // const {token} = prop; + const [record, setRecord] = useState(null); + const location = useLocation(); + + useEffect(() => { + console.log(name, place, portals, time, date); + }) + + function timeSince() { + const now = new Date(); + const dateNew = new Date(date); + + const secondsPast = Math.floor((now - dateNew) / 1000); + console.log('Seconds past:', secondsPast); + + if (secondsPast < 60) { + return `${secondsPast} seconds ago`; + } + if (secondsPast < 3600) { + const minutes = Math.floor(secondsPast / 60); + return `${minutes} minutes ago`; + } + if (secondsPast < 86400) { + const hours = Math.floor(secondsPast / 3600); + return `${hours} hours ago`; + } + if (secondsPast < 2592000) { + const days = Math.floor(secondsPast / 86400); + return `${days} days ago`; + } + if (secondsPast < 31536000) { + const months = Math.floor(secondsPast / 2592000); + return `${months} months ago`; + } + const years = Math.floor(secondsPast / 31536000); + return `${years} years ago`; + } + + return( +
+ {place} +
+ + {name} +
+ {portals} + {time} + {timeSince()} +
+ ) +} diff --git a/frontend/src/components/sidebar.css b/frontend/src/components/sidebar.css index 29978db..34ede80 100644 --- a/frontend/src/components/sidebar.css +++ b/frontend/src/components/sidebar.css @@ -15,6 +15,8 @@ height: 80px; padding: 20px 0 20px 30px; + cursor: pointer; + user-select: none; } #logo-text{ @@ -75,11 +77,24 @@ span>b{ width: 310px; height: 40px; border-radius: 20px; - padding: 5px 0 0 6px; + padding: 0.4em 0 0 11px; transition: width .3s, - background-color .3s; + background-color .15s, + padding .3s; +} + +.sidebar-button-selected { + background-color: #202232; +} + +.sidebar-button-deselected { + background-color: #20223200; +} + +.sidebar-button-deselected:hover { + background-color: #202232aa; } button>img { @@ -125,6 +140,7 @@ a{text-decoration: none;height: 40px;} margin: 8px 0 8px 0; overflow-y: auto; max-height: calc(100vh - 172px); + scrollbar-width: thin; } #search-data::-webkit-scrollbar{display: none;} .search-map{ @@ -140,6 +156,7 @@ a{text-decoration: none;height: 40px;} transition: background-color .1s; background-color: #2b2e46; grid-template-rows: 20% 20% 60%; + width: calc(100% - 15px); } .search-map>span{ color: #888; diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js index 29859ab..6777867 100644 --- a/frontend/src/components/sidebar.js +++ b/frontend/src/components/sidebar.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Link, useLocation } from "react-router-dom"; import "../App.css" @@ -18,6 +18,7 @@ import Login from "./login.js" export default function Sidebar(prop) { const {token,setToken} = prop const [profile, setProfile] = React.useState(null); + React.useEffect(() => { fetch(`https://lp.ardapektezol.com/api/v1/profile`,{ headers: { @@ -28,7 +29,6 @@ React.useEffect(() => { .then(d => setProfile(d.data)) }, [token]); - // Locks search button for 300ms before it can be clicked again, prevents spam const [isLocked, setIsLocked] = React.useState(false); function HandleLock(arg) { @@ -40,34 +40,38 @@ if (!isLocked) { } +// The menu button +const [sidebar, setSidebar] = React.useState(); + // Clicked buttons function SidebarClick(x){ const btn = document.querySelectorAll("button.sidebar-button"); if(sidebar===1){setSidebar(0);SidebarHide()} +// clusterfuck btn.forEach((e,i) =>{ - btn[i].style.backgroundColor="inherit" + btn[i].classList.remove("sidebar-button-selected") + btn[i].classList.add("sidebar-button-deselected") }) -btn[x].style.backgroundColor="#202232" +btn[x].classList.add("sidebar-button-selected") +btn[x].classList.remove("sidebar-button-deselected") } - -// The menu button -const [sidebar, setSidebar] = React.useState(); - function SidebarHide(){ const btn = document.querySelectorAll("button.sidebar-button") const span = document.querySelectorAll("button.sidebar-button>span"); const side = document.querySelector("#sidebar-list"); const login = document.querySelectorAll(".login>button")[1]; +const searchbar = document.querySelector("#searchbar"); if(sidebar===1){ setSidebar(0) side.style.width="320px" btn.forEach((e, i) =>{ e.style.width="310px" + e.style.padding = "0.4em 0 0 11px" setTimeout(() => { span[i].style.opacity="1" login.style.opacity="1" @@ -77,28 +81,29 @@ if(sidebar===1){ side.style.zIndex="2" } else { side.style.width="40px"; + searchbar.focus(); + setSearch(searchbar.value) setSidebar(1) btn.forEach((e,i) =>{ e.style.width="40px" + e.style.padding = "0.4em 0 0 5px" span[i].style.opacity="0" }) login.style.opacity="0" setTimeout(() => { side.style.zIndex="0" }, 300); - } + } } - // Links const location = useLocation() React.useEffect(()=>{ if(location.pathname==="/"){SidebarClick(1)} if(location.pathname.includes("news")){SidebarClick(2)} - if(location.pathname.includes("records")){SidebarClick(3)} + if(location.pathname.includes("games")){SidebarClick(3)} if(location.pathname.includes("leaderboards")){SidebarClick(4)} - if(location.pathname.includes("discussions")){SidebarClick(5)} - if(location.pathname.includes("scorelog")){SidebarClick(6)} - if(location.pathname.includes("profile")){SidebarClick(7)} + if(location.pathname.includes("scorelog")){SidebarClick(5)} + if(location.pathname.includes("profile")){SidebarClick(6)} if(location.pathname.includes("rules")){SidebarClick(9)} if(location.pathname.includes("about")){SidebarClick(10)} @@ -107,6 +112,7 @@ React.useEffect(()=>{ const [search,setSearch] = React.useState(null) const [searchData,setSearchData] = React.useState(null) + React.useEffect(()=>{ fetch(`https://lp.ardapektezol.com/api/v1/search?q=${search}`) .then(r=>r.json()) @@ -139,18 +145,14 @@ return ( - - + + - - - - diff --git a/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..7d737f2 Binary files /dev/null and b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf differ diff --git a/frontend/src/index.js b/frontend/src/index.js index 20f2cdc..f648298 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -4,7 +4,5 @@ import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - ); -- cgit v1.2.3