diff options
| author | Wolfboy248 <121288977+Wolfboy248@users.noreply.github.com> | 2024-07-10 21:51:25 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-10 22:51:25 +0300 |
| commit | cfac59282da55f4791d6352f15887a15e9ff6ec5 (patch) | |
| tree | bf8cea62f60f9239073a9cf82648d69d1e6b6a1c /frontend/src | |
| parent | feat: return portal counts for each cat in chapter select (#175) (diff) | |
| download | lphub-cfac59282da55f4791d6352f15887a15e9ff6ec5.tar.gz lphub-cfac59282da55f4791d6352f15887a15e9ff6ec5.tar.bz2 lphub-cfac59282da55f4791d6352f15887a15e9ff6ec5.zip | |
Games page, maplist page (#153)
Co-authored-by: Wolfboy248 <105884620+Wolfboy248@users.noreply.github.com>
Diffstat (limited to 'frontend/src')
21 files changed, 1949 insertions, 35 deletions
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 { | |||
| 4 | margin: 0; | 4 | margin: 0; |
| 5 | } | 5 | } |
| 6 | 6 | ||
| 7 | .loader { | ||
| 8 | animation: loader 1.2s ease infinite; | ||
| 9 | background-size: 400% 300%; | ||
| 10 | background-image: linear-gradient(-90deg, #202232 0%, #202232 25%, #2a2c41 50%, #202232 75%, #202232 100%); | ||
| 11 | } | ||
| 12 | |||
| 13 | @keyframes loader { | ||
| 14 | 0% { | ||
| 15 | background-position: 100% 20%; | ||
| 16 | } | ||
| 17 | |||
| 18 | 50% { | ||
| 19 | background-position: 0% 50%; | ||
| 20 | } | ||
| 21 | |||
| 22 | 100% { | ||
| 23 | background-position: 0% 50%; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 7 | @font-face { | 27 | @font-face { |
| 8 | font-family: 'BarlowCondensed-Bold'; | 28 | font-family: 'BarlowCondensed-Bold'; |
| 9 | src: local('BarlowCondensed-Bold'), url(./fonts/BarlowCondensed-Bold.ttf) format('truetype'); | 29 | src: local('BarlowCondensed-Bold'), url(./fonts/BarlowCondensed-Bold.ttf) format('truetype'); |
| @@ -17,4 +37,9 @@ body { | |||
| 17 | @font-face { | 37 | @font-face { |
| 18 | font-family: 'BarlowSemiCondensed-Regular'; | 38 | font-family: 'BarlowSemiCondensed-Regular'; |
| 19 | src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-Regular.ttf) format('truetype'); | 39 | src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-Regular.ttf) format('truetype'); |
| 40 | } | ||
| 41 | |||
| 42 | @font-face { | ||
| 43 | font-family: 'BarlowSemiCondensed-SemiBold'; | ||
| 44 | src: local('BarlowSemiCondensed-Regular'), url(./fonts/BarlowSemiCondensed-SemiBold.ttf) format('truetype'); | ||
| 20 | } \ No newline at end of file | 45 | } \ 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"; | |||
| 8 | import Summary from "./components/pages/summary.js" | 8 | import Summary from "./components/pages/summary.js" |
| 9 | import Profile from "./components/pages/profile.js" | 9 | import Profile from "./components/pages/profile.js" |
| 10 | import About from './components/pages/about.js'; | 10 | import About from './components/pages/about.js'; |
| 11 | import Games from "./components/pages/games.js"; | ||
| 12 | import Maplist from './components/pages/maplist.js'; | ||
| 13 | import Home from "./components/pages/maplist.js"; | ||
| 14 | import Homepage from './components/pages/home.js'; | ||
| 11 | 15 | ||
| 12 | 16 | ||
| 13 | export default function App() { | 17 | export default function App() { |
| 14 | |||
| 15 | const [token, setToken] = React.useState(null); | 18 | const [token, setToken] = React.useState(null); |
| 16 | const [mod,setMod] = React.useState(false) | 19 | const [mod,setMod] = React.useState(false) |
| 17 | React.useEffect(()=>{ | 20 | React.useEffect(()=>{ |
| @@ -25,7 +28,7 @@ export default function App() { | |||
| 25 | <BrowserRouter> | 28 | <BrowserRouter> |
| 26 | <Sidebar token={token} setToken={setToken}/> | 29 | <Sidebar token={token} setToken={setToken}/> |
| 27 | <Routes> | 30 | <Routes> |
| 28 | <Route index element={<Main text="Homepage"/>}></Route> | 31 | <Route index element={<Homepage token={token} mod={mod}/>}></Route> |
| 29 | <Route path="/news" element={<Main text="News"/>}></Route> | 32 | <Route path="/news" element={<Main text="News"/>}></Route> |
| 30 | <Route path="/records" element={<Main text="Records"/>}></Route> | 33 | <Route path="/records" element={<Main text="Records"/>}></Route> |
| 31 | <Route path="/leaderboards" element={<Main text="Leaderboards"/>}></Route> | 34 | <Route path="/leaderboards" element={<Main text="Leaderboards"/>}></Route> |
| @@ -36,6 +39,8 @@ export default function App() { | |||
| 36 | <Route path="/rules" element={<Main text="Rules"/>}></Route> | 39 | <Route path="/rules" element={<Main text="Rules"/>}></Route> |
| 37 | <Route path="/about" element={<About/>}></Route> | 40 | <Route path="/about" element={<About/>}></Route> |
| 38 | <Route path="/maps/*" element={<Summary token={token} mod={mod}/>}></Route> | 41 | <Route path="/maps/*" element={<Summary token={token} mod={mod}/>}></Route> |
| 42 | <Route path="/games" element={<Games/>}></Route> | ||
| 43 | <Route path="/games/game" element={<Maplist token={token} mod={mod} />}></Route> | ||
| 39 | <Route path="*" element={<Main text="404 Page not found"/>}></Route> | 44 | <Route path="*" element={<Main text="404 Page not found"/>}></Route> |
| 40 | </Routes> | 45 | </Routes> |
| 41 | </BrowserRouter> | 46 | </BrowserRouter> |
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 { | |||
| 10 | 10 | ||
| 11 | padding-right: 30px; | 11 | padding-right: 30px; |
| 12 | 12 | ||
| 13 | font-size: 40px; | 13 | /* font-size: 40px; */ |
| 14 | font-family: BarlowSemiCondensed-Regular; | 14 | font-family: BarlowSemiCondensed-Regular; |
| 15 | color: #cdcfdf; | 15 | color: #cdcfdf; |
| 16 | 16 | ||
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'; | |||
| 2 | 2 | ||
| 3 | import "../App.css" | 3 | import "../App.css" |
| 4 | import "./main.css"; | 4 | import "./main.css"; |
| 5 | import { Link } from 'react-router-dom'; | ||
| 5 | 6 | ||
| 6 | export default function Main(props) { | 7 | export default function Main(props) { |
| 7 | 8 | ||
| @@ -9,7 +10,7 @@ export default function Main(props) { | |||
| 9 | return ( | 10 | return ( |
| 10 | <main> | 11 | <main> |
| 11 | <h1>{props.text}</h1> | 12 | <h1>{props.text}</h1> |
| 12 | </main> | 13 | </main> |
| 13 | ) | 14 | ) |
| 14 | } | 15 | } |
| 15 | 16 | ||
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 @@ | |||
| 1 | .news-container { | ||
| 2 | background-color: #2A2D40; | ||
| 3 | border-radius: 24px; | ||
| 4 | font-size: 18px; | ||
| 5 | overflow: hidden; | ||
| 6 | margin-bottom: 10px; | ||
| 7 | } | ||
| 8 | |||
| 9 | .news-title { | ||
| 10 | padding: 20px; | ||
| 11 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 12 | font-size: 22px; | ||
| 13 | } | ||
| 14 | |||
| 15 | .news-description-div { | ||
| 16 | margin: 0px 20px; | ||
| 17 | padding: 8px 0px; | ||
| 18 | } | ||
| 19 | |||
| 20 | .news-title-header { | ||
| 21 | background-color: #2B2E46; | ||
| 22 | padding: 10px 0px; | ||
| 23 | } | ||
| 24 | |||
| 25 | .news-container>span { | ||
| 26 | font-size: 18px; | ||
| 27 | font-family: BarlowSemiCondensed-Regular; | ||
| 28 | line-height: 0px; | ||
| 29 | } \ 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 @@ | |||
| 1 | import React, { useEffect, useRef, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import "./news.css" | ||
| 5 | |||
| 6 | export default function News({newsInfo}) { | ||
| 7 | // const { token } = prop | ||
| 8 | const [news, setNews] = React.useState(null); | ||
| 9 | const location = useLocation(); | ||
| 10 | |||
| 11 | return ( | ||
| 12 | <div className='news-container'> | ||
| 13 | <div className='news-title-header'> | ||
| 14 | <span className='news-title'>{newsInfo.title}</span> | ||
| 15 | </div> | ||
| 16 | <div className='news-description-div'> | ||
| 17 | <span className='description'>{newsInfo.short_description}</span> | ||
| 18 | </div> | ||
| 19 | </div> | ||
| 20 | ) | ||
| 21 | } \ 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 @@ | |||
| 1 | import React, { useEffect, useRef, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import "./games.css" | ||
| 5 | |||
| 6 | export default function GameEntry({ gameInfo }) { | ||
| 7 | const [gameEntry, setGameEntry] = React.useState(null); | ||
| 8 | const location = useLocation(); | ||
| 9 | |||
| 10 | const gameInfoCats = gameInfo.category_portals; | ||
| 11 | |||
| 12 | useEffect(() => { | ||
| 13 | gameInfoCats.forEach(catInfo => { | ||
| 14 | const itemBody = document.createElement("div"); | ||
| 15 | const itemTitle = document.createElement("span"); | ||
| 16 | const spacing = document.createElement("br"); | ||
| 17 | const itemNum = document.createElement("span"); | ||
| 18 | |||
| 19 | itemTitle.innerText = catInfo.category.name; | ||
| 20 | itemNum.innerText = catInfo.portal_count; | ||
| 21 | itemTitle.classList.add("games-page-item-body-item-title"); | ||
| 22 | itemNum.classList.add("games-page-item-body-item-num"); | ||
| 23 | itemBody.appendChild(itemTitle); | ||
| 24 | itemBody.appendChild(spacing); | ||
| 25 | itemBody.appendChild(itemNum); | ||
| 26 | itemBody.className = "games-page-item-body-item"; | ||
| 27 | |||
| 28 | // itemBody.innerHTML = ` | ||
| 29 | // <span className='games-page-item-body-item-title'>${catInfo.category.name}</span><br /> | ||
| 30 | // <span className='games-page-item-body-item-num'>${catInfo.portal_count}</span>` | ||
| 31 | |||
| 32 | document.getElementById(`${gameInfo.id}`).appendChild(itemBody); | ||
| 33 | }); | ||
| 34 | }) | ||
| 35 | |||
| 36 | return ( | ||
| 37 | <Link to={"/games/game?game=" + gameInfo.id}><div className='games-page-item'> | ||
| 38 | <div className='games-page-item-header'> | ||
| 39 | <div style={{backgroundImage: `url(${gameInfo.image})`}} className='games-page-item-header-img'></div> | ||
| 40 | <span><b>{gameInfo.name}</b></span> | ||
| 41 | </div> | ||
| 42 | <div id={gameInfo.id} className='games-page-item-body'> | ||
| 43 | </div> | ||
| 44 | </div></Link> | ||
| 45 | ) | ||
| 46 | } | ||
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 @@ | |||
| 1 | .games-page { | ||
| 2 | position: absolute; | ||
| 3 | left: 320px; | ||
| 4 | color: white; | ||
| 5 | width: calc(100% - 320px); | ||
| 6 | height: 100%; | ||
| 7 | font-family: BarlowSemiCondensed-Regular; | ||
| 8 | color: #ffffff; | ||
| 9 | overflow-y: scroll; | ||
| 10 | scrollbar-width: thin; | ||
| 11 | } | ||
| 12 | |||
| 13 | .games-page-item-content { | ||
| 14 | position: absolute; | ||
| 15 | left: 50px; | ||
| 16 | width: calc(100% - 100px); | ||
| 17 | } | ||
| 18 | |||
| 19 | .games-page-item-content a { | ||
| 20 | color: inherit; | ||
| 21 | } | ||
| 22 | |||
| 23 | .games-page-header { | ||
| 24 | margin-top: 50px; | ||
| 25 | margin-left: 50px; | ||
| 26 | } | ||
| 27 | |||
| 28 | span>b { | ||
| 29 | font-size: 56px; | ||
| 30 | font-family: BarlowCondensed-Bold; | ||
| 31 | } | ||
| 32 | |||
| 33 | .loader-game { | ||
| 34 | width: 100%; | ||
| 35 | height: 256px; | ||
| 36 | border-radius: 24px; | ||
| 37 | overflow: hidden; | ||
| 38 | margin: 25px 0px; | ||
| 39 | } | ||
| 40 | |||
| 41 | .games-page-item { | ||
| 42 | width: 100%; | ||
| 43 | height: 256px; | ||
| 44 | background: #202232; | ||
| 45 | border-radius: 24px; | ||
| 46 | overflow: hidden; | ||
| 47 | margin: 25px 0px; | ||
| 48 | } | ||
| 49 | |||
| 50 | .games-page-item-header { | ||
| 51 | width: 100%; | ||
| 52 | height: 50%; | ||
| 53 | background-size: cover; | ||
| 54 | overflow: hidden; | ||
| 55 | } | ||
| 56 | |||
| 57 | .games-page-item-header-img { | ||
| 58 | width: 100%; | ||
| 59 | height: 100%; | ||
| 60 | backdrop-filter: blur(4px); | ||
| 61 | filter: blur(4px); | ||
| 62 | background-size: cover; | ||
| 63 | } | ||
| 64 | |||
| 65 | .games-page-item-header span>b { | ||
| 66 | display: flex; | ||
| 67 | justify-content: center; | ||
| 68 | align-items: center; | ||
| 69 | height: 100%; | ||
| 70 | transform: translateY(-100%); | ||
| 71 | } | ||
| 72 | |||
| 73 | .games-page-item-body { | ||
| 74 | display: flex; | ||
| 75 | justify-content: center; | ||
| 76 | align-items: center; | ||
| 77 | height: 50%; | ||
| 78 | } | ||
| 79 | |||
| 80 | .games-page-item-body-item { | ||
| 81 | background: #2B2E46; | ||
| 82 | text-align: center; | ||
| 83 | width: max-content; | ||
| 84 | width: calc(100% - 24px); | ||
| 85 | height: 100px; | ||
| 86 | border-radius: 24px; | ||
| 87 | color: #CDCFDF; | ||
| 88 | margin: 12px; | ||
| 89 | } | ||
| 90 | |||
| 91 | .games-page-item-body-item-title { | ||
| 92 | margin-top: 0px; | ||
| 93 | font-size: 26px; | ||
| 94 | } | ||
| 95 | |||
| 96 | .games-page-item-body-item-num { | ||
| 97 | font-size: 50px; | ||
| 98 | font-family: BarlowCondensed-Bold; | ||
| 99 | } \ 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 @@ | |||
| 1 | import React, { useEffect, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import "./games.css" | ||
| 5 | import GameEntry from './game'; | ||
| 6 | |||
| 7 | export default function Games(prop) { | ||
| 8 | const { token } = prop; | ||
| 9 | const [games, setGames] = useState([]); | ||
| 10 | const location = useLocation(); | ||
| 11 | |||
| 12 | useEffect(() => { | ||
| 13 | const fetchGames = async () => { | ||
| 14 | try { | ||
| 15 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 16 | headers: { | ||
| 17 | 'Authorization': token | ||
| 18 | } | ||
| 19 | }); | ||
| 20 | |||
| 21 | const data = await response.json(); | ||
| 22 | setGames(data.data); | ||
| 23 | pageLoad(); | ||
| 24 | } catch (err) { | ||
| 25 | console.error("Error fetching games:", err); | ||
| 26 | } | ||
| 27 | }; | ||
| 28 | |||
| 29 | fetchGames(); | ||
| 30 | |||
| 31 | function pageLoad() { | ||
| 32 | const loaders = document.querySelectorAll(".loader"); | ||
| 33 | loaders.forEach((loader) => { | ||
| 34 | loader.style.display = "none"; | ||
| 35 | }); | ||
| 36 | } | ||
| 37 | }, [token]); | ||
| 38 | |||
| 39 | return ( | ||
| 40 | <div className='games-page'> | ||
| 41 | <section className='games-page-header'> | ||
| 42 | <span><b>Games list</b></span> | ||
| 43 | </section> | ||
| 44 | |||
| 45 | <section> | ||
| 46 | <div className='games-page-content'> | ||
| 47 | <div className='games-page-item-content'> | ||
| 48 | <div className='loader loader-game'></div> | ||
| 49 | <div className='loader loader-game'></div> | ||
| 50 | {games.map((game, index) => ( | ||
| 51 | <GameEntry gameInfo={game} key={index} /> | ||
| 52 | ))} | ||
| 53 | </div> | ||
| 54 | </div> | ||
| 55 | </section> | ||
| 56 | </div> | ||
| 57 | ); | ||
| 58 | } | ||
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 @@ | |||
| 1 | * { | ||
| 2 | scrollbar-width: thin; | ||
| 3 | } | ||
| 4 | |||
| 5 | .homepage-panel { | ||
| 6 | background-color: #202232; | ||
| 7 | margin: 10px 10px; | ||
| 8 | padding: 10px; | ||
| 9 | border-radius: 24px; | ||
| 10 | overflow: hidden; | ||
| 11 | flex: 1 1 100%; | ||
| 12 | align-items: stretch; | ||
| 13 | width: 100%; | ||
| 14 | } | ||
| 15 | |||
| 16 | .homepage-panel-title-div { | ||
| 17 | background-color: #2B2E46; | ||
| 18 | width: fit-content; | ||
| 19 | padding: 5px 18px; | ||
| 20 | border-radius: 200px; | ||
| 21 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 22 | font-size: 32px; | ||
| 23 | margin-bottom: 10px; | ||
| 24 | } | ||
| 25 | |||
| 26 | .stats-div { | ||
| 27 | background-color: #2B2E46; | ||
| 28 | border-radius: 24px; | ||
| 29 | text-align: center; | ||
| 30 | display: grid; | ||
| 31 | padding: 10px 0px; | ||
| 32 | width: 100%; | ||
| 33 | } | ||
| 34 | |||
| 35 | .stats-div span { | ||
| 36 | font-family: BarlowSemiCondensed-Regular; | ||
| 37 | font-size: 18px; | ||
| 38 | } | ||
| 39 | |||
| 40 | .stats-div span>b { | ||
| 41 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 42 | font-size: 42px; | ||
| 43 | } | ||
| 44 | |||
| 45 | .record-title div { | ||
| 46 | --padding: 20px; | ||
| 47 | width: calc(100% - calc(var(--padding * 2))); | ||
| 48 | height: 32px; | ||
| 49 | border-radius: 200px; | ||
| 50 | font-size: 18px; | ||
| 51 | display: grid; | ||
| 52 | grid-template-columns: calc(20% - 3.6px) calc(25% - 4.5px) calc(15% - 2.7px) calc(15% - 2.7px) calc(25% - 4.5px); | ||
| 53 | text-align: center; | ||
| 54 | padding: 0px var(--padding); | ||
| 55 | vertical-align: middle; | ||
| 56 | align-items: center; | ||
| 57 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 58 | } | ||
| 59 | |||
| 60 | .record-title::after { | ||
| 61 | content: ""; | ||
| 62 | display: flex; | ||
| 63 | width: 100%; | ||
| 64 | height: 3px; | ||
| 65 | background-color: #2B2E46; | ||
| 66 | margin-bottom: 5px; | ||
| 67 | } | ||
| 68 | |||
| 69 | .recommended-map-img { | ||
| 70 | width: 250px; | ||
| 71 | border-radius: 24px; | ||
| 72 | margin-bottom: 0; | ||
| 73 | border: 7px solid #2B2E46; | ||
| 74 | } | ||
| 75 | |||
| 76 | .difficulty-bar-home { | ||
| 77 | width: 100%; | ||
| 78 | display: grid; | ||
| 79 | grid-template-columns: 20% 20% 20% 20% 20%; | ||
| 80 | align-items: center; | ||
| 81 | margin: 5px; | ||
| 82 | margin-top: 16px; | ||
| 83 | } | ||
| 84 | |||
| 85 | .difficulty-point { | ||
| 86 | background: #2B2E46; | ||
| 87 | height: 3px; | ||
| 88 | margin: 5px; | ||
| 89 | border-radius: 10px; | ||
| 90 | } | ||
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 @@ | |||
| 1 | import React, { useEffect, useRef, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import "./home.css" | ||
| 5 | import News from '../news'; | ||
| 6 | import Record from '../record'; | ||
| 7 | |||
| 8 | export default function Homepage(prop) { | ||
| 9 | const {token, setToken} = prop | ||
| 10 | const [home, setHome] = React.useState(null); | ||
| 11 | const location = useLocation(); | ||
| 12 | |||
| 13 | useEffect(() => { | ||
| 14 | async function fetchMapImg() { | ||
| 15 | try { | ||
| 16 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 17 | headers: { | ||
| 18 | 'Authorization': token | ||
| 19 | } | ||
| 20 | }); | ||
| 21 | |||
| 22 | const data = await response.json(); | ||
| 23 | |||
| 24 | const recommendedMapImg = document.querySelector("#recommendedMapImg"); | ||
| 25 | |||
| 26 | recommendedMapImg.src = `${data.data[0].image}` | ||
| 27 | |||
| 28 | const column1 = document.querySelector("#column1"); | ||
| 29 | const column2 = document.querySelector("#column2"); | ||
| 30 | |||
| 31 | column2.style.height = column1.clientHeight + "px"; | ||
| 32 | } catch (error) { | ||
| 33 | console.log(error) | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | fetchMapImg() | ||
| 38 | |||
| 39 | const panels = document.querySelectorAll(".homepage-panel"); | ||
| 40 | panels.forEach(e => { | ||
| 41 | // this is cuz react is silly | ||
| 42 | if (e.innerHTML.includes('<div class="homepage-panel-title-div">')) { | ||
| 43 | return | ||
| 44 | } | ||
| 45 | const title = e.getAttribute("title"); | ||
| 46 | |||
| 47 | const titleDiv = document.createElement("div"); | ||
| 48 | const titleSpan = document.createElement("span"); | ||
| 49 | |||
| 50 | titleDiv.classList.add("homepage-panel-title-div") | ||
| 51 | |||
| 52 | titleSpan.innerText = title | ||
| 53 | |||
| 54 | titleDiv.appendChild(titleSpan) | ||
| 55 | e.insertBefore(titleDiv, e.firstChild) | ||
| 56 | }); | ||
| 57 | }) | ||
| 58 | |||
| 59 | const newsList = [ | ||
| 60 | { | ||
| 61 | "title": "Portal Saved on Container Ride", | ||
| 62 | "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." | ||
| 63 | }, | ||
| 64 | { | ||
| 65 | "title": "Portal Saved on Container Ride", | ||
| 66 | "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." | ||
| 67 | }, | ||
| 68 | { | ||
| 69 | "title": "Portal Saved on Container Ride", | ||
| 70 | "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." | ||
| 71 | }, | ||
| 72 | { | ||
| 73 | "title": "Portal Saved on Container Ride", | ||
| 74 | "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." | ||
| 75 | }, | ||
| 76 | { | ||
| 77 | "title": "Portal Saved on Container Ride", | ||
| 78 | "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." | ||
| 79 | }, | ||
| 80 | { | ||
| 81 | "title": "Portal Saved on Container Ride", | ||
| 82 | "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." | ||
| 83 | }, | ||
| 84 | { | ||
| 85 | "title": "Portal Saved on Container Ride", | ||
| 86 | "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." | ||
| 87 | }, | ||
| 88 | { | ||
| 89 | "title": "Portal Saved on Container Ride", | ||
| 90 | "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." | ||
| 91 | }, | ||
| 92 | { | ||
| 93 | "title": "Portal Saved on Container Ride", | ||
| 94 | "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." | ||
| 95 | }, | ||
| 96 | { | ||
| 97 | "title": "Portal Saved on Container Ride", | ||
| 98 | "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." | ||
| 99 | }, | ||
| 100 | { | ||
| 101 | "title": "Portal Saved on Container Ride", | ||
| 102 | "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." | ||
| 103 | }, | ||
| 104 | { | ||
| 105 | "title": "Portal Saved on Container Ride", | ||
| 106 | "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." | ||
| 107 | }, | ||
| 108 | { | ||
| 109 | "title": "Portal Saved on Container Ride", | ||
| 110 | "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." | ||
| 111 | }, | ||
| 112 | { | ||
| 113 | "title": "Portal Saved on Container Ride", | ||
| 114 | "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." | ||
| 115 | }, | ||
| 116 | ] | ||
| 117 | |||
| 118 | return ( | ||
| 119 | <main> | ||
| 120 | <section style={{marginTop: "40px", userSelect: "none"}}> | ||
| 121 | <span style={{fontSize: "40px"}}>Welcome back,</span><br/> | ||
| 122 | <span><b style={{ fontSize: "96px", transform: "translateY(-20px)", display: "block"}}>Krzyhau</b></span> | ||
| 123 | </section> | ||
| 124 | |||
| 125 | <div style={{display: "grid", gridTemplateColumns: "calc(50%) calc(50%)"}}> | ||
| 126 | <div id='column1' style={{ display: "flex", alignItems: "self-start", flexWrap: "wrap", alignContent: "start" }}> | ||
| 127 | {/* Column 1 */} | ||
| 128 | <section title="Your Profile" className='homepage-panel'> | ||
| 129 | <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: "12px"}}> | ||
| 130 | <div className='stats-div'> | ||
| 131 | <span>Overall rank</span><br/> | ||
| 132 | <span><b>#69</b></span> | ||
| 133 | </div> | ||
| 134 | <div className='stats-div'> | ||
| 135 | <span>Singleplayer</span><br/> | ||
| 136 | <span style={{fontSize: "22px"}}><b>#10</b> (60/62)</span> | ||
| 137 | </div> | ||
| 138 | <div className='stats-div'> | ||
| 139 | <span>Overall rank</span><br/> | ||
| 140 | <span style={{fontSize: "22px"}}><b>#69</b> (13/37)</span> | ||
| 141 | </div> | ||
| 142 | </div> | ||
| 143 | </section> | ||
| 144 | <section title="What's Next?" className='homepage-panel'> | ||
| 145 | <div style={{display: "flex"}}> | ||
| 146 | <img className='recommended-map-img' id="recommendedMapImg"></img> | ||
| 147 | <div style={{marginLeft: "12px", display: "block", width: "100%"}}> | ||
| 148 | <span style={{fontFamily: "BarlowSemiCondensed-SemiBold", fontSize: "32px", width: "100%", display: "block"}}>Container Ride</span> | ||
| 149 | <span style={{fontSize: "20px"}}>Your Record: 4 portals</span> | ||
| 150 | <span style={{fontFamily: "BarlowSemiCondensed-SemiBold", fontSize: "36px", width: "100%", display: "block"}}>World Record: 2 portals</span> | ||
| 151 | <div className='difficulty-bar-home'> | ||
| 152 | <div className='difficulty-point' style={{backgroundColor: "#51C355"}}></div> | ||
| 153 | <div className='difficulty-point'></div> | ||
| 154 | <div className='difficulty-point'></div> | ||
| 155 | <div className='difficulty-point'></div> | ||
| 156 | <div className='difficulty-point'></div> | ||
| 157 | </div> | ||
| 158 | </div> | ||
| 159 | </div> | ||
| 160 | </section> | ||
| 161 | <section title="Newest Records" className='homepage-panel' style={{height: "250px"}}> | ||
| 162 | <div className='record-title'> | ||
| 163 | <div> | ||
| 164 | <span>Place</span> | ||
| 165 | <span style={{textAlign: "left"}}>Runner</span> | ||
| 166 | <span>Portals</span> | ||
| 167 | <span>Time</span> | ||
| 168 | <span>Date</span> | ||
| 169 | </div> | ||
| 170 | </div> | ||
| 171 | <div style={{overflowY: "scroll", height: "calc(100% - 90px)", paddingRight: "10px"}}> | ||
| 172 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 173 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 174 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 175 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 176 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 177 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 178 | <Record name={"Krzyhau"} portals={"2"} date={new Date("2024-05-21T08:45:00")} place={"2"} time={"20.20"}></Record> | ||
| 179 | </div> | ||
| 180 | </section> | ||
| 181 | </div> | ||
| 182 | {/* Column 2 */} | ||
| 183 | <div id='column2' style={{display: "flex", alignItems: "stretch", height: "87px"}}> | ||
| 184 | <section title="News" className='homepage-panel'> | ||
| 185 | <div id='newsContent' style={{ display: "block", width: "100%", overflowY: "scroll", height: "calc(100% - 50px)"}}> | ||
| 186 | {newsList.map((newsList, index) => ( | ||
| 187 | <News newsInfo={newsList} key={index}></News> | ||
| 188 | ))} | ||
| 189 | </div> | ||
| 190 | </section> | ||
| 191 | </div> | ||
| 192 | </div> | ||
| 193 | |||
| 194 | |||
| 195 | |||
| 196 | </main> | ||
| 197 | ) | ||
| 198 | } \ 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 @@ | |||
| 1 | .maplist-page { | ||
| 2 | position: relative; | ||
| 3 | left: 350px; | ||
| 4 | height: 100vh; | ||
| 5 | color: #cdcfdf; | ||
| 6 | width: calc(100% - 380px); | ||
| 7 | font-family: BarlowSemiCondensed-Regular; | ||
| 8 | overflow-y: scroll; | ||
| 9 | overflow-x: hidden; | ||
| 10 | padding-right: 30px; | ||
| 11 | } | ||
| 12 | |||
| 13 | a { | ||
| 14 | color: inherit; | ||
| 15 | width: fit-content; | ||
| 16 | } | ||
| 17 | |||
| 18 | .maplist-page-content { | ||
| 19 | position: absolute; | ||
| 20 | left: 0px; | ||
| 21 | width: calc(100% - 50px); | ||
| 22 | } | ||
| 23 | |||
| 24 | .maplist-page-header { | ||
| 25 | margin-top: 20px; | ||
| 26 | display: grid; | ||
| 27 | margin-bottom: 10px; | ||
| 28 | } | ||
| 29 | |||
| 30 | .nav-btn { | ||
| 31 | height: 40px; | ||
| 32 | background-color: #2b2e46; | ||
| 33 | color: inherit; | ||
| 34 | font-size: 18px; | ||
| 35 | font-family: inherit; | ||
| 36 | border: none; | ||
| 37 | border-radius: 20px; | ||
| 38 | transition: background-color .1s; | ||
| 39 | cursor: default; | ||
| 40 | width: fit-content; | ||
| 41 | } | ||
| 42 | |||
| 43 | .nav-btn>span { | ||
| 44 | padding: 0 8px 0 8px; | ||
| 45 | } | ||
| 46 | |||
| 47 | .nav-btn:hover { | ||
| 48 | background-color: #202232; | ||
| 49 | cursor: pointer; | ||
| 50 | } | ||
| 51 | |||
| 52 | .game { | ||
| 53 | width: 100%; | ||
| 54 | height: 192px; | ||
| 55 | background: #202232; | ||
| 56 | border-radius: 24px; | ||
| 57 | overflow: hidden; | ||
| 58 | } | ||
| 59 | |||
| 60 | .game-header { | ||
| 61 | width: 100%; | ||
| 62 | height: 144px; | ||
| 63 | display: flex; | ||
| 64 | justify-content: center; | ||
| 65 | align-items: center; | ||
| 66 | overflow: hidden; | ||
| 67 | } | ||
| 68 | |||
| 69 | .game-header-text { | ||
| 70 | display: flex; | ||
| 71 | justify-content: center; | ||
| 72 | align-items: center; | ||
| 73 | position: absolute; | ||
| 74 | } | ||
| 75 | |||
| 76 | .game-img { | ||
| 77 | width: 100%; | ||
| 78 | height: 100%; | ||
| 79 | background-size: cover; | ||
| 80 | filter: blur(4px); | ||
| 81 | } | ||
| 82 | |||
| 83 | .game-header-text>span { | ||
| 84 | font-size: 42px; | ||
| 85 | font-weight: 500; | ||
| 86 | margin: 5px; | ||
| 87 | } | ||
| 88 | |||
| 89 | .game-header-text span>b { | ||
| 90 | font-size: 96px; | ||
| 91 | font-weight: 600; | ||
| 92 | } | ||
| 93 | |||
| 94 | .game-nav { | ||
| 95 | display: flex; | ||
| 96 | height: 48px; | ||
| 97 | } | ||
| 98 | |||
| 99 | .game-nav-btn { | ||
| 100 | width: 100%; | ||
| 101 | height: 100%; | ||
| 102 | border: none; | ||
| 103 | border-radius: 0px; | ||
| 104 | color: inherit; | ||
| 105 | font-family: inherit; | ||
| 106 | font-size: 22px; | ||
| 107 | background: #2B2E46; | ||
| 108 | transition: background-color .1s; | ||
| 109 | margin: 0 1px; | ||
| 110 | display: flex; | ||
| 111 | justify-content: center; | ||
| 112 | align-items: center; | ||
| 113 | } | ||
| 114 | |||
| 115 | .game-nav-btn:hover, .selected { | ||
| 116 | background-color: #202232; | ||
| 117 | cursor: pointer; | ||
| 118 | } | ||
| 119 | |||
| 120 | .gameview-nav { | ||
| 121 | margin-top: 20px; | ||
| 122 | display: flex; | ||
| 123 | height: 56px; | ||
| 124 | border-radius: 24px; | ||
| 125 | overflow: hidden; | ||
| 126 | background-color: #202232; | ||
| 127 | } | ||
| 128 | |||
| 129 | .maplist { | ||
| 130 | width: 100%; | ||
| 131 | margin-top: 20px; | ||
| 132 | margin-bottom: 40px; | ||
| 133 | } | ||
| 134 | |||
| 135 | .chapter-name { | ||
| 136 | font-size: 30px; | ||
| 137 | } | ||
| 138 | |||
| 139 | .chapter-page-div { | ||
| 140 | display: flex; | ||
| 141 | justify-content: right; | ||
| 142 | transform: translateY(-30px); | ||
| 143 | } | ||
| 144 | |||
| 145 | .chapter-page-div button { | ||
| 146 | background-color: #00000000; | ||
| 147 | border: 0; | ||
| 148 | cursor: pointer; | ||
| 149 | height: 30px; | ||
| 150 | padding: 0; | ||
| 151 | width: 30px; | ||
| 152 | } | ||
| 153 | |||
| 154 | .chapter-page-div span { | ||
| 155 | color: #cdcfdf; | ||
| 156 | font-family: BarlowSemiCondensed-Regular; | ||
| 157 | font-size: 20px; | ||
| 158 | } | ||
| 159 | |||
| 160 | .maplist-maps { | ||
| 161 | display: grid; | ||
| 162 | grid-template-columns: 25% 25% 25% 25%; | ||
| 163 | margin-top: 10px; | ||
| 164 | transform: translateY(-30px); | ||
| 165 | } | ||
| 166 | |||
| 167 | .maplist-item { | ||
| 168 | background: #202232; | ||
| 169 | border-radius: 24px; | ||
| 170 | overflow: hidden; | ||
| 171 | margin: 10px 10px; | ||
| 172 | /* padding: 10px 15px; */ | ||
| 173 | cursor: pointer; | ||
| 174 | user-select: none; | ||
| 175 | } | ||
| 176 | |||
| 177 | .loader-map { | ||
| 178 | border-radius: 24px; | ||
| 179 | overflow: hidden; | ||
| 180 | margin: 10px 10px; | ||
| 181 | /* padding: 10px 15px; */ | ||
| 182 | user-select: none; | ||
| 183 | width: calc(100% - 20px); | ||
| 184 | height: calc(223px); | ||
| 185 | } | ||
| 186 | |||
| 187 | .maplist-img-div { | ||
| 188 | height: 150px; | ||
| 189 | overflow: hidden; | ||
| 190 | } | ||
| 191 | |||
| 192 | .maplist-img { | ||
| 193 | width: 100%; | ||
| 194 | height: 100%; | ||
| 195 | background-size: cover; | ||
| 196 | filter: blur(4px); | ||
| 197 | opacity: 0.7; | ||
| 198 | } | ||
| 199 | |||
| 200 | .maplist-portalcount-div { | ||
| 201 | display: flex; | ||
| 202 | justify-content: center; | ||
| 203 | align-items: center; | ||
| 204 | text-align: center; | ||
| 205 | height: 100%; | ||
| 206 | transform: translateY(-100%); | ||
| 207 | overflow: hidden; | ||
| 208 | } | ||
| 209 | |||
| 210 | .maplist-title { | ||
| 211 | font-size: 22px; | ||
| 212 | text-align: center; | ||
| 213 | width: 100%; | ||
| 214 | display: inherit; | ||
| 215 | padding: 5px 0px; | ||
| 216 | color: #CDCFDF; | ||
| 217 | } | ||
| 218 | |||
| 219 | .maplist-portals { | ||
| 220 | margin-left: 5px; | ||
| 221 | font-size: 32px; | ||
| 222 | } | ||
| 223 | |||
| 224 | .difficulty-div { | ||
| 225 | display: flex; | ||
| 226 | padding: 7px 10px; | ||
| 227 | } | ||
| 228 | |||
| 229 | .difficulty-label { | ||
| 230 | font-size: 18px; | ||
| 231 | } | ||
| 232 | |||
| 233 | .difficulty-bar { | ||
| 234 | width: 100%; | ||
| 235 | display: grid; | ||
| 236 | grid-template-columns: 20% 20% 20% 20% 20%; | ||
| 237 | align-items: center; | ||
| 238 | margin: 5px; | ||
| 239 | } | ||
| 240 | |||
| 241 | .difficulty-point { | ||
| 242 | background: #2B2E46; | ||
| 243 | height: 3px; | ||
| 244 | margin: 5px; | ||
| 245 | border-radius: 10px; | ||
| 246 | } | ||
| 247 | |||
| 248 | .stats { | ||
| 249 | margin-top: 30px; | ||
| 250 | } | ||
| 251 | |||
| 252 | .portalcount-over-time-div { | ||
| 253 | width: 100%; | ||
| 254 | height: 450px; | ||
| 255 | position: relative; | ||
| 256 | background-color: #202232; | ||
| 257 | border-radius: 20px; | ||
| 258 | } | ||
| 259 | |||
| 260 | .graph-title { | ||
| 261 | width: 100%; | ||
| 262 | display: inherit; | ||
| 263 | font-size: 24px; | ||
| 264 | margin-top: 5px; | ||
| 265 | text-align: center; | ||
| 266 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 267 | padding-top: 7px; | ||
| 268 | } | ||
| 269 | |||
| 270 | .portalcount-graph { | ||
| 271 | height: calc(100% - 30px); | ||
| 272 | width: calc(100% - 80px); | ||
| 273 | } | ||
| 274 | |||
| 275 | .chart { | ||
| 276 | height: calc(100% - 80px); | ||
| 277 | width: 100%; | ||
| 278 | position: relative; | ||
| 279 | padding: 0px 0px; | ||
| 280 | scrollbar-width: thin; | ||
| 281 | } | ||
| 282 | |||
| 283 | .line-chart { | ||
| 284 | list-style: none; | ||
| 285 | margin: 0; | ||
| 286 | padding: 0; | ||
| 287 | height: 100%; | ||
| 288 | border-bottom: 2px solid #2B2E46; | ||
| 289 | } | ||
| 290 | |||
| 291 | .data-point { | ||
| 292 | background-color: #202232; | ||
| 293 | border: 4px solid #006FDE; | ||
| 294 | border-radius: 50%; | ||
| 295 | height: 6px; | ||
| 296 | position: absolute; | ||
| 297 | width: 6px; | ||
| 298 | bottom: calc(var(--y) - 4.5px); | ||
| 299 | left: calc(var(--x) - 6.5px); | ||
| 300 | transition: all 0.2s cubic-bezier(0.075, 0.82, 0.165, 1); | ||
| 301 | z-index: 1; | ||
| 302 | animation: point_intro 0.2s cubic-bezier(0.075, 0.82, 0.165, 1.8); | ||
| 303 | animation-fill-mode: backwards; | ||
| 304 | } | ||
| 305 | |||
| 306 | .data-point:hover, .data-point-active { | ||
| 307 | background-color: #006FDE; | ||
| 308 | box-shadow: 0px 0px 10px #006FDE; | ||
| 309 | } | ||
| 310 | |||
| 311 | .line-segment { | ||
| 312 | background-color: #006FDE; | ||
| 313 | bottom: var(--y); | ||
| 314 | height: 4px; | ||
| 315 | left: var(--x); | ||
| 316 | position: absolute; | ||
| 317 | transform: rotate(calc(var(--angle) * -1deg)); | ||
| 318 | width: calc(var(--hypotenuse) * 1px); | ||
| 319 | transform-origin: left bottom; | ||
| 320 | border-radius: 20px; | ||
| 321 | z-index: 1; | ||
| 322 | animation: line_intro 0.05s cubic-bezier(0, 1, 0.31, 0.96); | ||
| 323 | animation-fill-mode: backwards; | ||
| 324 | } | ||
| 325 | |||
| 326 | #dataPointInfo { | ||
| 327 | position: absolute; | ||
| 328 | width: 400px; | ||
| 329 | height: 85px; | ||
| 330 | background: #202232; | ||
| 331 | box-shadow: 0px 4px 16px 0px #00000080; | ||
| 332 | transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1); | ||
| 333 | z-index: 1000; | ||
| 334 | opacity: 0; | ||
| 335 | left: auto; | ||
| 336 | border-radius: 20px; | ||
| 337 | padding: 15px 7px; | ||
| 338 | } | ||
| 339 | |||
| 340 | .section-header { | ||
| 341 | display: flex; | ||
| 342 | text-align: center; | ||
| 343 | font-family: BarlowSemiCondensed-SemiBold; | ||
| 344 | font-size: 18px; | ||
| 345 | height: 40%; | ||
| 346 | justify-content: space-evenly; | ||
| 347 | align-items: center; | ||
| 348 | } | ||
| 349 | |||
| 350 | .section-header span, .section-data span { | ||
| 351 | flex: 1; | ||
| 352 | } | ||
| 353 | |||
| 354 | .divider { | ||
| 355 | width: 100%; | ||
| 356 | height: 2px; | ||
| 357 | background-color: #2B2E46; | ||
| 358 | display: flex; | ||
| 359 | margin: 5px 0px 8px 0px; | ||
| 360 | } | ||
| 361 | |||
| 362 | .section-data { | ||
| 363 | display: flex; | ||
| 364 | grid-template-columns: 25% 25% 25% 25%; | ||
| 365 | text-align: center; | ||
| 366 | background-color: #2B2E46; | ||
| 367 | height: 52%; | ||
| 368 | border-radius: 200px; | ||
| 369 | align-items: center; | ||
| 370 | justify-content: space-evenly; | ||
| 371 | flex-grow: 1; | ||
| 372 | font-family: BarlowSemiCondensed-Regular; | ||
| 373 | font-size: 18px; | ||
| 374 | padding: 0px 5px; | ||
| 375 | } | ||
| 376 | |||
| 377 | @keyframes line_intro { | ||
| 378 | 0% { | ||
| 379 | width: 0; | ||
| 380 | } | ||
| 381 | 100% { | ||
| 382 | width: calc(var(--hypotenuse) * 1px); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | @keyframes point_intro { | ||
| 387 | 0% { | ||
| 388 | opacity: 0; | ||
| 389 | width: 0; | ||
| 390 | height: 0; | ||
| 391 | transform: translate(3px, -3px); | ||
| 392 | } | ||
| 393 | 100% { | ||
| 394 | width: 6px; | ||
| 395 | height: 6px; | ||
| 396 | transform: translate(0px, 0px); | ||
| 397 | opacity: 1; | ||
| 398 | } | ||
| 399 | } | ||
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 @@ | |||
| 1 | import React, { useEffect, useRef, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | import { BrowserRouter as Router, Route, Routes, useNavigate } from 'react-router-dom'; | ||
| 4 | |||
| 5 | import "./maplist.css" | ||
| 6 | import img5 from "../../imgs/5.png" | ||
| 7 | import img6 from "../../imgs/6.png" | ||
| 8 | |||
| 9 | export default function Maplist(prop) { | ||
| 10 | const {token,setToken} = prop | ||
| 11 | const scrollRef = useRef(null) | ||
| 12 | const [games, setGames] = React.useState(null); | ||
| 13 | const location = useLocation(); | ||
| 14 | |||
| 15 | let gameTitle; | ||
| 16 | let minPage; | ||
| 17 | let maxPage; | ||
| 18 | let currentPage; | ||
| 19 | let add = 0; | ||
| 20 | let gameState; | ||
| 21 | let catState = 0; | ||
| 22 | async function detectGame() { | ||
| 23 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 24 | headers: { | ||
| 25 | 'Authorization': token | ||
| 26 | } | ||
| 27 | }); | ||
| 28 | |||
| 29 | const data = await response.json(); | ||
| 30 | |||
| 31 | const url = new URL(window.location.href) | ||
| 32 | |||
| 33 | const params = new URLSearchParams(url.search) | ||
| 34 | gameState = parseFloat(params.get("game")) | ||
| 35 | |||
| 36 | document.querySelector("#catPortalCount").innerText = data.data[gameState - 1].category_portals[0].portal_count; | ||
| 37 | |||
| 38 | if (gameState == 1){ | ||
| 39 | gameTitle = data.data[0].name; | ||
| 40 | |||
| 41 | maxPage = 9; | ||
| 42 | minPage = 1; | ||
| 43 | createCategories(1); | ||
| 44 | } else if (gameState == 2){ | ||
| 45 | gameTitle = data.data[1].name; | ||
| 46 | |||
| 47 | maxPage = 16; | ||
| 48 | minPage = 10; | ||
| 49 | add = 10 | ||
| 50 | createCategories(2); | ||
| 51 | } | ||
| 52 | |||
| 53 | let chapterParam = params.get("chapter") | ||
| 54 | |||
| 55 | currentPage = minPage; | ||
| 56 | |||
| 57 | if (chapterParam) { | ||
| 58 | currentPage = +chapterParam + add | ||
| 59 | } | ||
| 60 | |||
| 61 | changePage(currentPage); | ||
| 62 | |||
| 63 | // if (chapterParam) { | ||
| 64 | // document.querySelector("#pageNumbers").innerText = `${chapterParam - minPage + 1}/${maxPage - minPage + 1}` | ||
| 65 | // } | ||
| 66 | } | ||
| 67 | |||
| 68 | function changeMaplistOrStatistics(index, name) { | ||
| 69 | const maplistBtns = document.querySelectorAll("#maplistBtn"); | ||
| 70 | maplistBtns.forEach((btn, i) => { | ||
| 71 | if (i == index) { | ||
| 72 | btn.className = "game-nav-btn selected" | ||
| 73 | |||
| 74 | if (name == "maplist") { | ||
| 75 | document.querySelector(".stats").style.display = "none"; | ||
| 76 | document.querySelector(".maplist").style.display = "block"; | ||
| 77 | document.querySelector(".maplist").setAttribute("currentTab", "maplist"); | ||
| 78 | } else { | ||
| 79 | document.querySelector(".stats").style.display = "block"; | ||
| 80 | document.querySelector(".maplist").style.display = "none"; | ||
| 81 | |||
| 82 | document.querySelector(".maplist-page").scrollTo({ top: 372, behavior: "smooth" }) | ||
| 83 | document.querySelector(".maplist").setAttribute("currentTab", "stats"); | ||
| 84 | } | ||
| 85 | } else { | ||
| 86 | btn.className = "game-nav-btn"; | ||
| 87 | } | ||
| 88 | }); | ||
| 89 | } | ||
| 90 | |||
| 91 | async function createCategories(gameID) { | ||
| 92 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 93 | headers: { | ||
| 94 | 'Authorization': token | ||
| 95 | } | ||
| 96 | }); | ||
| 97 | |||
| 98 | const data = await response.json(); | ||
| 99 | let categoriesArr = data.data[gameID - 1].category_portals; | ||
| 100 | |||
| 101 | const gameNav = document.querySelector(".game-nav"); | ||
| 102 | gameNav.innerHTML = ""; | ||
| 103 | categoriesArr.forEach((category) => { | ||
| 104 | createCategory(category); | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | |||
| 108 | let categoryNum = 0; | ||
| 109 | function createCategory(category) { | ||
| 110 | const gameNav = document.querySelector(".game-nav"); | ||
| 111 | |||
| 112 | categoryNum++; | ||
| 113 | const gameNavBtn = document.createElement("button"); | ||
| 114 | if (categoryNum == 1) { | ||
| 115 | gameNavBtn.className = "game-nav-btn selected"; | ||
| 116 | } else { | ||
| 117 | gameNavBtn.className = "game-nav-btn"; | ||
| 118 | } | ||
| 119 | gameNavBtn.id = "catBtn" | ||
| 120 | gameNavBtn.innerText = category.category.name; | ||
| 121 | |||
| 122 | gameNavBtn.addEventListener("click", (e) => { | ||
| 123 | changeCategory(category, e); | ||
| 124 | changePage(currentPage); | ||
| 125 | }) | ||
| 126 | |||
| 127 | gameNav.appendChild(gameNavBtn); | ||
| 128 | } | ||
| 129 | |||
| 130 | async function changeCategory(category, btn) { | ||
| 131 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 132 | headers: { | ||
| 133 | 'Authorization': token | ||
| 134 | } | ||
| 135 | }); | ||
| 136 | |||
| 137 | const data = await response.json(); | ||
| 138 | catState = category.category.id - 1; | ||
| 139 | console.log(catState) | ||
| 140 | const navBtns = document.querySelectorAll("#catBtn"); | ||
| 141 | navBtns.forEach((btns) => { | ||
| 142 | btns.classList.remove("selected"); | ||
| 143 | }); | ||
| 144 | |||
| 145 | btn.srcElement.classList.add("selected"); | ||
| 146 | document.querySelector("#catPortalCount").innerText = category.portal_count; | ||
| 147 | } | ||
| 148 | |||
| 149 | async function changePage(page) { | ||
| 150 | |||
| 151 | const pageNumbers = document.querySelector("#pageNumbers"); | ||
| 152 | |||
| 153 | pageNumbers.innerText = `${currentPage - minPage + 1}/${maxPage - minPage + 1}`; | ||
| 154 | |||
| 155 | const maplistMaps = document.querySelector(".maplist-maps"); | ||
| 156 | maplistMaps.innerHTML = ""; | ||
| 157 | for (let index = 0; index < 8; index++) { | ||
| 158 | const loadingAnimation = document.createElement("div"); | ||
| 159 | loadingAnimation.classList.add("loader"); | ||
| 160 | loadingAnimation.classList.add("loader-map") | ||
| 161 | maplistMaps.appendChild(loadingAnimation); | ||
| 162 | } | ||
| 163 | const data = await fetchMaps(page); | ||
| 164 | const maps = data.data.maps; | ||
| 165 | const name = data.data.chapter.name; | ||
| 166 | |||
| 167 | let chapterName = "Chapter"; | ||
| 168 | const chapterNumberOld = name.split(" - ")[0]; | ||
| 169 | let chapterNumber1 = chapterNumberOld.split("Chapter ")[1]; | ||
| 170 | if (chapterNumber1 == undefined) { | ||
| 171 | chapterName = "Course" | ||
| 172 | chapterNumber1 = chapterNumberOld.split("Course ")[1]; | ||
| 173 | } | ||
| 174 | const chapterNumber = chapterNumber1.toString().padStart(2, "0"); | ||
| 175 | const chapterTitle = name.split(" - ")[1]; | ||
| 176 | |||
| 177 | const chapterNumberElement = document.querySelector(".chapter-num") | ||
| 178 | const chapterTitleElement = document.querySelector(".chapter-name") | ||
| 179 | chapterNumberElement.innerText = chapterName + " " + chapterNumber; | ||
| 180 | chapterTitleElement.innerText = chapterTitle; | ||
| 181 | |||
| 182 | maplistMaps.innerHTML = ""; | ||
| 183 | maps.forEach(map => { | ||
| 184 | let portalCount; | ||
| 185 | if (map.category_portals[catState] != undefined) { | ||
| 186 | portalCount = map.category_portals[catState].portal_count; | ||
| 187 | } else { | ||
| 188 | portalCount = map.category_portals[0].portal_count; | ||
| 189 | } | ||
| 190 | addMap(map.name, portalCount, map.image, map.difficulty + 1, map.id); | ||
| 191 | }); | ||
| 192 | |||
| 193 | const gameTitleElement = document.querySelector("#gameTitle"); | ||
| 194 | gameTitleElement.innerText = gameTitle; | ||
| 195 | |||
| 196 | const url = new URL(window.location.href) | ||
| 197 | |||
| 198 | const params = new URLSearchParams(url.search) | ||
| 199 | |||
| 200 | let chapterParam = params.get("chapter") | ||
| 201 | |||
| 202 | try { | ||
| 203 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 204 | headers: { | ||
| 205 | 'Authorization': token | ||
| 206 | } | ||
| 207 | }); | ||
| 208 | |||
| 209 | const data = await response.json(); | ||
| 210 | |||
| 211 | const gameImg = document.querySelector(".game-img"); | ||
| 212 | |||
| 213 | gameImg.style.backgroundImage = `url(${data.data[0].image})`; | ||
| 214 | |||
| 215 | // const mapImg = document.querySelectorAll(".maplist-img"); | ||
| 216 | // mapImg.forEach((map) => { | ||
| 217 | // map.style.backgroundImage = `url(${data.data[0].image})`; | ||
| 218 | // }); | ||
| 219 | |||
| 220 | } catch (error) { | ||
| 221 | console.log("error fetching games:", error); | ||
| 222 | } | ||
| 223 | |||
| 224 | asignDifficulties(); | ||
| 225 | } | ||
| 226 | |||
| 227 | async function addMap(mapName, mapPortalCount, mapImage, difficulty, mapID) { | ||
| 228 | // jesus christ | ||
| 229 | const maplistItem = document.createElement("div"); | ||
| 230 | const maplistTitle = document.createElement("span"); | ||
| 231 | const maplistImgDiv = document.createElement("div"); | ||
| 232 | const maplistImg = document.createElement("div"); | ||
| 233 | const maplistPortalcountDiv = document.createElement("div"); | ||
| 234 | const maplistPortalcount = document.createElement("span"); | ||
| 235 | const b = document.createElement("b"); | ||
| 236 | const maplistPortalcountPortals = document.createElement("span"); | ||
| 237 | const difficultyDiv = document.createElement("div"); | ||
| 238 | const difficultyLabel = document.createElement("span"); | ||
| 239 | const difficultyBar = document.createElement("div"); | ||
| 240 | const difficultyPoint1 = document.createElement("div"); | ||
| 241 | const difficultyPoint2 = document.createElement("div"); | ||
| 242 | const difficultyPoint3 = document.createElement("div"); | ||
| 243 | const difficultyPoint4 = document.createElement("div"); | ||
| 244 | const difficultyPoint5 = document.createElement("div"); | ||
| 245 | |||
| 246 | maplistItem.className = "maplist-item"; | ||
| 247 | maplistTitle.className = "maplist-title"; | ||
| 248 | maplistImgDiv.className = "maplist-img-div"; | ||
| 249 | maplistImg.className = "maplist-img"; | ||
| 250 | maplistPortalcountDiv.className = "maplist-portalcount-div"; | ||
| 251 | maplistPortalcount.className = "maplist-portalcount"; | ||
| 252 | maplistPortalcountPortals.className = "maplist-portals"; | ||
| 253 | difficultyDiv.className = "difficulty-div"; | ||
| 254 | difficultyLabel.className = "difficulty-label"; | ||
| 255 | difficultyBar.className = "difficulty-bar"; | ||
| 256 | difficultyPoint1.className = "difficulty-point"; | ||
| 257 | difficultyPoint2.className = "difficulty-point"; | ||
| 258 | difficultyPoint3.className = "difficulty-point"; | ||
| 259 | difficultyPoint4.className = "difficulty-point"; | ||
| 260 | difficultyPoint5.className = "difficulty-point"; | ||
| 261 | |||
| 262 | |||
| 263 | maplistTitle.innerText = mapName; | ||
| 264 | difficultyLabel.innerText = "Difficulty: " | ||
| 265 | maplistPortalcountPortals.innerText = "portals" | ||
| 266 | b.innerText = mapPortalCount; | ||
| 267 | maplistImg.style.backgroundImage = `url(${mapImage})`; | ||
| 268 | difficultyBar.setAttribute("difficulty", difficulty) | ||
| 269 | maplistItem.setAttribute("id", mapID) | ||
| 270 | maplistItem.addEventListener("click", () => { | ||
| 271 | console.log(mapID) | ||
| 272 | window.location.href = "/maps/" + mapID | ||
| 273 | }) | ||
| 274 | |||
| 275 | // appends | ||
| 276 | // maplist item | ||
| 277 | maplistItem.appendChild(maplistTitle); | ||
| 278 | maplistImgDiv.appendChild(maplistImg); | ||
| 279 | maplistImgDiv.appendChild(maplistPortalcountDiv); | ||
| 280 | maplistPortalcountDiv.appendChild(maplistPortalcount); | ||
| 281 | maplistPortalcount.appendChild(b); | ||
| 282 | maplistPortalcountDiv.appendChild(maplistPortalcountPortals); | ||
| 283 | maplistItem.appendChild(maplistImgDiv); | ||
| 284 | maplistItem.appendChild(difficultyDiv); | ||
| 285 | difficultyDiv.appendChild(difficultyLabel); | ||
| 286 | difficultyDiv.appendChild(difficultyBar); | ||
| 287 | difficultyBar.appendChild(difficultyPoint1); | ||
| 288 | difficultyBar.appendChild(difficultyPoint2); | ||
| 289 | difficultyBar.appendChild(difficultyPoint3); | ||
| 290 | difficultyBar.appendChild(difficultyPoint4); | ||
| 291 | difficultyBar.appendChild(difficultyPoint5); | ||
| 292 | |||
| 293 | // display in place | ||
| 294 | const maplistMaps = document.querySelector(".maplist-maps"); | ||
| 295 | maplistMaps.appendChild(maplistItem); | ||
| 296 | } | ||
| 297 | |||
| 298 | async function fetchMaps(chapterID) { | ||
| 299 | try{ | ||
| 300 | const response = await fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapterID}`, { | ||
| 301 | headers: { | ||
| 302 | 'Authorization': token | ||
| 303 | } | ||
| 304 | }); | ||
| 305 | |||
| 306 | const data = await response.json(); | ||
| 307 | return data; | ||
| 308 | } catch (err) { | ||
| 309 | console.log(err) | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | // difficulty stuff | ||
| 314 | function asignDifficulties() { | ||
| 315 | const difficulties = document.querySelectorAll(".difficulty-bar"); | ||
| 316 | difficulties.forEach((difficultyElement) => { | ||
| 317 | let difficulty = difficultyElement.getAttribute("difficulty"); | ||
| 318 | if (difficulty == "1") { | ||
| 319 | difficultyElement.childNodes[0].style.backgroundColor = "#51C355"; | ||
| 320 | } else if (difficulty == "2") { | ||
| 321 | difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A"; | ||
| 322 | difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A"; | ||
| 323 | } else if (difficulty == "3") { | ||
| 324 | difficultyElement.childNodes[0].style.backgroundColor = "#8AC93A"; | ||
| 325 | difficultyElement.childNodes[1].style.backgroundColor = "#8AC93A"; | ||
| 326 | difficultyElement.childNodes[2].style.backgroundColor = "#8AC93A"; | ||
| 327 | } else if (difficulty == "4") { | ||
| 328 | difficultyElement.childNodes[0].style.backgroundColor = "#C35F51"; | ||
| 329 | difficultyElement.childNodes[1].style.backgroundColor = "#C35F51"; | ||
| 330 | difficultyElement.childNodes[2].style.backgroundColor = "#C35F51"; | ||
| 331 | difficultyElement.childNodes[3].style.backgroundColor = "#C35F51"; | ||
| 332 | } else if (difficulty == "5") { | ||
| 333 | difficultyElement.childNodes[0].style.backgroundColor = "#C35F51"; | ||
| 334 | difficultyElement.childNodes[1].style.backgroundColor = "#C35F51"; | ||
| 335 | difficultyElement.childNodes[2].style.backgroundColor = "#C35F51"; | ||
| 336 | difficultyElement.childNodes[3].style.backgroundColor = "#C35F51"; | ||
| 337 | difficultyElement.childNodes[4].style.backgroundColor = "#C35F51"; | ||
| 338 | } | ||
| 339 | }); | ||
| 340 | } | ||
| 341 | |||
| 342 | const divRef = useRef(null); | ||
| 343 | |||
| 344 | React.useEffect(() => { | ||
| 345 | |||
| 346 | const lineChart = document.querySelector(".line-chart") | ||
| 347 | function createGraph() { | ||
| 348 | // max | ||
| 349 | let items = [ | ||
| 350 | { | ||
| 351 | record: "100", | ||
| 352 | date: new Date(2011, 4, 4), | ||
| 353 | map: "Container Ride", | ||
| 354 | first: "tiny zach" | ||
| 355 | }, | ||
| 356 | { | ||
| 357 | record: "98", | ||
| 358 | date: new Date(2012, 6, 4), | ||
| 359 | map: "Container Ride", | ||
| 360 | first: "tiny zach" | ||
| 361 | }, | ||
| 362 | { | ||
| 363 | record: "94", | ||
| 364 | date: new Date(2013, 0, 1), | ||
| 365 | map: "Container Ride", | ||
| 366 | first: "tiny zach" | ||
| 367 | }, | ||
| 368 | { | ||
| 369 | record: "90", | ||
| 370 | date: new Date(2014, 0, 1), | ||
| 371 | map: "Container Ride", | ||
| 372 | first: "tiny zach" | ||
| 373 | }, | ||
| 374 | { | ||
| 375 | record: "88", | ||
| 376 | date: new Date(2015, 6, 14), | ||
| 377 | map: "Container Ride", | ||
| 378 | first: "tiny zach" | ||
| 379 | }, | ||
| 380 | { | ||
| 381 | record: "84", | ||
| 382 | date: new Date(2016, 8, 19), | ||
| 383 | map: "Container Ride", | ||
| 384 | first: "tiny zach" | ||
| 385 | }, | ||
| 386 | { | ||
| 387 | record: "82", | ||
| 388 | date: new Date(2017, 3, 20), | ||
| 389 | map: "Container Ride", | ||
| 390 | first: "tiny zach" | ||
| 391 | }, | ||
| 392 | { | ||
| 393 | record: "81", | ||
| 394 | date: new Date(2018, 2, 25), | ||
| 395 | map: "Container Ride", | ||
| 396 | first: "tiny zach" | ||
| 397 | }, | ||
| 398 | { | ||
| 399 | record: "80", | ||
| 400 | date: new Date(2019, 3, 4), | ||
| 401 | map: "Container Ride", | ||
| 402 | first: "tiny zach" | ||
| 403 | }, | ||
| 404 | { | ||
| 405 | record: "78", | ||
| 406 | date: new Date(2020, 11, 21), | ||
| 407 | map: "Container Ride", | ||
| 408 | first: "tiny zach" | ||
| 409 | }, | ||
| 410 | { | ||
| 411 | record: "77", | ||
| 412 | date: new Date(2021, 10, 25), | ||
| 413 | map: "Container Ride", | ||
| 414 | first: "tiny zach" | ||
| 415 | }, | ||
| 416 | { | ||
| 417 | record: "76", | ||
| 418 | date: new Date(2022, 4, 17), | ||
| 419 | map: "Container Ride", | ||
| 420 | first: "tiny zach" | ||
| 421 | }, | ||
| 422 | { | ||
| 423 | record: "75", | ||
| 424 | date: new Date(2023, 9, 31), | ||
| 425 | map: "Container Ride", | ||
| 426 | first: "tiny zach" | ||
| 427 | }, | ||
| 428 | { | ||
| 429 | record: "74", | ||
| 430 | date: new Date(2024, 4, 4), | ||
| 431 | map: "Container Ride", | ||
| 432 | first: "tiny zach" | ||
| 433 | }, | ||
| 434 | ] | ||
| 435 | |||
| 436 | function calculatePosition(date, startDate, endDate, maxWidth) { | ||
| 437 | const totalMilliseconds = endDate - startDate + 10000000000; | ||
| 438 | const millisecondsFromStart = date - startDate + 5000000000; | ||
| 439 | return (millisecondsFromStart / totalMilliseconds) * maxWidth | ||
| 440 | } | ||
| 441 | |||
| 442 | const minDate = items.reduce((min, dp) => dp.date < min ? dp.date : min, items[0].date) | ||
| 443 | const maxDate = items.reduce((max, dp) => dp.date > max ? dp.date : max, items[0].date) | ||
| 444 | |||
| 445 | const graph_width = document.querySelector(".portalcount-over-time-div").clientWidth | ||
| 446 | // console.log(graph_width) | ||
| 447 | |||
| 448 | const uniqueYears = new Set() | ||
| 449 | items.forEach(dp => uniqueYears.add(dp.date.getFullYear())) | ||
| 450 | let minYear = Infinity; | ||
| 451 | let maxYear = -Infinity; | ||
| 452 | |||
| 453 | items.forEach(dp => { | ||
| 454 | const year = dp.date.getFullYear(); | ||
| 455 | minYear = Math.min(minYear, year); | ||
| 456 | maxYear = Math.max(maxYear, year); | ||
| 457 | }); | ||
| 458 | |||
| 459 | // Add missing years to the set | ||
| 460 | for (let year = minYear; year <= maxYear; year++) { | ||
| 461 | uniqueYears.add(year); | ||
| 462 | } | ||
| 463 | const uniqueYearsArr = Array.from(uniqueYears) | ||
| 464 | |||
| 465 | items = items.map(dp => ({ | ||
| 466 | record: dp.record, | ||
| 467 | date: dp.date, | ||
| 468 | x: calculatePosition(dp.date, minDate, maxDate, lineChart.clientWidth), | ||
| 469 | map: dp.map, | ||
| 470 | first: dp.first | ||
| 471 | })) | ||
| 472 | |||
| 473 | const yearInterval = lineChart.clientWidth / uniqueYears.size | ||
| 474 | for (let index = 1; index < (uniqueYears.size); index++) { | ||
| 475 | const placeholderlmao = document.createElement("div") | ||
| 476 | const yearSpan = document.createElement("span") | ||
| 477 | yearSpan.style.position = "absolute" | ||
| 478 | placeholderlmao.style.height = "100%" | ||
| 479 | placeholderlmao.style.width = "2px" | ||
| 480 | placeholderlmao.style.backgroundColor = "#00000080" | ||
| 481 | placeholderlmao.style.position = `absolute` | ||
| 482 | const thing = calculatePosition(new Date(uniqueYearsArr[index], 0, 0), minDate, maxDate, lineChart.clientWidth) | ||
| 483 | placeholderlmao.style.left = `${thing}px` | ||
| 484 | yearSpan.style.left = `${thing}px` | ||
| 485 | yearSpan.style.bottom = "-34px" | ||
| 486 | yearSpan.innerText = uniqueYearsArr[index] | ||
| 487 | yearSpan.style.fontFamily = "BarlowSemiCondensed-Regular" | ||
| 488 | yearSpan.style.fontSize = "22px" | ||
| 489 | yearSpan.style.opacity = "0.8" | ||
| 490 | lineChart.appendChild(yearSpan) | ||
| 491 | |||
| 492 | } | ||
| 493 | |||
| 494 | let maxPortals; | ||
| 495 | let minPortals; | ||
| 496 | let precision; | ||
| 497 | let multiplier = 1; | ||
| 498 | for (let index = 0; index < items.length; index++) { | ||
| 499 | precision = Math.floor((items[0].record - items[items.length - 1].record)) | ||
| 500 | if (precision > 20) { | ||
| 501 | precision = 20 | ||
| 502 | } | ||
| 503 | minPortals = Math.floor((items[items.length - 1].record) / 10) * 10 | ||
| 504 | if (index == 0) { | ||
| 505 | maxPortals = items[index].record - minPortals | ||
| 506 | } | ||
| 507 | } | ||
| 508 | function calculateMultiplier(value) { | ||
| 509 | while (value > precision) { | ||
| 510 | multiplier += 1; | ||
| 511 | value -= precision; | ||
| 512 | } | ||
| 513 | } | ||
| 514 | calculateMultiplier(items[0].record); | ||
| 515 | // if (items[0].record > 10) { | ||
| 516 | // multiplier = 2; | ||
| 517 | // } | ||
| 518 | |||
| 519 | // Original cubic bezier control points | ||
| 520 | const P0 = { x: 0, y: 0 }; | ||
| 521 | const P1 = { x: 0.26, y: 1 }; | ||
| 522 | const P2 = { x: 0.74, y: 1 }; | ||
| 523 | const P3 = { x: 1, y: 0 }; | ||
| 524 | |||
| 525 | function calculateIntermediateControlPoints(t, P0, P1, P2, P3) { | ||
| 526 | const x = (1 - t) ** 3 * P0.x + | ||
| 527 | 3 * (1 - t) ** 2 * t * P1.x + | ||
| 528 | 3 * (1 - t) * t ** 2 * P2.x + | ||
| 529 | t ** 3 * P3.x; | ||
| 530 | |||
| 531 | const y = (1 - t) ** 3 * P0.y + | ||
| 532 | 3 * (1 - t) ** 2 * t * P1.y + | ||
| 533 | 3 * (1 - t) * t ** 2 * P2.y + | ||
| 534 | t ** 3 * P3.y; | ||
| 535 | |||
| 536 | return { x, y }; | ||
| 537 | } | ||
| 538 | |||
| 539 | |||
| 540 | let delay = 0; | ||
| 541 | for (let index = 0; index < items.length; index++) { | ||
| 542 | let chart_height = 340; | ||
| 543 | const item = items[index]; | ||
| 544 | delay += 0.05; | ||
| 545 | // console.log(lineChart.clientWidth) | ||
| 546 | |||
| 547 | // maxPortals++; | ||
| 548 | // maxPortals++; | ||
| 549 | |||
| 550 | let point_height = (chart_height / maxPortals) | ||
| 551 | |||
| 552 | for (let index = 0; index < (maxPortals / multiplier); index++) { | ||
| 553 | // console.log((index + 1) * multiplier) | ||
| 554 | let current_portal_count = (index + 1); | ||
| 555 | |||
| 556 | const placeholderDiv = document.createElement("div") | ||
| 557 | const numPortalsText = document.createElement("span") | ||
| 558 | const numPortalsTextBottom = document.createElement("span") | ||
| 559 | numPortalsText.innerText = (current_portal_count * multiplier) + minPortals | ||
| 560 | numPortalsTextBottom.innerText = minPortals | ||
| 561 | placeholderDiv.style.position = "absolute" | ||
| 562 | numPortalsText.style.position = "absolute" | ||
| 563 | numPortalsTextBottom.style.position = "absolute" | ||
| 564 | numPortalsText.style.left = "-37px" | ||
| 565 | numPortalsText.style.opacity = "0.2" | ||
| 566 | numPortalsTextBottom.style.opacity = "0.2" | ||
| 567 | numPortalsText.style.fontFamily = "BarlowSemiCondensed-Regular" | ||
| 568 | numPortalsTextBottom.style.fontFamily = "BarlowSemiCondensed-Regular" | ||
| 569 | numPortalsText.style.fontSize = "22px" | ||
| 570 | numPortalsTextBottom.style.left = "-37px" | ||
| 571 | numPortalsTextBottom.style.fontSize = "22px" | ||
| 572 | numPortalsTextBottom.style.fontWeight = "400" | ||
| 573 | numPortalsText.style.color = "#CDCFDF" | ||
| 574 | numPortalsTextBottom.style.color = "#CDCFDF" | ||
| 575 | numPortalsText.style.fontFamily = "inherit" | ||
| 576 | numPortalsTextBottom.style.fontFamily = "inherit" | ||
| 577 | numPortalsText.style.textAlign = "right" | ||
| 578 | numPortalsTextBottom.style.textAlign = "right" | ||
| 579 | numPortalsText.style.width = "30px" | ||
| 580 | numPortalsTextBottom.style.width = "30px" | ||
| 581 | placeholderDiv.style.bottom = `${(point_height * current_portal_count * multiplier) - 2}px` | ||
| 582 | numPortalsText.style.bottom = `${(point_height * current_portal_count * multiplier) - 2 - 9}px` | ||
| 583 | numPortalsTextBottom.style.bottom = `${0 - 2 - 8}px` | ||
| 584 | placeholderDiv.id = placeholderDiv.style.bottom | ||
| 585 | placeholderDiv.style.width = "100%" | ||
| 586 | placeholderDiv.style.height = "2px" | ||
| 587 | placeholderDiv.style.backgroundColor = "#2B2E46" | ||
| 588 | placeholderDiv.style.zIndex = "0" | ||
| 589 | |||
| 590 | if (index == 0) { | ||
| 591 | lineChart.appendChild(numPortalsTextBottom) | ||
| 592 | } | ||
| 593 | lineChart.appendChild(numPortalsText) | ||
| 594 | lineChart.appendChild(placeholderDiv) | ||
| 595 | } | ||
| 596 | |||
| 597 | const li = document.createElement("li"); | ||
| 598 | const lineSeg = document.createElement("div"); | ||
| 599 | const dataPoint = document.createElement("div"); | ||
| 600 | |||
| 601 | li.style = `--y: ${point_height * (item.record - minPortals) - 3}px; --x: ${item.x}px`; | ||
| 602 | lineSeg.className = "line-segment"; | ||
| 603 | dataPoint.className = "data-point"; | ||
| 604 | |||
| 605 | if (items[index + 1] !== undefined) { | ||
| 606 | const hypotenuse = Math.sqrt( | ||
| 607 | Math.pow(items[index + 1].x - items[index].x, 2) + | ||
| 608 | Math.pow((point_height * items[index + 1].record) - point_height * item.record, 2) | ||
| 609 | ); | ||
| 610 | const angle = Math.asin( | ||
| 611 | ((point_height * item.record) - (point_height * items[index + 1].record)) / hypotenuse | ||
| 612 | ); | ||
| 613 | |||
| 614 | lineSeg.style = `--hypotenuse: ${hypotenuse}; --angle: ${angle * (-180 / Math.PI)}`; | ||
| 615 | const t0 = index / items.length; | ||
| 616 | const t1 = (index + 1) / items.length | ||
| 617 | |||
| 618 | const P0t0 = calculateIntermediateControlPoints(t0, P0, P1, P2, P3); | ||
| 619 | const P1t1 = calculateIntermediateControlPoints(t1, P0, P1, P2, P3); | ||
| 620 | const bezierStyle = `cubic-bezier(${P0t0.x.toFixed(3)}, ${P0t0.y.toFixed(3)}, ${P1t1.x.toFixed(3)}, ${P1t1.y.toFixed(3)})` | ||
| 621 | lineSeg.style.animationTimingFunction = bezierStyle | ||
| 622 | lineSeg.style.animationDelay = delay + "s" | ||
| 623 | } | ||
| 624 | dataPoint.style.animationDelay = delay + "s" | ||
| 625 | |||
| 626 | let isHoveringOverData = false; | ||
| 627 | let isDataActive = false; | ||
| 628 | document.querySelector("#dataPointInfo").style.left = item.x + "px"; | ||
| 629 | document.querySelector("#dataPointInfo").style.bottom = (point_height * item.record -3) + "px"; | ||
| 630 | dataPoint.addEventListener("mouseenter", (e) => { | ||
| 631 | isDataActive = true; | ||
| 632 | isHoveringOverData = true; | ||
| 633 | const dataPoints = document.querySelectorAll(".data-point") | ||
| 634 | dataPoints.forEach(point => { | ||
| 635 | point.classList.remove("data-point-active") | ||
| 636 | }); | ||
| 637 | dataPoint.classList.add("data-point-active") | ||
| 638 | document.querySelector("#dataPointRecord").innerText = item.record; | ||
| 639 | document.querySelector("#dataPointMap").innerText = item.map; | ||
| 640 | document.querySelector("#dataPointDate").innerText = item.date.toLocaleDateString("en-GB"); | ||
| 641 | document.querySelector("#dataPointFirst").innerText = item.first; | ||
| 642 | if ((lineChart.clientWidth - 400) < item.x) { | ||
| 643 | document.querySelector("#dataPointInfo").style.left = item.x - 400 + "px"; | ||
| 644 | } else { | ||
| 645 | document.querySelector("#dataPointInfo").style.left = item.x + "px"; | ||
| 646 | } | ||
| 647 | if ((lineChart.clientHeight - 115) < (point_height * (item.record - minPortals) -3)) { | ||
| 648 | document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) - 115 + "px"; | ||
| 649 | } else { | ||
| 650 | document.querySelector("#dataPointInfo").style.bottom = (point_height * (item.record - minPortals) -3) + "px"; | ||
| 651 | } | ||
| 652 | document.querySelector("#dataPointInfo").style.opacity = "1"; | ||
| 653 | document.querySelector("#dataPointInfo").style.zIndex = "10"; | ||
| 654 | }); | ||
| 655 | document.querySelector("#dataPointInfo").addEventListener("mouseenter", (e) => { | ||
| 656 | isHoveringOverData = true; | ||
| 657 | }) | ||
| 658 | document.querySelector("#dataPointInfo").addEventListener("mouseleave", (e) => { | ||
| 659 | isHoveringOverData = false; | ||
| 660 | }) | ||
| 661 | document.addEventListener("mousedown", () => { | ||
| 662 | if (!isHoveringOverData) { | ||
| 663 | isDataActive = false | ||
| 664 | dataPoint.classList.remove("data-point-active") | ||
| 665 | document.querySelector("#dataPointInfo").style.opacity = "0"; | ||
| 666 | document.querySelector("#dataPointInfo").style.zIndex = "0"; | ||
| 667 | } | ||
| 668 | }) | ||
| 669 | dataPoint.addEventListener("mouseenter", (e) => { | ||
| 670 | isHoveringOverData = false; | ||
| 671 | }) | ||
| 672 | document.querySelector(".chart").addEventListener("mouseleave", () => { | ||
| 673 | isDataActive = false | ||
| 674 | dataPoint.classList.remove("data-point-active") | ||
| 675 | document.querySelector("#dataPointInfo").style.opacity = "0"; | ||
| 676 | document.querySelector("#dataPointInfo").style.zIndex = "0"; | ||
| 677 | }) | ||
| 678 | |||
| 679 | li.appendChild(lineSeg); | ||
| 680 | li.appendChild(dataPoint); | ||
| 681 | lineChart.appendChild(li); | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | createGraph() | ||
| 686 | |||
| 687 | async function fetchGames() { | ||
| 688 | try { | ||
| 689 | const response = await fetch("https://lp.ardapektezol.com/api/v1/games", { | ||
| 690 | headers: { | ||
| 691 | 'Authorization': token | ||
| 692 | } | ||
| 693 | }); | ||
| 694 | |||
| 695 | const data = await response.json(); | ||
| 696 | |||
| 697 | const gameImg = document.querySelector(".game-img"); | ||
| 698 | |||
| 699 | gameImg.style.backgroundImage = `url(${data.data[0].image})`; | ||
| 700 | |||
| 701 | // const mapImg = document.querySelectorAll(".maplist-img"); | ||
| 702 | // mapImg.forEach((map) => { | ||
| 703 | // map.style.backgroundImage = `url(${data.data[0].image})`; | ||
| 704 | // }); | ||
| 705 | |||
| 706 | } catch (error) { | ||
| 707 | console.log("error fetching games:", error); | ||
| 708 | } | ||
| 709 | } | ||
| 710 | |||
| 711 | detectGame(); | ||
| 712 | |||
| 713 | const maplistImg = document.querySelector("#maplistImg"); | ||
| 714 | maplistImg.src = img5; | ||
| 715 | const statisticsImg = document.querySelector("#statisticsImg"); | ||
| 716 | statisticsImg.src = img6; | ||
| 717 | |||
| 718 | fetchGames(); | ||
| 719 | |||
| 720 | const handleResize = (entries) => { | ||
| 721 | for (let entry of entries) { | ||
| 722 | lineChart.innerHTML = "" | ||
| 723 | createGraph() | ||
| 724 | if (document.querySelector(".maplist").getAttribute("currentTab") == "stats") { | ||
| 725 | document.querySelector(".stats").style.display = "block" | ||
| 726 | } else { | ||
| 727 | document.querySelector(".stats").style.display = "none" | ||
| 728 | } | ||
| 729 | } | ||
| 730 | }; | ||
| 731 | |||
| 732 | const resizeObserver = new ResizeObserver(handleResize); | ||
| 733 | |||
| 734 | // if (scrollRef.current) { | ||
| 735 | // //hi | ||
| 736 | // if (new URLSearchParams(new URL(window.location.href).search).get("chapter")) { | ||
| 737 | // setTimeout(() => { | ||
| 738 | // scrollRef.current.scrollIntoView({ behavior: "smooth", block: "start" }) | ||
| 739 | // }, 200); | ||
| 740 | // } | ||
| 741 | |||
| 742 | // } | ||
| 743 | |||
| 744 | if (divRef.current) { | ||
| 745 | resizeObserver.observe(divRef.current); | ||
| 746 | } | ||
| 747 | |||
| 748 | return () => { | ||
| 749 | if (divRef.current) { | ||
| 750 | resizeObserver.unobserve(divRef.current); | ||
| 751 | } | ||
| 752 | resizeObserver.disconnect(); | ||
| 753 | }; | ||
| 754 | |||
| 755 | |||
| 756 | }) | ||
| 757 | return ( | ||
| 758 | <div ref={divRef} className='maplist-page'> | ||
| 759 | <div className='maplist-page-content'> | ||
| 760 | <section className='maplist-page-header'> | ||
| 761 | <Link to='/games'><button className='nav-btn'> | ||
| 762 | <i className='triangle'></i> | ||
| 763 | <span>Games list</span> | ||
| 764 | </button></Link> | ||
| 765 | <span><b id='gameTitle'> </b></span> | ||
| 766 | </section> | ||
| 767 | |||
| 768 | <div className='game'> | ||
| 769 | <div className='game-header'> | ||
| 770 | <div className='game-img'></div> | ||
| 771 | <div className='game-header-text'> | ||
| 772 | <span><b id='catPortalCount'>0</b></span> | ||
| 773 | <span>portals</span> | ||
| 774 | </div> | ||
| 775 | </div> | ||
| 776 | |||
| 777 | <div className='game-nav'> | ||
| 778 | </div> | ||
| 779 | </div> | ||
| 780 | |||
| 781 | <div className='gameview-nav'> | ||
| 782 | <button id='maplistBtn' onClick={() => {changeMaplistOrStatistics(0, "maplist")}} className='game-nav-btn selected'> | ||
| 783 | <img id='maplistImg'/> | ||
| 784 | <span>Map List</span> | ||
| 785 | </button> | ||
| 786 | <button id='maplistBtn' onClick={() => changeMaplistOrStatistics(1, "stats")} className='game-nav-btn'> | ||
| 787 | <img id='statisticsImg'/> | ||
| 788 | <span>Statistics</span> | ||
| 789 | </button> | ||
| 790 | </div> | ||
| 791 | |||
| 792 | <div ref={scrollRef} className='maplist'> | ||
| 793 | <div className='chapter'> | ||
| 794 | <span className='chapter-num'>undefined</span><br/> | ||
| 795 | <span className='chapter-name'>undefined</span> | ||
| 796 | |||
| 797 | <div className='chapter-page-div'> | ||
| 798 | <button id='pageChanger' onClick={() => { currentPage--; currentPage < minPage ? currentPage = minPage : changePage(currentPage); }}> | ||
| 799 | <i className='triangle'></i> | ||
| 800 | </button> | ||
| 801 | <span id='pageNumbers'>0/0</span> | ||
| 802 | <button id='pageChanger' onClick={() => { currentPage++; currentPage > maxPage ? currentPage = maxPage : changePage(currentPage); }}> | ||
| 803 | <i style={{ transform: "rotate(180deg)" }} className='triangle'></i> | ||
| 804 | </button> | ||
| 805 | </div> | ||
| 806 | |||
| 807 | <div className='maplist-maps'> | ||
| 808 | </div> | ||
| 809 | </div> | ||
| 810 | </div> | ||
| 811 | |||
| 812 | <div style={{display: "block"}} className='stats'> | ||
| 813 | <div className='portalcount-over-time-div'> | ||
| 814 | <span className='graph-title'>Portal count over time</span><br/> | ||
| 815 | |||
| 816 | <div className='portalcount-graph'> | ||
| 817 | <figure className='chart'> | ||
| 818 | <div style={{display: "block"}}></div> | ||
| 819 | <div id="dataPointInfo"> | ||
| 820 | <div className='section-header'> | ||
| 821 | <span className='header-title'>Date</span> | ||
| 822 | <span className='header-title'>Map</span> | ||
| 823 | <span className='header-title'>Record</span> | ||
| 824 | <span className='header-title'>First completion</span> | ||
| 825 | </div> | ||
| 826 | <div className='divider'></div> | ||
| 827 | <div className='section-data'> | ||
| 828 | <span id='dataPointDate'></span> | ||
| 829 | <span id='dataPointMap'></span> | ||
| 830 | <span id='dataPointRecord'></span> | ||
| 831 | <span id='dataPointFirst'>Hello</span> | ||
| 832 | </div> | ||
| 833 | </div> | ||
| 834 | <ul className='line-chart'> | ||
| 835 | |||
| 836 | </ul> | ||
| 837 | </figure> | ||
| 838 | </div> | ||
| 839 | </div> | ||
| 840 | </div> | ||
| 841 | </div> | ||
| 842 | </div> | ||
| 843 | ) | ||
| 844 | } \ 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 @@ | |||
| 35 | border: none; | 35 | border: none; |
| 36 | transition: background-color .1s; | 36 | transition: background-color .1s; |
| 37 | } | 37 | } |
| 38 | #section1>div>.nav-button:nth-child(1){border-radius: 20px 0 0 20px;} | 38 | /* #section1>div>.nav-button:nth-child(1){border-radius: 0px;}:nth-child(1){border-radius: 20px 0 0 20px;} |
| 39 | #section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} | 39 | #section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;} */ |
| 40 | .nav-button>span{padding: 0 8px 0 8px;} | 40 | .nav-button>span{padding: 0 8px 0 8px;} |
| 41 | .nav-button:hover{background-color: #202232;cursor: pointer;} | 41 | .nav-button:hover{background-color: #202232;cursor: pointer;} |
| 42 | 42 | ||
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 @@ | |||
| 1 | import React from 'react'; | 1 | import React, { useEffect } from 'react'; |
| 2 | import { useLocation } from "react-router-dom"; | 2 | import { useLocation, Link } from "react-router-dom"; |
| 3 | import ReactMarkdown from 'react-markdown' | 3 | import ReactMarkdown from 'react-markdown' |
| 4 | 4 | ||
| 5 | import "./summary.css"; | 5 | import "./summary.css"; |
| @@ -317,8 +317,18 @@ if(window.confirm(`Are you sure you want to remove post: ${post.title}?`)){ | |||
| 317 | } | 317 | } |
| 318 | 318 | ||
| 319 | 319 | ||
| 320 | |||
| 321 | if(data!==null){ | 320 | if(data!==null){ |
| 321 | |||
| 322 | let current_chapter = data.map.chapter_name | ||
| 323 | let isCoop = false; | ||
| 324 | if (data.map.game_name == "Portal 2 - Cooperative") { | ||
| 325 | isCoop = true | ||
| 326 | } | ||
| 327 | |||
| 328 | current_chapter = data.map.chapter_name.split(" ") | ||
| 329 | // current_chapter = current_chapter.split("-") | ||
| 330 | current_chapter = current_chapter[1] | ||
| 331 | |||
| 322 | return ( | 332 | return ( |
| 323 | <> | 333 | <> |
| 324 | {token!==null?mod===true?<Modview selectedRun={selectedRun} data={data} token={token}/>:"":""} | 334 | {token!==null?mod===true?<Modview selectedRun={selectedRun} data={data} token={token}/>:"":""} |
| @@ -329,8 +339,8 @@ return ( | |||
| 329 | <main> | 339 | <main> |
| 330 | <section id='section1' className='summary1'> | 340 | <section id='section1' className='summary1'> |
| 331 | <div> | 341 | <div> |
| 332 | <button className='nav-button'><i className='triangle'></i><span>{data.map.game_name}</span></button> | 342 | <Link to="/games"><button className='nav-button' style={{borderRadius: "20px 0px 0px 20px"}}><i className='triangle'></i><span>Games list</span></button></Link> |
| 333 | <button className='nav-button'><i className='triangle'></i><span>{data.map.chapter_name}</span></button> | 343 | <Link to={`/games/${data.map.game_name == "Portal 2 - Singleplayer" ? "game?game=1" : data.map.game_name == "Portal 2 - Cooperative" ? "game?game=2" : "psm"}&chapter=${current_chapter}`}><button className='nav-button' style={{borderRadius: "0px 20px 20px 0px", marginLeft: "2px"}}><i className='triangle'></i><span>{data.map.chapter_name}</span></button></Link> |
| 334 | <br/><span><b>{data.map.map_name}</b></span> | 344 | <br/><span><b>{data.map.map_name}</b></span> |
| 335 | </div> | 345 | </div> |
| 336 | 346 | ||
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 @@ | |||
| 1 | .record-container { | ||
| 2 | --padding: 20px; | ||
| 3 | width: calc(100% - calc(var(--padding * 2))); | ||
| 4 | height: 42px; | ||
| 5 | background-color: #2B2E46; | ||
| 6 | border-radius: 200px; | ||
| 7 | font-size: 18px; | ||
| 8 | display: grid; | ||
| 9 | grid-template-columns: 20% 25% 15% 15% 25%; | ||
| 10 | text-align: center; | ||
| 11 | padding: 0px var(--padding); | ||
| 12 | vertical-align: middle; | ||
| 13 | align-items: center; | ||
| 14 | margin-bottom: 6px; | ||
| 15 | } \ 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 @@ | |||
| 1 | import React, { useEffect, useRef, useState } from 'react'; | ||
| 2 | import { useLocation, Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import "./record.css" | ||
| 5 | |||
| 6 | export default function Record({ name, place, portals, time, date }) { | ||
| 7 | // const {token} = prop; | ||
| 8 | const [record, setRecord] = useState(null); | ||
| 9 | const location = useLocation(); | ||
| 10 | |||
| 11 | useEffect(() => { | ||
| 12 | console.log(name, place, portals, time, date); | ||
| 13 | }) | ||
| 14 | |||
| 15 | function timeSince() { | ||
| 16 | const now = new Date(); | ||
| 17 | const dateNew = new Date(date); | ||
| 18 | |||
| 19 | const secondsPast = Math.floor((now - dateNew) / 1000); | ||
| 20 | console.log('Seconds past:', secondsPast); | ||
| 21 | |||
| 22 | if (secondsPast < 60) { | ||
| 23 | return `${secondsPast} seconds ago`; | ||
| 24 | } | ||
| 25 | if (secondsPast < 3600) { | ||
| 26 | const minutes = Math.floor(secondsPast / 60); | ||
| 27 | return `${minutes} minutes ago`; | ||
| 28 | } | ||
| 29 | if (secondsPast < 86400) { | ||
| 30 | const hours = Math.floor(secondsPast / 3600); | ||
| 31 | return `${hours} hours ago`; | ||
| 32 | } | ||
| 33 | if (secondsPast < 2592000) { | ||
| 34 | const days = Math.floor(secondsPast / 86400); | ||
| 35 | return `${days} days ago`; | ||
| 36 | } | ||
| 37 | if (secondsPast < 31536000) { | ||
| 38 | const months = Math.floor(secondsPast / 2592000); | ||
| 39 | return `${months} months ago`; | ||
| 40 | } | ||
| 41 | const years = Math.floor(secondsPast / 31536000); | ||
| 42 | return `${years} years ago`; | ||
| 43 | } | ||
| 44 | |||
| 45 | return( | ||
| 46 | <div className='record-container'> | ||
| 47 | <span>{place}</span> | ||
| 48 | <div style={{display: "flex", alignItems: "center"}}> | ||
| 49 | <img style={{height: "40px", borderRadius: "200px"}} src="https://avatars.steamstatic.com/32d110951da2339d8b8d8419bc945d9a2b150b2a_full.jpg"></img> | ||
| 50 | <span style={{paddingLeft: "5px", fontFamily: "BarlowSemiCondensed-SemiBold"}}>{name}</span> | ||
| 51 | </div> | ||
| 52 | <span style={{fontFamily: "BarlowCondensed-Bold", color: "#D980FF"}}>{portals}</span> | ||
| 53 | <span>{time}</span> | ||
| 54 | <span>{timeSince()}</span> | ||
| 55 | </div> | ||
| 56 | ) | ||
| 57 | } | ||
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 @@ | |||
| 15 | 15 | ||
| 16 | height: 80px; | 16 | height: 80px; |
| 17 | padding: 20px 0 20px 30px; | 17 | padding: 20px 0 20px 30px; |
| 18 | cursor: pointer; | ||
| 19 | user-select: none; | ||
| 18 | } | 20 | } |
| 19 | 21 | ||
| 20 | #logo-text{ | 22 | #logo-text{ |
| @@ -75,11 +77,24 @@ span>b{ | |||
| 75 | width: 310px; | 77 | width: 310px; |
| 76 | height: 40px; | 78 | height: 40px; |
| 77 | border-radius: 20px; | 79 | border-radius: 20px; |
| 78 | padding: 5px 0 0 6px; | 80 | padding: 0.4em 0 0 11px; |
| 79 | 81 | ||
| 80 | transition: | 82 | transition: |
| 81 | width .3s, | 83 | width .3s, |
| 82 | background-color .3s; | 84 | background-color .15s, |
| 85 | padding .3s; | ||
| 86 | } | ||
| 87 | |||
| 88 | .sidebar-button-selected { | ||
| 89 | background-color: #202232; | ||
| 90 | } | ||
| 91 | |||
| 92 | .sidebar-button-deselected { | ||
| 93 | background-color: #20223200; | ||
| 94 | } | ||
| 95 | |||
| 96 | .sidebar-button-deselected:hover { | ||
| 97 | background-color: #202232aa; | ||
| 83 | } | 98 | } |
| 84 | 99 | ||
| 85 | button>img { | 100 | button>img { |
| @@ -125,6 +140,7 @@ a{text-decoration: none;height: 40px;} | |||
| 125 | margin: 8px 0 8px 0; | 140 | margin: 8px 0 8px 0; |
| 126 | overflow-y: auto; | 141 | overflow-y: auto; |
| 127 | max-height: calc(100vh - 172px); | 142 | max-height: calc(100vh - 172px); |
| 143 | scrollbar-width: thin; | ||
| 128 | } | 144 | } |
| 129 | #search-data::-webkit-scrollbar{display: none;} | 145 | #search-data::-webkit-scrollbar{display: none;} |
| 130 | .search-map{ | 146 | .search-map{ |
| @@ -140,6 +156,7 @@ a{text-decoration: none;height: 40px;} | |||
| 140 | transition: background-color .1s; | 156 | transition: background-color .1s; |
| 141 | background-color: #2b2e46; | 157 | background-color: #2b2e46; |
| 142 | grid-template-rows: 20% 20% 60%; | 158 | grid-template-rows: 20% 20% 60%; |
| 159 | width: calc(100% - 15px); | ||
| 143 | } | 160 | } |
| 144 | .search-map>span{ | 161 | .search-map>span{ |
| 145 | color: #888; | 162 | 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 @@ | |||
| 1 | import React from 'react'; | 1 | import React, { useEffect } from 'react'; |
| 2 | import { Link, useLocation } from "react-router-dom"; | 2 | import { Link, useLocation } from "react-router-dom"; |
| 3 | 3 | ||
| 4 | import "../App.css" | 4 | import "../App.css" |
| @@ -18,6 +18,7 @@ import Login from "./login.js" | |||
| 18 | export default function Sidebar(prop) { | 18 | export default function Sidebar(prop) { |
| 19 | const {token,setToken} = prop | 19 | const {token,setToken} = prop |
| 20 | const [profile, setProfile] = React.useState(null); | 20 | const [profile, setProfile] = React.useState(null); |
| 21 | |||
| 21 | React.useEffect(() => { | 22 | React.useEffect(() => { |
| 22 | fetch(`https://lp.ardapektezol.com/api/v1/profile`,{ | 23 | fetch(`https://lp.ardapektezol.com/api/v1/profile`,{ |
| 23 | headers: { | 24 | headers: { |
| @@ -28,7 +29,6 @@ React.useEffect(() => { | |||
| 28 | .then(d => setProfile(d.data)) | 29 | .then(d => setProfile(d.data)) |
| 29 | }, [token]); | 30 | }, [token]); |
| 30 | 31 | ||
| 31 | |||
| 32 | // Locks search button for 300ms before it can be clicked again, prevents spam | 32 | // Locks search button for 300ms before it can be clicked again, prevents spam |
| 33 | const [isLocked, setIsLocked] = React.useState(false); | 33 | const [isLocked, setIsLocked] = React.useState(false); |
| 34 | function HandleLock(arg) { | 34 | function HandleLock(arg) { |
| @@ -40,34 +40,38 @@ if (!isLocked) { | |||
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | 42 | ||
| 43 | // The menu button | ||
| 44 | const [sidebar, setSidebar] = React.useState(); | ||
| 45 | |||
| 43 | // Clicked buttons | 46 | // Clicked buttons |
| 44 | function SidebarClick(x){ | 47 | function SidebarClick(x){ |
| 45 | const btn = document.querySelectorAll("button.sidebar-button"); | 48 | const btn = document.querySelectorAll("button.sidebar-button"); |
| 46 | 49 | ||
| 47 | if(sidebar===1){setSidebar(0);SidebarHide()} | 50 | if(sidebar===1){setSidebar(0);SidebarHide()} |
| 48 | 51 | ||
| 52 | // clusterfuck | ||
| 49 | btn.forEach((e,i) =>{ | 53 | btn.forEach((e,i) =>{ |
| 50 | btn[i].style.backgroundColor="inherit" | 54 | btn[i].classList.remove("sidebar-button-selected") |
| 55 | btn[i].classList.add("sidebar-button-deselected") | ||
| 51 | }) | 56 | }) |
| 52 | btn[x].style.backgroundColor="#202232" | 57 | btn[x].classList.add("sidebar-button-selected") |
| 58 | btn[x].classList.remove("sidebar-button-deselected") | ||
| 53 | 59 | ||
| 54 | } | 60 | } |
| 55 | 61 | ||
| 56 | |||
| 57 | // The menu button | ||
| 58 | const [sidebar, setSidebar] = React.useState(); | ||
| 59 | |||
| 60 | function SidebarHide(){ | 62 | function SidebarHide(){ |
| 61 | const btn = document.querySelectorAll("button.sidebar-button") | 63 | const btn = document.querySelectorAll("button.sidebar-button") |
| 62 | const span = document.querySelectorAll("button.sidebar-button>span"); | 64 | const span = document.querySelectorAll("button.sidebar-button>span"); |
| 63 | const side = document.querySelector("#sidebar-list"); | 65 | const side = document.querySelector("#sidebar-list"); |
| 64 | const login = document.querySelectorAll(".login>button")[1]; | 66 | const login = document.querySelectorAll(".login>button")[1]; |
| 67 | const searchbar = document.querySelector("#searchbar"); | ||
| 65 | 68 | ||
| 66 | if(sidebar===1){ | 69 | if(sidebar===1){ |
| 67 | setSidebar(0) | 70 | setSidebar(0) |
| 68 | side.style.width="320px" | 71 | side.style.width="320px" |
| 69 | btn.forEach((e, i) =>{ | 72 | btn.forEach((e, i) =>{ |
| 70 | e.style.width="310px" | 73 | e.style.width="310px" |
| 74 | e.style.padding = "0.4em 0 0 11px" | ||
| 71 | setTimeout(() => { | 75 | setTimeout(() => { |
| 72 | span[i].style.opacity="1" | 76 | span[i].style.opacity="1" |
| 73 | login.style.opacity="1" | 77 | login.style.opacity="1" |
| @@ -77,28 +81,29 @@ if(sidebar===1){ | |||
| 77 | side.style.zIndex="2" | 81 | side.style.zIndex="2" |
| 78 | } else { | 82 | } else { |
| 79 | side.style.width="40px"; | 83 | side.style.width="40px"; |
| 84 | searchbar.focus(); | ||
| 85 | setSearch(searchbar.value) | ||
| 80 | setSidebar(1) | 86 | setSidebar(1) |
| 81 | btn.forEach((e,i) =>{ | 87 | btn.forEach((e,i) =>{ |
| 82 | e.style.width="40px" | 88 | e.style.width="40px" |
| 89 | e.style.padding = "0.4em 0 0 5px" | ||
| 83 | span[i].style.opacity="0" | 90 | span[i].style.opacity="0" |
| 84 | }) | 91 | }) |
| 85 | login.style.opacity="0" | 92 | login.style.opacity="0" |
| 86 | setTimeout(() => { | 93 | setTimeout(() => { |
| 87 | side.style.zIndex="0" | 94 | side.style.zIndex="0" |
| 88 | }, 300); | 95 | }, 300); |
| 89 | } | 96 | } |
| 90 | } | 97 | } |
| 91 | |||
| 92 | // Links | 98 | // Links |
| 93 | const location = useLocation() | 99 | const location = useLocation() |
| 94 | React.useEffect(()=>{ | 100 | React.useEffect(()=>{ |
| 95 | if(location.pathname==="/"){SidebarClick(1)} | 101 | if(location.pathname==="/"){SidebarClick(1)} |
| 96 | if(location.pathname.includes("news")){SidebarClick(2)} | 102 | if(location.pathname.includes("news")){SidebarClick(2)} |
| 97 | if(location.pathname.includes("records")){SidebarClick(3)} | 103 | if(location.pathname.includes("games")){SidebarClick(3)} |
| 98 | if(location.pathname.includes("leaderboards")){SidebarClick(4)} | 104 | if(location.pathname.includes("leaderboards")){SidebarClick(4)} |
| 99 | if(location.pathname.includes("discussions")){SidebarClick(5)} | 105 | if(location.pathname.includes("scorelog")){SidebarClick(5)} |
| 100 | if(location.pathname.includes("scorelog")){SidebarClick(6)} | 106 | if(location.pathname.includes("profile")){SidebarClick(6)} |
| 101 | if(location.pathname.includes("profile")){SidebarClick(7)} | ||
| 102 | if(location.pathname.includes("rules")){SidebarClick(9)} | 107 | if(location.pathname.includes("rules")){SidebarClick(9)} |
| 103 | if(location.pathname.includes("about")){SidebarClick(10)} | 108 | if(location.pathname.includes("about")){SidebarClick(10)} |
| 104 | 109 | ||
| @@ -107,6 +112,7 @@ React.useEffect(()=>{ | |||
| 107 | 112 | ||
| 108 | const [search,setSearch] = React.useState(null) | 113 | const [search,setSearch] = React.useState(null) |
| 109 | const [searchData,setSearchData] = React.useState(null) | 114 | const [searchData,setSearchData] = React.useState(null) |
| 115 | |||
| 110 | React.useEffect(()=>{ | 116 | React.useEffect(()=>{ |
| 111 | fetch(`https://lp.ardapektezol.com/api/v1/search?q=${search}`) | 117 | fetch(`https://lp.ardapektezol.com/api/v1/search?q=${search}`) |
| 112 | .then(r=>r.json()) | 118 | .then(r=>r.json()) |
| @@ -139,18 +145,14 @@ return ( | |||
| 139 | <button className='sidebar-button'><img src={img3} alt="" /><span>News</span></button> | 145 | <button className='sidebar-button'><img src={img3} alt="" /><span>News</span></button> |
| 140 | </Link> | 146 | </Link> |
| 141 | 147 | ||
| 142 | <Link to="/records" tabIndex={-1}> | 148 | <Link to="/games" tabIndex={-1}> |
| 143 | <button className='sidebar-button'><img src={img4} alt="" /><span>Records</span></button> | 149 | <button className='sidebar-button'><img src={img4} alt="" /><span>Games</span></button> |
| 144 | </Link> | 150 | </Link> |
| 145 | 151 | ||
| 146 | <Link to="/leaderboards" tabIndex={-1}> | 152 | <Link to="/leaderboards" tabIndex={-1}> |
| 147 | <button className='sidebar-button'><img src={img5} alt="" /><span>Leaderboards</span></button> | 153 | <button className='sidebar-button'><img src={img5} alt="" /><span>Leaderboards</span></button> |
| 148 | </Link> | 154 | </Link> |
| 149 | 155 | ||
| 150 | <Link to="/discussions" tabIndex={-1}> | ||
| 151 | <button className='sidebar-button'><img src={img6} alt="" /><span>Discussions</span></button> | ||
| 152 | </Link> | ||
| 153 | |||
| 154 | <Link to="/scorelog" tabIndex={-1}> | 156 | <Link to="/scorelog" tabIndex={-1}> |
| 155 | <button className='sidebar-button'><img src={img7} alt="" /><span>Score Logs</span></button> | 157 | <button className='sidebar-button'><img src={img7} alt="" /><span>Score Logs</span></button> |
| 156 | </Link> | 158 | </Link> |
diff --git a/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..7d737f2 --- /dev/null +++ b/frontend/src/fonts/BarlowSemiCondensed-SemiBold.ttf | |||
| Binary files 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'; | |||
| 4 | 4 | ||
| 5 | const root = ReactDOM.createRoot(document.getElementById('root')); | 5 | const root = ReactDOM.createRoot(document.getElementById('root')); |
| 6 | root.render( | 6 | root.render( |
| 7 | <React.StrictMode> | ||
| 8 | <App/> | 7 | <App/> |
| 9 | </React.StrictMode> | ||
| 10 | ); | 8 | ); |