aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
authorFifthWit <fifthwitbusiness@gmail.com>2025-01-30 10:44:30 -0600
committerFifthWit <fifthwitbusiness@gmail.com>2025-01-30 10:44:30 -0600
commite40f07211f5f15dcb138e2520a76d13afd3c0cfd (patch)
tree46bad6a17e66d55a4a65088c0b6eb8c48641615a /frontend/src/components
parentadded prettier for more consistency (diff)
downloadlphub-e40f07211f5f15dcb138e2520a76d13afd3c0cfd.tar.gz
lphub-e40f07211f5f15dcb138e2520a76d13afd3c0cfd.tar.bz2
lphub-e40f07211f5f15dcb138e2520a76d13afd3c0cfd.zip
formatted with prettier
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/ConfirmDialog.tsx49
-rw-r--r--frontend/src/components/Discussions.tsx70
-rw-r--r--frontend/src/components/GameCategory.tsx37
-rw-r--r--frontend/src/components/GameEntry.tsx33
-rw-r--r--frontend/src/components/Leaderboards.tsx72
-rw-r--r--frontend/src/components/Login.tsx103
-rw-r--r--frontend/src/components/MapEntry.tsx10
-rw-r--r--frontend/src/components/MessageDialog.tsx46
-rw-r--r--frontend/src/components/MessageDialogLoad.tsx44
-rw-r--r--frontend/src/components/ModMenu.tsx126
-rw-r--r--frontend/src/components/RankingEntry.tsx94
-rw-r--r--frontend/src/components/Sidebar.tsx262
-rw-r--r--frontend/src/components/Summary.tsx265
-rw-r--r--frontend/src/components/UploadRunDialog.tsx366
14 files changed, 963 insertions, 614 deletions
diff --git a/frontend/src/components/ConfirmDialog.tsx b/frontend/src/components/ConfirmDialog.tsx
index 44a653b..925118d 100644
--- a/frontend/src/components/ConfirmDialog.tsx
+++ b/frontend/src/components/ConfirmDialog.tsx
@@ -1,31 +1,36 @@
1import React from 'react'; 1import React from 'react';
2 2
3import "@css/Dialog.css" 3import '@css/Dialog.css';
4 4
5interface ConfirmDialogProps { 5interface ConfirmDialogProps {
6 title: string; 6 title: string;
7 subtitle: string; 7 subtitle: string;
8 onConfirm: () => void; 8 onConfirm: () => void;
9 onCancel: () => void; 9 onCancel: () => void;
10}; 10}
11 11
12const ConfirmDialog: React.FC<ConfirmDialogProps> = ({ title, subtitle, onConfirm, onCancel }) => { 12const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
13 return ( 13 title,
14 <div className='dimmer'> 14 subtitle,
15 <div className='dialog'> 15 onConfirm,
16 <div className='dialog-element dialog-header'> 16 onCancel,
17 <span>{title}</span> 17}) => {
18 </div> 18 return (
19 <div className='dialog-element dialog-description'> 19 <div className="dimmer">
20 <span>{subtitle}</span> 20 <div className="dialog">
21 </div> 21 <div className="dialog-element dialog-header">
22 <div className='dialog-element dialog-btns-container'> 22 <span>{title}</span>
23 <button onClick={onCancel}>Cancel</button> 23 </div>
24 <button onClick={onConfirm}>Confirm</button> 24 <div className="dialog-element dialog-description">
25 </div> 25 <span>{subtitle}</span>
26 </div> 26 </div>
27 <div className="dialog-element dialog-btns-container">
28 <button onClick={onCancel}>Cancel</button>
29 <button onClick={onConfirm}>Confirm</button>
27 </div> 30 </div>
28 ) 31 </div>
32 </div>
33 );
29}; 34};
30 35
31export default ConfirmDialog; 36export default ConfirmDialog;
diff --git a/frontend/src/components/Discussions.tsx b/frontend/src/components/Discussions.tsx
index 62a9fc7..994cd8e 100644
--- a/frontend/src/components/Discussions.tsx
+++ b/frontend/src/components/Discussions.tsx
@@ -1,16 +1,16 @@
1import React from "react"; 1import React from 'react';
2 2
3import { 3import {
4 MapDiscussion, 4 MapDiscussion,
5 MapDiscussions, 5 MapDiscussions,
6 MapDiscussionsDetail, 6 MapDiscussionsDetail,
7} from "@customTypes/Map"; 7} from '@customTypes/Map';
8import { MapDiscussionContent } from "@customTypes/Content"; 8import { MapDiscussionContent } from '@customTypes/Content';
9import { time_ago } from "@utils/Time"; 9import { time_ago } from '@utils/Time';
10import { API } from "@api/Api"; 10import { API } from '@api/Api';
11import "@css/Maps.css"; 11import '@css/Maps.css';
12import { Link } from "react-router-dom"; 12import { Link } from 'react-router-dom';
13import useConfirm from "@hooks/UseConfirm"; 13import useConfirm from '@hooks/UseConfirm';
14 14
15interface DiscussionsProps { 15interface DiscussionsProps {
16 token?: string; 16 token?: string;
@@ -32,17 +32,17 @@ const Discussions: React.FC<DiscussionsProps> = ({
32 const [discussionThread, setDiscussionThread] = React.useState< 32 const [discussionThread, setDiscussionThread] = React.useState<
33 MapDiscussion | undefined 33 MapDiscussion | undefined
34 >(undefined); 34 >(undefined);
35 const [discussionSearch, setDiscussionSearch] = React.useState<string>(""); 35 const [discussionSearch, setDiscussionSearch] = React.useState<string>('');
36 36
37 const [createDiscussion, setCreateDiscussion] = 37 const [createDiscussion, setCreateDiscussion] =
38 React.useState<boolean>(false); 38 React.useState<boolean>(false);
39 const [createDiscussionContent, setCreateDiscussionContent] = 39 const [createDiscussionContent, setCreateDiscussionContent] =
40 React.useState<MapDiscussionContent>({ 40 React.useState<MapDiscussionContent>({
41 title: "", 41 title: '',
42 content: "", 42 content: '',
43 }); 43 });
44 const [createDiscussionCommentContent, setCreateDiscussionCommentContent] = 44 const [createDiscussionCommentContent, setCreateDiscussionCommentContent] =
45 React.useState<string>(""); 45 React.useState<string>('');
46 46
47 const _open_map_discussion = async (discussion_id: number) => { 47 const _open_map_discussion = async (discussion_id: number) => {
48 const mapDiscussion = await API.get_map_discussion(mapID, discussion_id); 48 const mapDiscussion = await API.get_map_discussion(mapID, discussion_id);
@@ -72,7 +72,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
72 const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => { 72 const _delete_map_discussion = async (discussion: MapDiscussionsDetail) => {
73 if ( 73 if (
74 await confirm( 74 await confirm(
75 "Delete Map Discussion", 75 'Delete Map Discussion',
76 `Are you sure you want to remove post: ${discussion.title}?` 76 `Are you sure you want to remove post: ${discussion.title}?`
77 ) 77 )
78 ) { 78 ) {
@@ -90,8 +90,8 @@ const Discussions: React.FC<DiscussionsProps> = ({
90 <input 90 <input
91 type="text" 91 type="text"
92 value={discussionSearch} 92 value={discussionSearch}
93 placeholder={"Search for posts..."} 93 placeholder={'Search for posts...'}
94 onChange={(e) => setDiscussionSearch(e.target.value)} 94 onChange={e => setDiscussionSearch(e.target.value)}
95 /> 95 />
96 <div> 96 <div>
97 <button onClick={() => setCreateDiscussion(true)}>New Post</button> 97 <button onClick={() => setCreateDiscussion(true)}>New Post</button>
@@ -104,11 +104,11 @@ const Discussions: React.FC<DiscussionsProps> = ({
104 <div id="discussion-create"> 104 <div id="discussion-create">
105 <span>Create Post</span> 105 <span>Create Post</span>
106 <button onClick={() => setCreateDiscussion(false)}>X</button> 106 <button onClick={() => setCreateDiscussion(false)}>X</button>
107 <div style={{ gridColumn: "1 / span 2" }}> 107 <div style={{ gridColumn: '1 / span 2' }}>
108 <input 108 <input
109 id="discussion-create-title" 109 id="discussion-create-title"
110 placeholder="Title..." 110 placeholder="Title..."
111 onChange={(e) => 111 onChange={e =>
112 setCreateDiscussionContent({ 112 setCreateDiscussionContent({
113 ...createDiscussionContent, 113 ...createDiscussionContent,
114 title: e.target.value, 114 title: e.target.value,
@@ -118,7 +118,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
118 <input 118 <input
119 id="discussion-create-content" 119 id="discussion-create-content"
120 placeholder="Enter the content..." 120 placeholder="Enter the content..."
121 onChange={(e) => 121 onChange={e =>
122 setCreateDiscussionContent({ 122 setCreateDiscussionContent({
123 ...createDiscussionContent, 123 ...createDiscussionContent,
124 content: e.target.value, 124 content: e.target.value,
@@ -126,7 +126,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
126 } 126 }
127 /> 127 />
128 </div> 128 </div>
129 <div style={{ placeItems: "end", gridColumn: "1 / span 2" }}> 129 <div style={{ placeItems: 'end', gridColumn: '1 / span 2' }}>
130 <button 130 <button
131 id="discussion-create-button" 131 id="discussion-create-button"
132 onClick={() => _create_map_discussion()} 132 onClick={() => _create_map_discussion()}
@@ -157,8 +157,8 @@ const Discussions: React.FC<DiscussionsProps> = ({
157 {time_ago( 157 {time_ago(
158 new Date( 158 new Date(
159 discussionThread.discussion.created_at 159 discussionThread.discussion.created_at
160 .replace("T", " ") 160 .replace('T', ' ')
161 .replace("Z", "") 161 .replace('Z', '')
162 ) 162 )
163 )} 163 )}
164 </span> 164 </span>
@@ -170,7 +170,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
170 (a, b) => 170 (a, b) =>
171 new Date(a.date).getTime() - new Date(b.date).getTime() 171 new Date(a.date).getTime() - new Date(b.date).getTime()
172 ) 172 )
173 .map((e) => ( 173 .map(e => (
174 <> 174 <>
175 <Link to={`/users/${e.user.steam_id}`}> 175 <Link to={`/users/${e.user.steam_id}`}>
176 <img src={e.user.avatar_link} alt="" /> 176 <img src={e.user.avatar_link} alt="" />
@@ -180,7 +180,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
180 <span> 180 <span>
181 {time_ago( 181 {time_ago(
182 new Date( 182 new Date(
183 e.date.replace("T", " ").replace("Z", "") 183 e.date.replace('T', ' ').replace('Z', '')
184 ) 184 )
185 )} 185 )}
186 </span> 186 </span>
@@ -188,29 +188,29 @@ const Discussions: React.FC<DiscussionsProps> = ({
188 </div> 188 </div>
189 </> 189 </>
190 )) 190 ))
191 : ""} 191 : ''}
192 </div> 192 </div>
193 <div id="discussion-send"> 193 <div id="discussion-send">
194 <input 194 <input
195 type="text" 195 type="text"
196 value={createDiscussionCommentContent} 196 value={createDiscussionCommentContent}
197 placeholder={"Message"} 197 placeholder={'Message'}
198 onKeyDown={(e) => 198 onKeyDown={e =>
199 e.key === "Enter" && 199 e.key === 'Enter' &&
200 _create_map_discussion_comment(discussionThread.discussion.id) 200 _create_map_discussion_comment(discussionThread.discussion.id)
201 } 201 }
202 onChange={(e) => 202 onChange={e =>
203 setCreateDiscussionCommentContent(e.target.value) 203 setCreateDiscussionCommentContent(e.target.value)
204 } 204 }
205 /> 205 />
206 <div> 206 <div>
207 <button 207 <button
208 onClick={() => { 208 onClick={() => {
209 if (createDiscussionCommentContent !== "") { 209 if (createDiscussionCommentContent !== '') {
210 _create_map_discussion_comment( 210 _create_map_discussion_comment(
211 discussionThread.discussion.id 211 discussionThread.discussion.id
212 ); 212 );
213 setCreateDiscussionCommentContent(""); 213 setCreateDiscussionCommentContent('');
214 } 214 }
215 }} 215 }}
216 > 216 >
@@ -222,7 +222,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
222 ) : data ? ( 222 ) : data ? (
223 <> 223 <>
224 {data.discussions 224 {data.discussions
225 .filter((f) => f.title.includes(discussionSearch)) 225 .filter(f => f.title.includes(discussionSearch))
226 .sort( 226 .sort(
227 (a, b) => 227 (a, b) =>
228 new Date(b.updated_at).getTime() - 228 new Date(b.updated_at).getTime() -
@@ -234,7 +234,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
234 <span>{e.title}</span> 234 <span>{e.title}</span>
235 {isModerator ? ( 235 {isModerator ? (
236 <button 236 <button
237 onClick={(m) => { 237 onClick={m => {
238 m.stopPropagation(); 238 m.stopPropagation();
239 _delete_map_discussion(e); 239 _delete_map_discussion(e);
240 }} 240 }}
@@ -248,10 +248,10 @@ const Discussions: React.FC<DiscussionsProps> = ({
248 <b>{e.creator.user_name}:</b> {e.content} 248 <b>{e.creator.user_name}:</b> {e.content}
249 </span> 249 </span>
250 <span> 250 <span>
251 Last Updated:{" "} 251 Last Updated:{' '}
252 {time_ago( 252 {time_ago(
253 new Date( 253 new Date(
254 e.updated_at.replace("T", " ").replace("Z", "") 254 e.updated_at.replace('T', ' ').replace('Z', '')
255 ) 255 )
256 )} 256 )}
257 </span> 257 </span>
@@ -260,7 +260,7 @@ const Discussions: React.FC<DiscussionsProps> = ({
260 ))} 260 ))}
261 </> 261 </>
262 ) : ( 262 ) : (
263 <span style={{ textAlign: "center", display: "block" }}> 263 <span style={{ textAlign: 'center', display: 'block' }}>
264 No Discussions... 264 No Discussions...
265 </span> 265 </span>
266 ) 266 )
diff --git a/frontend/src/components/GameCategory.tsx b/frontend/src/components/GameCategory.tsx
index d8879ef..b13be48 100644
--- a/frontend/src/components/GameCategory.tsx
+++ b/frontend/src/components/GameCategory.tsx
@@ -1,24 +1,31 @@
1import React from 'react'; 1import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from 'react-router-dom';
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from '@customTypes/Game';
5import "@css/Games.css" 5import '@css/Games.css';
6 6
7interface GameCategoryProps { 7interface GameCategoryProps {
8 game: Game; 8 game: Game;
9 cat: GameCategoryPortals; 9 cat: GameCategoryPortals;
10} 10}
11 11
12const GameCategory: React.FC<GameCategoryProps> = ({cat, game}) => { 12const GameCategory: React.FC<GameCategoryProps> = ({ cat, game }) => {
13 return ( 13 return (
14 <Link className="games-page-item-body-item" to={"/games/" + game.id + "?cat=" + cat.category.id}> 14 <Link
15 <div> 15 className="games-page-item-body-item"
16 <span className='games-page-item-body-item-title'>{cat.category.name}</span> 16 to={'/games/' + game.id + '?cat=' + cat.category.id}
17 <br /> 17 >
18 <span className='games-page-item-body-item-num'>{cat.portal_count}</span> 18 <div>
19 </div> 19 <span className="games-page-item-body-item-title">
20 </Link> 20 {cat.category.name}
21 ) 21 </span>
22} 22 <br />
23 <span className="games-page-item-body-item-num">
24 {cat.portal_count}
25 </span>
26 </div>
27 </Link>
28 );
29};
23 30
24export default GameCategory; 31export default GameCategory;
diff --git a/frontend/src/components/GameEntry.tsx b/frontend/src/components/GameEntry.tsx
index 3bd2842..b58bbdd 100644
--- a/frontend/src/components/GameEntry.tsx
+++ b/frontend/src/components/GameEntry.tsx
@@ -1,8 +1,8 @@
1import React from 'react'; 1import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from 'react-router-dom';
3 3
4import { Game, GameCategoryPortals } from '@customTypes/Game'; 4import { Game, GameCategoryPortals } from '@customTypes/Game';
5import "@css/Games.css" 5import '@css/Games.css';
6 6
7import GameCategory from '@components/GameCategory'; 7import GameCategory from '@components/GameCategory';
8 8
@@ -18,17 +18,26 @@ const GameEntry: React.FC<GameEntryProps> = ({ game }) => {
18 }, [game.category_portals]); 18 }, [game.category_portals]);
19 19
20 return ( 20 return (
21 <Link to={"/games/" + game.id}><div className='games-page-item'> 21 <Link to={'/games/' + game.id}>
22 <div className='games-page-item-header'> 22 <div className="games-page-item">
23 <div style={{ backgroundImage: `url(${game.image})` }} className='games-page-item-header-img'></div> 23 <div className="games-page-item-header">
24 <span><b>{game.name}</b></span> 24 <div
25 style={{ backgroundImage: `url(${game.image})` }}
26 className="games-page-item-header-img"
27 ></div>
28 <span>
29 <b>{game.name}</b>
30 </span>
31 </div>
32 <div id={game.id as any as string} className="games-page-item-body">
33 {catInfo.map((cat, index) => {
34 return (
35 <GameCategory cat={cat} game={game} key={index}></GameCategory>
36 );
37 })}
38 </div>
25 </div> 39 </div>
26 <div id={game.id as any as string} className='games-page-item-body'> 40 </Link>
27 {catInfo.map((cat, index) => {
28 return <GameCategory cat={cat} game={game} key={index}></GameCategory>
29 })}
30 </div>
31 </div></Link>
32 ); 41 );
33}; 42};
34 43
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index 05f01d0..fb72f2b 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -1,12 +1,12 @@
1import React from "react"; 1import React from 'react';
2import { Link, useNavigate } from "react-router-dom"; 2import { Link, useNavigate } from 'react-router-dom';
3 3
4import { DownloadIcon, ThreedotIcon } from "@images/Images"; 4import { DownloadIcon, ThreedotIcon } from '@images/Images';
5import { MapLeaderboard } from "@customTypes/Map"; 5import { MapLeaderboard } from '@customTypes/Map';
6import { ticks_to_time, time_ago } from "@utils/Time"; 6import { ticks_to_time, time_ago } from '@utils/Time';
7import { API } from "@api/Api"; 7import { API } from '@api/Api';
8import useMessage from "@hooks/UseMessage"; 8import useMessage from '@hooks/UseMessage';
9import "@css/Maps.css"; 9import '@css/Maps.css';
10 10
11interface LeaderboardsProps { 11interface LeaderboardsProps {
12 mapID: string; 12 mapID: string;
@@ -35,7 +35,7 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
35 if (!data) { 35 if (!data) {
36 return ( 36 return (
37 <section id="section6" className="summary2"> 37 <section id="section6" className="summary2">
38 <h1 style={{ textAlign: "center" }}> 38 <h1 style={{ textAlign: 'center' }}>
39 Map is not available for competitive boards. 39 Map is not available for competitive boards.
40 </h1> 40 </h1>
41 </section> 41 </section>
@@ -45,7 +45,7 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
45 if (data.records.length === 0) { 45 if (data.records.length === 0) {
46 return ( 46 return (
47 <section id="section6" className="summary2"> 47 <section id="section6" className="summary2">
48 <h1 style={{ textAlign: "center" }}>No records found.</h1> 48 <h1 style={{ textAlign: 'center' }}>No records found.</h1>
49 </section> 49 </section>
50 ); 50 );
51 } 51 }
@@ -58,8 +58,8 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
58 id="leaderboard-top" 58 id="leaderboard-top"
59 style={ 59 style={
60 data.map.is_coop 60 data.map.is_coop
61 ? { gridTemplateColumns: "7.5% 40% 7.5% 15% 15% 15%" } 61 ? { gridTemplateColumns: '7.5% 40% 7.5% 15% 15% 15%' }
62 : { gridTemplateColumns: "7.5% 30% 10% 20% 17.5% 15%" } 62 : { gridTemplateColumns: '7.5% 30% 10% 20% 17.5% 15%' }
63 } 63 }
64 > 64 >
65 <span>Place</span> 65 <span>Place</span>
@@ -82,13 +82,13 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
82 onClick={() => 82 onClick={() =>
83 pageNumber === 1 83 pageNumber === 1
84 ? null 84 ? null
85 : setPageNumber((prevPageNumber) => prevPageNumber - 1) 85 : setPageNumber(prevPageNumber => prevPageNumber - 1)
86 } 86 }
87 > 87 >
88 <i 88 <i
89 className="triangle" 89 className="triangle"
90 style={{ position: "relative", left: "-5px" }} 90 style={{ position: 'relative', left: '-5px' }}
91 ></i>{" "} 91 ></i>{' '}
92 </button> 92 </button>
93 <span> 93 <span>
94 {data.pagination.current_page}/{data.pagination.total_pages} 94 {data.pagination.current_page}/{data.pagination.total_pages}
@@ -97,17 +97,17 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
97 onClick={() => 97 onClick={() =>
98 pageNumber === data.pagination.total_pages 98 pageNumber === data.pagination.total_pages
99 ? null 99 ? null
100 : setPageNumber((prevPageNumber) => prevPageNumber + 1) 100 : setPageNumber(prevPageNumber => prevPageNumber + 1)
101 } 101 }
102 > 102 >
103 <i 103 <i
104 className="triangle" 104 className="triangle"
105 style={{ 105 style={{
106 position: "relative", 106 position: 'relative',
107 left: "5px", 107 left: '5px',
108 transform: "rotate(180deg)", 108 transform: 'rotate(180deg)',
109 }} 109 }}
110 ></i>{" "} 110 ></i>{' '}
111 </button> 111 </button>
112 </div> 112 </div>
113 </div> 113 </div>
@@ -120,33 +120,33 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
120 key={index} 120 key={index}
121 style={ 121 style={
122 data.map.is_coop 122 data.map.is_coop
123 ? { gridTemplateColumns: "3% 4.5% 40% 4% 3.5% 15% 15% 14.5%" } 123 ? { gridTemplateColumns: '3% 4.5% 40% 4% 3.5% 15% 15% 14.5%' }
124 : { gridTemplateColumns: "3% 4.5% 30% 4% 6% 20% 17% 15%" } 124 : { gridTemplateColumns: '3% 4.5% 30% 4% 6% 20% 17% 15%' }
125 } 125 }
126 > 126 >
127 <span>{r.placement}</span> 127 <span>{r.placement}</span>
128 <span> </span> 128 <span> </span>
129 {r.kind === "multiplayer" ? ( 129 {r.kind === 'multiplayer' ? (
130 <div> 130 <div>
131 <Link to={`/users/${r.host.steam_id}`}> 131 <Link to={`/users/${r.host.steam_id}`}>
132 <span> 132 <span>
133 <img src={r.host.avatar_link} alt="" /> &nbsp;{" "} 133 <img src={r.host.avatar_link} alt="" /> &nbsp;{' '}
134 {r.host.user_name} 134 {r.host.user_name}
135 </span> 135 </span>
136 </Link> 136 </Link>
137 <Link to={`/users/${r.partner.steam_id}`}> 137 <Link to={`/users/${r.partner.steam_id}`}>
138 <span> 138 <span>
139 <img src={r.partner.avatar_link} alt="" /> &nbsp;{" "} 139 <img src={r.partner.avatar_link} alt="" /> &nbsp;{' '}
140 {r.partner.user_name} 140 {r.partner.user_name}
141 </span> 141 </span>
142 </Link> 142 </Link>
143 </div> 143 </div>
144 ) : ( 144 ) : (
145 r.kind === "singleplayer" && ( 145 r.kind === 'singleplayer' && (
146 <div> 146 <div>
147 <Link to={`/users/${r.user.steam_id}`}> 147 <Link to={`/users/${r.user.steam_id}`}>
148 <span> 148 <span>
149 <img src={r.user.avatar_link} alt="" /> &nbsp;{" "} 149 <img src={r.user.avatar_link} alt="" /> &nbsp;{' '}
150 {r.user.user_name} 150 {r.user.user_name}
151 </span> 151 </span>
152 </Link> 152 </Link>
@@ -158,25 +158,25 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
158 <span> </span> 158 <span> </span>
159 <span 159 <span
160 className="hover-popup" 160 className="hover-popup"
161 popup-text={r.score_time + " ticks"} 161 popup-text={r.score_time + ' ticks'}
162 > 162 >
163 {ticks_to_time(r.score_time)} 163 {ticks_to_time(r.score_time)}
164 </span> 164 </span>
165 <span 165 <span
166 className="hover-popup" 166 className="hover-popup"
167 popup-text={r.record_date.replace("T", " ").split(".")[0]} 167 popup-text={r.record_date.replace('T', ' ').split('.')[0]}
168 > 168 >
169 {time_ago( 169 {time_ago(
170 new Date(r.record_date.replace("T", " ").replace("Z", "")) 170 new Date(r.record_date.replace('T', ' ').replace('Z', ''))
171 )} 171 )}
172 </span> 172 </span>
173 173
174 {r.kind === "multiplayer" ? ( 174 {r.kind === 'multiplayer' ? (
175 <span> 175 <span>
176 <button 176 <button
177 onClick={() => { 177 onClick={() => {
178 message( 178 message(
179 "Demo Information", 179 'Demo Information',
180 `Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}` 180 `Host Demo ID: ${r.host_demo_id} \nParnter Demo ID: ${r.partner_demo_id}`
181 ); 181 );
182 }} 182 }}
@@ -193,7 +193,7 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
193 alt="download" 193 alt="download"
194 style={{ 194 style={{
195 filter: 195 filter:
196 "hue-rotate(160deg) contrast(60%) saturate(1000%)", 196 'hue-rotate(160deg) contrast(60%) saturate(1000%)',
197 }} 197 }}
198 /> 198 />
199 </button> 199 </button>
@@ -207,17 +207,17 @@ const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
207 alt="download" 207 alt="download"
208 style={{ 208 style={{
209 filter: 209 filter:
210 "hue-rotate(300deg) contrast(60%) saturate(1000%)", 210 'hue-rotate(300deg) contrast(60%) saturate(1000%)',
211 }} 211 }}
212 /> 212 />
213 </button> 213 </button>
214 </span> 214 </span>
215 ) : ( 215 ) : (
216 r.kind === "singleplayer" && ( 216 r.kind === 'singleplayer' && (
217 <span> 217 <span>
218 <button 218 <button
219 onClick={() => { 219 onClick={() => {
220 message("Demo Information", `Demo ID: ${r.demo_id}`); 220 message('Demo Information', `Demo ID: ${r.demo_id}`);
221 }} 221 }}
222 > 222 >
223 <img src={ThreedotIcon} alt="demo_id" /> 223 <img src={ThreedotIcon} alt="demo_id" />
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
index f1628b2..093e406 100644
--- a/frontend/src/components/Login.tsx
+++ b/frontend/src/components/Login.tsx
@@ -4,77 +4,78 @@ import { Link, useNavigate } from 'react-router-dom';
4import { ExitIcon, UserIcon, LoginIcon } from '@images/Images'; 4import { ExitIcon, UserIcon, LoginIcon } from '@images/Images';
5import { UserProfile } from '@customTypes/Profile'; 5import { UserProfile } from '@customTypes/Profile';
6import { API } from '@api/Api'; 6import { API } from '@api/Api';
7import "@css/Login.css"; 7import '@css/Login.css';
8 8
9interface LoginProps { 9interface LoginProps {
10 setToken: React.Dispatch<React.SetStateAction<string | undefined>>; 10 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
11 profile?: UserProfile; 11 profile?: UserProfile;
12 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>; 12 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>;
13}; 13}
14 14
15const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => { 15const Login: React.FC<LoginProps> = ({ setToken, profile, setProfile }) => {
16
17 const navigate = useNavigate(); 16 const navigate = useNavigate();
18 17
19 const _login = () => { 18 const _login = () => {
20 window.location.href = "/api/v1/login"; 19 window.location.href = '/api/v1/login';
21 }; 20 };
22 21
23 const _logout = () => { 22 const _logout = () => {
24 setProfile(undefined); 23 setProfile(undefined);
25 setToken(undefined); 24 setToken(undefined);
26 API.delete_token(); 25 API.delete_token();
27 navigate("/"); 26 navigate('/');
28 }; 27 };
29 28
30 return ( 29 return (
31 <> 30 <>
32 {profile 31 {profile ? (
33 ? 32 <>
34 ( 33 {profile.profile ? (
35 <> 34 <>
36 {profile.profile ? 35 <Link to="/profile" tabIndex={-1} className="login">
37 ( 36 <button className="sidebar-button">
38 <> 37 <img
39 <Link to="/profile" tabIndex={-1} className='login'> 38 className="avatar-img"
40 <button className='sidebar-button'> 39 src={profile.avatar_link}
41 <img className="avatar-img" src={profile.avatar_link} alt="" /> 40 alt=""
42 <span>{profile.user_name}</span> 41 />
43 </button> 42 <span>{profile.user_name}</span>
44 <button className='logout-button' onClick={_logout}> 43 </button>
45 <img src={ExitIcon} alt="" /><span /> 44 <button className="logout-button" onClick={_logout}>
46 </button> 45 <img src={ExitIcon} alt="" />
47 </Link> 46 <span />
48 </> 47 </button>
49 ) 48 </Link>
50 : 49 </>
51 ( 50 ) : (
52 <> 51 <>
53 <Link to="/" tabIndex={-1} className='login'> 52 <Link to="/" tabIndex={-1} className="login">
54 <button className='sidebar-button'> 53 <button className="sidebar-button">
55 <img className="avatar-img" src={profile.avatar_link} alt="" /> 54 <img
56 <span>Loading Profile...</span> 55 className="avatar-img"
57 </button> 56 src={profile.avatar_link}
58 <button disabled className='logout-button' onClick={_logout}> 57 alt=""
59 <img src={ExitIcon} alt="" /><span /> 58 />
60 </button> 59 <span>Loading Profile...</span>
61 </Link> 60 </button>
62 </> 61 <button disabled className="logout-button" onClick={_logout}>
63 ) 62 <img src={ExitIcon} alt="" />
64 } 63 <span />
65 </> 64 </button>
66 ) 65 </Link>
67 : 66 </>
68 ( 67 )}
69 <Link to="/api/v1/login" tabIndex={-1} className='login' > 68 </>
70 <button className='sidebar-button' onClick={_login}> 69 ) : (
71 <img className="avatar-img" src={UserIcon} alt="" /> 70 <Link to="/api/v1/login" tabIndex={-1} className="login">
72 <span> 71 <button className="sidebar-button" onClick={_login}>
73 <img src={LoginIcon} alt="Sign in through Steam" /> 72 <img className="avatar-img" src={UserIcon} alt="" />
74 </span> 73 <span>
75 </button> 74 <img src={LoginIcon} alt="Sign in through Steam" />
76 </Link> 75 </span>
77 )} 76 </button>
77 </Link>
78 )}
78 </> 79 </>
79 ); 80 );
80}; 81};
diff --git a/frontend/src/components/MapEntry.tsx b/frontend/src/components/MapEntry.tsx
index 0f494ad..f1dee5b 100644
--- a/frontend/src/components/MapEntry.tsx
+++ b/frontend/src/components/MapEntry.tsx
@@ -1,12 +1,8 @@
1import React from 'react'; 1import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from 'react-router-dom';
3 3
4const MapEntry: React.FC = () => { 4const MapEntry: React.FC = () => {
5 return ( 5 return <div></div>;
6 <div> 6};
7
8 </div>
9 )
10}
11 7
12export default MapEntry; 8export default MapEntry;
diff --git a/frontend/src/components/MessageDialog.tsx b/frontend/src/components/MessageDialog.tsx
index 5c85189..b739ebc 100644
--- a/frontend/src/components/MessageDialog.tsx
+++ b/frontend/src/components/MessageDialog.tsx
@@ -1,29 +1,33 @@
1import React from 'react'; 1import React from 'react';
2 2
3import "@css/Dialog.css" 3import '@css/Dialog.css';
4 4
5interface MessageDialogProps { 5interface MessageDialogProps {
6 title: string; 6 title: string;
7 subtitle: string; 7 subtitle: string;
8 onClose: () => void; 8 onClose: () => void;
9}; 9}
10 10
11const MessageDialog: React.FC<MessageDialogProps> = ({ title, subtitle, onClose }) => { 11const MessageDialog: React.FC<MessageDialogProps> = ({
12 return ( 12 title,
13 <div className='dimmer'> 13 subtitle,
14 <div className='dialog'> 14 onClose,
15 <div className='dialog-element dialog-header'> 15}) => {
16 <span>{title}</span> 16 return (
17 </div> 17 <div className="dimmer">
18 <div className='dialog-element dialog-description'> 18 <div className="dialog">
19 <span>{subtitle}</span> 19 <div className="dialog-element dialog-header">
20 </div> 20 <span>{title}</span>
21 <div className='dialog-element dialog-btns-container'>
22 <button onClick={onClose}>Close</button>
23 </div>
24 </div>
25 </div> 21 </div>
26 ) 22 <div className="dialog-element dialog-description">
27} 23 <span>{subtitle}</span>
24 </div>
25 <div className="dialog-element dialog-btns-container">
26 <button onClick={onClose}>Close</button>
27 </div>
28 </div>
29 </div>
30 );
31};
28 32
29export default MessageDialog; 33export default MessageDialog;
diff --git a/frontend/src/components/MessageDialogLoad.tsx b/frontend/src/components/MessageDialogLoad.tsx
index 966e064..acea27d 100644
--- a/frontend/src/components/MessageDialogLoad.tsx
+++ b/frontend/src/components/MessageDialogLoad.tsx
@@ -1,29 +1,31 @@
1import React from 'react'; 1import React from 'react';
2 2
3import "@css/Dialog.css" 3import '@css/Dialog.css';
4 4
5interface MessageDialogLoadProps { 5interface MessageDialogLoadProps {
6 title: string; 6 title: string;
7 onClose: () => void; 7 onClose: () => void;
8}; 8}
9 9
10const MessageDialogLoad: React.FC<MessageDialogLoadProps> = ({ title, onClose }) => { 10const MessageDialogLoad: React.FC<MessageDialogLoadProps> = ({
11 return ( 11 title,
12 <div className='dimmer'> 12 onClose,
13 <div className='dialog'> 13}) => {
14 <div className='dialog-element dialog-header'> 14 return (
15 <span>{title}</span> 15 <div className="dimmer">
16 </div> 16 <div className="dialog">
17 <div className='dialog-element dialog-description'> 17 <div className="dialog-element dialog-header">
18 <div style={{display: "flex", justifyContent: "center"}}> 18 <span>{title}</span>
19 <span className="loader"></span>
20 </div>
21 </div>
22 <div className='dialog-element dialog-btns-container'>
23 </div>
24 </div>
25 </div> 19 </div>
26 ) 20 <div className="dialog-element dialog-description">
27} 21 <div style={{ display: 'flex', justifyContent: 'center' }}>
22 <span className="loader"></span>
23 </div>
24 </div>
25 <div className="dialog-element dialog-btns-container"></div>
26 </div>
27 </div>
28 );
29};
28 30
29export default MessageDialogLoad; 31export default MessageDialogLoad;
diff --git a/frontend/src/components/ModMenu.tsx b/frontend/src/components/ModMenu.tsx
index f765cd8..140d6a3 100644
--- a/frontend/src/components/ModMenu.tsx
+++ b/frontend/src/components/ModMenu.tsx
@@ -1,12 +1,12 @@
1import React from "react"; 1import React from 'react';
2import ReactMarkdown from "react-markdown"; 2import ReactMarkdown from 'react-markdown';
3import { useNavigate } from "react-router-dom"; 3import { useNavigate } from 'react-router-dom';
4 4
5import { MapSummary } from "@customTypes/Map"; 5import { MapSummary } from '@customTypes/Map';
6import { ModMenuContent } from "@customTypes/Content"; 6import { ModMenuContent } from '@customTypes/Content';
7import { API } from "@api/Api"; 7import { API } from '@api/Api';
8import "@css/ModMenu.css"; 8import '@css/ModMenu.css';
9import useConfirm from "@hooks/UseConfirm"; 9import useConfirm from '@hooks/UseConfirm';
10 10
11interface ModMenuProps { 11interface ModMenuProps {
12 token?: string; 12 token?: string;
@@ -28,26 +28,26 @@ const ModMenu: React.FC<ModMenuProps> = ({
28 28
29 const [routeContent, setRouteContent] = React.useState<ModMenuContent>({ 29 const [routeContent, setRouteContent] = React.useState<ModMenuContent>({
30 id: 0, 30 id: 0,
31 name: "", 31 name: '',
32 score: 0, 32 score: 0,
33 date: "", 33 date: '',
34 showcase: "", 34 showcase: '',
35 description: "No description available.", 35 description: 'No description available.',
36 category_id: 1, 36 category_id: 1,
37 }); 37 });
38 38
39 const [image, setImage] = React.useState<string>(""); 39 const [image, setImage] = React.useState<string>('');
40 const [md, setMd] = React.useState<string>(""); 40 const [md, setMd] = React.useState<string>('');
41 41
42 const navigate = useNavigate(); 42 const navigate = useNavigate();
43 43
44 function compressImage(file: File): Promise<string> { 44 function compressImage(file: File): Promise<string> {
45 const reader = new FileReader(); 45 const reader = new FileReader();
46 reader.readAsDataURL(file); 46 reader.readAsDataURL(file);
47 return new Promise((resolve) => { 47 return new Promise(resolve => {
48 reader.onload = () => { 48 reader.onload = () => {
49 const img = new Image(); 49 const img = new Image();
50 if (typeof reader.result === "string") { 50 if (typeof reader.result === 'string') {
51 img.src = reader.result; 51 img.src = reader.result;
52 img.onload = () => { 52 img.onload = () => {
53 let { width, height } = img; 53 let { width, height } = img;
@@ -59,10 +59,10 @@ const ModMenu: React.FC<ModMenuProps> = ({
59 width *= 320 / height; 59 width *= 320 / height;
60 height = 320; 60 height = 320;
61 } 61 }
62 const canvas = document.createElement("canvas"); 62 const canvas = document.createElement('canvas');
63 canvas.width = width; 63 canvas.width = width;
64 canvas.height = height; 64 canvas.height = height;
65 canvas.getContext("2d")!.drawImage(img, 0, 0, width, height); 65 canvas.getContext('2d')!.drawImage(img, 0, 0, width, height);
66 resolve(canvas.toDataURL(file.type, 0.6)); 66 resolve(canvas.toDataURL(file.type, 0.6));
67 }; 67 };
68 } 68 }
@@ -73,8 +73,8 @@ const ModMenu: React.FC<ModMenuProps> = ({
73 const _edit_map_summary_image = async () => { 73 const _edit_map_summary_image = async () => {
74 if ( 74 if (
75 await confirm( 75 await confirm(
76 "Edit Map Summary Image", 76 'Edit Map Summary Image',
77 "Are you sure you want to submit this to the database?" 77 'Are you sure you want to submit this to the database?'
78 ) 78 )
79 ) { 79 ) {
80 if (token) { 80 if (token) {
@@ -82,7 +82,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
82 if (success) { 82 if (success) {
83 navigate(0); 83 navigate(0);
84 } else { 84 } else {
85 alert("Error. Check logs."); 85 alert('Error. Check logs.');
86 } 86 }
87 } 87 }
88 } 88 }
@@ -91,17 +91,17 @@ const ModMenu: React.FC<ModMenuProps> = ({
91 const _edit_map_summary_route = async () => { 91 const _edit_map_summary_route = async () => {
92 if ( 92 if (
93 await confirm( 93 await confirm(
94 "Edit Map Summary Route", 94 'Edit Map Summary Route',
95 "Are you sure you want to submit this to the database?" 95 'Are you sure you want to submit this to the database?'
96 ) 96 )
97 ) { 97 ) {
98 if (token) { 98 if (token) {
99 routeContent.date += "T00:00:00Z"; 99 routeContent.date += 'T00:00:00Z';
100 const success = await API.put_map_summary(token, mapID, routeContent); 100 const success = await API.put_map_summary(token, mapID, routeContent);
101 if (success) { 101 if (success) {
102 navigate(0); 102 navigate(0);
103 } else { 103 } else {
104 alert("Error. Check logs."); 104 alert('Error. Check logs.');
105 } 105 }
106 } 106 }
107 } 107 }
@@ -110,17 +110,17 @@ const ModMenu: React.FC<ModMenuProps> = ({
110 const _create_map_summary_route = async () => { 110 const _create_map_summary_route = async () => {
111 if ( 111 if (
112 await confirm( 112 await confirm(
113 "Create Map Summary Route", 113 'Create Map Summary Route',
114 "Are you sure you want to submit this to the database?" 114 'Are you sure you want to submit this to the database?'
115 ) 115 )
116 ) { 116 ) {
117 if (token) { 117 if (token) {
118 routeContent.date += "T00:00:00Z"; 118 routeContent.date += 'T00:00:00Z';
119 const success = await API.post_map_summary(token, mapID, routeContent); 119 const success = await API.post_map_summary(token, mapID, routeContent);
120 if (success) { 120 if (success) {
121 navigate(0); 121 navigate(0);
122 } else { 122 } else {
123 alert("Error. Check logs."); 123 alert('Error. Check logs.');
124 } 124 }
125 } 125 }
126 } 126 }
@@ -129,7 +129,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
129 const _delete_map_summary_route = async () => { 129 const _delete_map_summary_route = async () => {
130 if ( 130 if (
131 await confirm( 131 await confirm(
132 "Delete Map Summary Route", 132 'Delete Map Summary Route',
133 `Are you sure you want to submit this to the database?\n 133 `Are you sure you want to submit this to the database?\n
134 ${data.summary.routes[selectedRun].category.name}\n${data.summary.routes[selectedRun].history.score_count} portals\n${data.summary.routes[selectedRun].history.runner_name}` 134 ${data.summary.routes[selectedRun].category.name}\n${data.summary.routes[selectedRun].history.score_count} portals\n${data.summary.routes[selectedRun].history.runner_name}`
135 ) 135 )
@@ -143,7 +143,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
143 if (success) { 143 if (success) {
144 navigate(0); 144 navigate(0);
145 } else { 145 } else {
146 alert("Error. Check logs."); 146 alert('Error. Check logs.');
147 } 147 }
148 } 148 }
149 } 149 }
@@ -154,14 +154,14 @@ const ModMenu: React.FC<ModMenuProps> = ({
154 // add route 154 // add route
155 setRouteContent({ 155 setRouteContent({
156 id: 0, 156 id: 0,
157 name: "", 157 name: '',
158 score: 0, 158 score: 0,
159 date: "", 159 date: '',
160 showcase: "", 160 showcase: '',
161 description: "No description available.", 161 description: 'No description available.',
162 category_id: 1, 162 category_id: 1,
163 }); 163 });
164 setMd("No description available."); 164 setMd('No description available.');
165 } 165 }
166 if (menu === 2) { 166 if (menu === 2) {
167 // edit route 167 // edit route
@@ -169,7 +169,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
169 id: data.summary.routes[selectedRun].route_id, 169 id: data.summary.routes[selectedRun].route_id,
170 name: data.summary.routes[selectedRun].history.runner_name, 170 name: data.summary.routes[selectedRun].history.runner_name,
171 score: data.summary.routes[selectedRun].history.score_count, 171 score: data.summary.routes[selectedRun].history.score_count,
172 date: data.summary.routes[selectedRun].history.date.split("T")[0], 172 date: data.summary.routes[selectedRun].history.date.split('T')[0],
173 showcase: data.summary.routes[selectedRun].showcase, 173 showcase: data.summary.routes[selectedRun].showcase,
174 description: data.summary.routes[selectedRun].description, 174 description: data.summary.routes[selectedRun].description,
175 category_id: data.summary.routes[selectedRun].category.id, 175 category_id: data.summary.routes[selectedRun].category.id,
@@ -179,20 +179,20 @@ const ModMenu: React.FC<ModMenuProps> = ({
179 }, [menu, data.summary.routes, selectedRun]); 179 }, [menu, data.summary.routes, selectedRun]);
180 180
181 React.useEffect(() => { 181 React.useEffect(() => {
182 const modview = document.querySelector("div#modview") as HTMLElement; 182 const modview = document.querySelector('div#modview') as HTMLElement;
183 if (modview) { 183 if (modview) {
184 showButton 184 showButton
185 ? (modview.style.transform = "translateY(-68%)") 185 ? (modview.style.transform = 'translateY(-68%)')
186 : (modview.style.transform = "translateY(0%)"); 186 : (modview.style.transform = 'translateY(0%)');
187 } 187 }
188 188
189 const modview_block = document.querySelector( 189 const modview_block = document.querySelector(
190 "#modview_block" 190 '#modview_block'
191 ) as HTMLElement; 191 ) as HTMLElement;
192 if (modview_block) { 192 if (modview_block) {
193 showButton 193 showButton
194 ? (modview_block.style.display = "none") 194 ? (modview_block.style.display = 'none')
195 : (modview_block.style.display = "block"); 195 : (modview_block.style.display = 'block');
196 } 196 }
197 }, [showButton]); 197 }, [showButton]);
198 198
@@ -240,11 +240,9 @@ const ModMenu: React.FC<ModMenuProps> = ({
240 <input 240 <input
241 type="file" 241 type="file"
242 accept="image/*" 242 accept="image/*"
243 onChange={(e) => { 243 onChange={e => {
244 if (e.target.files) { 244 if (e.target.files) {
245 compressImage(e.target.files[0]).then((d) => 245 compressImage(e.target.files[0]).then(d => setImage(d));
246 setImage(d)
247 );
248 } 246 }
249 }} 247 }}
250 /> 248 />
@@ -275,7 +273,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
275 <input 273 <input
276 type="text" 274 type="text"
277 value={routeContent.name} 275 value={routeContent.name}
278 onChange={(e) => { 276 onChange={e => {
279 setRouteContent({ 277 setRouteContent({
280 ...routeContent, 278 ...routeContent,
281 name: e.target.value, 279 name: e.target.value,
@@ -288,7 +286,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
288 <input 286 <input
289 type="number" 287 type="number"
290 value={routeContent.score} 288 value={routeContent.score}
291 onChange={(e) => { 289 onChange={e => {
292 setRouteContent({ 290 setRouteContent({
293 ...routeContent, 291 ...routeContent,
294 score: parseInt(e.target.value), 292 score: parseInt(e.target.value),
@@ -301,7 +299,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
301 <input 299 <input
302 type="date" 300 type="date"
303 value={routeContent.date} 301 value={routeContent.date}
304 onChange={(e) => { 302 onChange={e => {
305 setRouteContent({ 303 setRouteContent({
306 ...routeContent, 304 ...routeContent,
307 date: e.target.value, 305 date: e.target.value,
@@ -314,7 +312,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
314 <input 312 <input
315 type="text" 313 type="text"
316 value={routeContent.showcase} 314 value={routeContent.showcase}
317 onChange={(e) => { 315 onChange={e => {
318 setRouteContent({ 316 setRouteContent({
319 ...routeContent, 317 ...routeContent,
320 showcase: e.target.value, 318 showcase: e.target.value,
@@ -324,12 +322,12 @@ const ModMenu: React.FC<ModMenuProps> = ({
324 </div> 322 </div>
325 <div 323 <div
326 id="modview-route-description" 324 id="modview-route-description"
327 style={{ height: "180px", gridColumn: "1 / span 5" }} 325 style={{ height: '180px', gridColumn: '1 / span 5' }}
328 > 326 >
329 <span>Description:</span> 327 <span>Description:</span>
330 <textarea 328 <textarea
331 value={routeContent.description} 329 value={routeContent.description}
332 onChange={(e) => { 330 onChange={e => {
333 setRouteContent({ 331 setRouteContent({
334 ...routeContent, 332 ...routeContent,
335 description: e.target.value, 333 description: e.target.value,
@@ -339,7 +337,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
339 /> 337 />
340 </div> 338 </div>
341 <button 339 <button
342 style={{ gridColumn: "2 / span 3", height: "40px" }} 340 style={{ gridColumn: '2 / span 3', height: '40px' }}
343 onClick={_edit_map_summary_route} 341 onClick={_edit_map_summary_route}
344 > 342 >
345 Apply 343 Apply
@@ -380,7 +378,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
380 <div id="modview-route-category"> 378 <div id="modview-route-category">
381 <span>Category:</span> 379 <span>Category:</span>
382 <select 380 <select
383 onChange={(e) => { 381 onChange={e => {
384 setRouteContent({ 382 setRouteContent({
385 ...routeContent, 383 ...routeContent,
386 category_id: parseInt(e.target.value), 384 category_id: parseInt(e.target.value),
@@ -393,8 +391,8 @@ const ModMenu: React.FC<ModMenuProps> = ({
393 <option value="2" key="2"> 391 <option value="2" key="2">
394 No SLA 392 No SLA
395 </option> 393 </option>
396 {data.map.game_name === "Portal 2 - Cooperative" ? ( 394 {data.map.game_name === 'Portal 2 - Cooperative' ? (
397 "" 395 ''
398 ) : ( 396 ) : (
399 <option value="3" key="3"> 397 <option value="3" key="3">
400 Inbounds SLA 398 Inbounds SLA
@@ -410,7 +408,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
410 <input 408 <input
411 type="text" 409 type="text"
412 value={routeContent.name} 410 value={routeContent.name}
413 onChange={(e) => { 411 onChange={e => {
414 setRouteContent({ 412 setRouteContent({
415 ...routeContent, 413 ...routeContent,
416 name: e.target.value, 414 name: e.target.value,
@@ -423,7 +421,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
423 <input 421 <input
424 type="number" 422 type="number"
425 value={routeContent.score} 423 value={routeContent.score}
426 onChange={(e) => { 424 onChange={e => {
427 setRouteContent({ 425 setRouteContent({
428 ...routeContent, 426 ...routeContent,
429 score: parseInt(e.target.value), 427 score: parseInt(e.target.value),
@@ -436,7 +434,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
436 <input 434 <input
437 type="date" 435 type="date"
438 value={routeContent.date} 436 value={routeContent.date}
439 onChange={(e) => { 437 onChange={e => {
440 setRouteContent({ 438 setRouteContent({
441 ...routeContent, 439 ...routeContent,
442 date: e.target.value, 440 date: e.target.value,
@@ -449,7 +447,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
449 <input 447 <input
450 type="text" 448 type="text"
451 value={routeContent.showcase} 449 value={routeContent.showcase}
452 onChange={(e) => { 450 onChange={e => {
453 setRouteContent({ 451 setRouteContent({
454 ...routeContent, 452 ...routeContent,
455 showcase: e.target.value, 453 showcase: e.target.value,
@@ -459,12 +457,12 @@ const ModMenu: React.FC<ModMenuProps> = ({
459 </div> 457 </div>
460 <div 458 <div
461 id="modview-route-description" 459 id="modview-route-description"
462 style={{ height: "180px", gridColumn: "1 / span 5" }} 460 style={{ height: '180px', gridColumn: '1 / span 5' }}
463 > 461 >
464 <span>Description:</span> 462 <span>Description:</span>
465 <textarea 463 <textarea
466 value={routeContent.description} 464 value={routeContent.description}
467 onChange={(e) => { 465 onChange={e => {
468 setRouteContent({ 466 setRouteContent({
469 ...routeContent, 467 ...routeContent,
470 description: e.target.value, 468 description: e.target.value,
@@ -474,7 +472,7 @@ const ModMenu: React.FC<ModMenuProps> = ({
474 /> 472 />
475 </div> 473 </div>
476 <button 474 <button
477 style={{ gridColumn: "2 / span 3", height: "40px" }} 475 style={{ gridColumn: '2 / span 3', height: '40px' }}
478 onClick={_create_map_summary_route} 476 onClick={_create_map_summary_route}
479 > 477 >
480 Apply 478 Apply
diff --git a/frontend/src/components/RankingEntry.tsx b/frontend/src/components/RankingEntry.tsx
index b899965..add36ca 100644
--- a/frontend/src/components/RankingEntry.tsx
+++ b/frontend/src/components/RankingEntry.tsx
@@ -1,46 +1,66 @@
1import React from 'react'; 1import React from 'react';
2import { Link } from "react-router-dom"; 2import { Link } from 'react-router-dom';
3import { RankingType, SteamRanking, SteamRankingType } from '@customTypes/Ranking'; 3import {
4 RankingType,
5 SteamRanking,
6 SteamRankingType,
7} from '@customTypes/Ranking';
4 8
5enum RankingCategories { 9enum RankingCategories {
6 rankings_overall, 10 rankings_overall,
7 rankings_multiplayer, 11 rankings_multiplayer,
8 rankings_singleplayer 12 rankings_singleplayer,
9} 13}
10 14
11interface RankingEntryProps { 15interface RankingEntryProps {
12 curRankingData: RankingType | SteamRankingType; 16 curRankingData: RankingType | SteamRankingType;
13 currentLeaderboardType: RankingCategories 17 currentLeaderboardType: RankingCategories;
14};
15
16const RankingEntry: React.FC<RankingEntryProps> = (prop) => {
17 if ("placement" in prop.curRankingData) {
18 return (
19 <div className='leaderboard-entry'>
20 <span>{prop.curRankingData.placement}</span>
21 <div>
22 <Link to={`/users/${prop.curRankingData.user.steam_id}`}>
23 <img src={prop.curRankingData.user.avatar_link}></img>
24 <span>{prop.curRankingData.user.user_name}</span>
25 </Link>
26 </div>
27 <span>{prop.curRankingData.total_score}</span>
28 </div>
29 )
30 } else {
31 return (
32 <div className='leaderboard-entry'>
33 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_rank : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_rank : prop.curRankingData.overall_rank}</span>
34 <div>
35 <Link to={`/users/${prop.curRankingData.steam_id}`}>
36 <img src={prop.curRankingData.avatar_link}></img>
37 <span>{prop.curRankingData.user_name}</span>
38 </Link>
39 </div>
40 <span>{prop.currentLeaderboardType == RankingCategories.rankings_singleplayer ? prop.curRankingData.sp_score : prop.currentLeaderboardType == RankingCategories.rankings_multiplayer ? prop.curRankingData.mp_score : prop.curRankingData.overall_score}</span>
41 </div>
42 )
43 }
44} 18}
45 19
20const RankingEntry: React.FC<RankingEntryProps> = prop => {
21 if ('placement' in prop.curRankingData) {
22 return (
23 <div className="leaderboard-entry">
24 <span>{prop.curRankingData.placement}</span>
25 <div>
26 <Link to={`/users/${prop.curRankingData.user.steam_id}`}>
27 <img src={prop.curRankingData.user.avatar_link}></img>
28 <span>{prop.curRankingData.user.user_name}</span>
29 </Link>
30 </div>
31 <span>{prop.curRankingData.total_score}</span>
32 </div>
33 );
34 } else {
35 return (
36 <div className="leaderboard-entry">
37 <span>
38 {prop.currentLeaderboardType ==
39 RankingCategories.rankings_singleplayer
40 ? prop.curRankingData.sp_rank
41 : prop.currentLeaderboardType ==
42 RankingCategories.rankings_multiplayer
43 ? prop.curRankingData.mp_rank
44 : prop.curRankingData.overall_rank}
45 </span>
46 <div>
47 <Link to={`/users/${prop.curRankingData.steam_id}`}>
48 <img src={prop.curRankingData.avatar_link}></img>
49 <span>{prop.curRankingData.user_name}</span>
50 </Link>
51 </div>
52 <span>
53 {prop.currentLeaderboardType ==
54 RankingCategories.rankings_singleplayer
55 ? prop.curRankingData.sp_score
56 : prop.currentLeaderboardType ==
57 RankingCategories.rankings_multiplayer
58 ? prop.curRankingData.mp_score
59 : prop.curRankingData.overall_score}
60 </span>
61 </div>
62 );
63 }
64};
65
46export default RankingEntry; 66export default RankingEntry;
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 67f7f3d..71b79be 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,23 +1,38 @@
1import React from 'react'; 1import React from 'react';
2import { Link, useLocation } from 'react-router-dom'; 2import { Link, useLocation } from 'react-router-dom';
3 3
4import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; 4import {
5 BookIcon,
6 FlagIcon,
7 HelpIcon,
8 HomeIcon,
9 LogoIcon,
10 PortalIcon,
11 SearchIcon,
12 UploadIcon,
13} from '@images/Images';
5import Login from '@components/Login'; 14import Login from '@components/Login';
6import { UserProfile } from '@customTypes/Profile'; 15import { UserProfile } from '@customTypes/Profile';
7import { Search } from '@customTypes/Search'; 16import { Search } from '@customTypes/Search';
8import { API } from '@api/Api'; 17import { API } from '@api/Api';
9import "@css/Sidebar.css"; 18import '@css/Sidebar.css';
10 19
11interface SidebarProps { 20interface SidebarProps {
12 setToken: React.Dispatch<React.SetStateAction<string | undefined>>; 21 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
13 profile?: UserProfile; 22 profile?: UserProfile;
14 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>; 23 setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>;
15 onUploadRun: () => void; 24 onUploadRun: () => void;
16}; 25}
17
18const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => {
19 26
20 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined); 27const Sidebar: React.FC<SidebarProps> = ({
28 setToken,
29 profile,
30 setProfile,
31 onUploadRun,
32}) => {
33 const [searchData, setSearchData] = React.useState<Search | undefined>(
34 undefined
35 );
21 const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false); 36 const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false);
22 const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true); 37 const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true);
23 38
@@ -25,71 +40,86 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
25 const path = location.pathname; 40 const path = location.pathname;
26 41
27 const handle_sidebar_click = (clicked_sidebar_idx: number) => { 42 const handle_sidebar_click = (clicked_sidebar_idx: number) => {
28 const btn = document.querySelectorAll("button.sidebar-button"); 43 const btn = document.querySelectorAll('button.sidebar-button');
29 if (isSidebarOpen) { setSidebarOpen(false); _handle_sidebar_hide() } 44 if (isSidebarOpen) {
45 setSidebarOpen(false);
46 _handle_sidebar_hide();
47 }
30 // clusterfuck 48 // clusterfuck
31 btn.forEach((e, i) => { 49 btn.forEach((e, i) => {
32 btn[i].classList.remove("sidebar-button-selected") 50 btn[i].classList.remove('sidebar-button-selected');
33 btn[i].classList.add("sidebar-button-deselected") 51 btn[i].classList.add('sidebar-button-deselected');
34 }) 52 });
35 btn[clicked_sidebar_idx].classList.add("sidebar-button-selected") 53 btn[clicked_sidebar_idx].classList.add('sidebar-button-selected');
36 btn[clicked_sidebar_idx].classList.remove("sidebar-button-deselected") 54 btn[clicked_sidebar_idx].classList.remove('sidebar-button-deselected');
37 }; 55 };
38 56
39 const _handle_sidebar_hide = () => { 57 const _handle_sidebar_hide = () => {
40 var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement> 58 var btn = document.querySelectorAll(
41 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement> 59 'button.sidebar-button'
42 const side = document.querySelector("#sidebar-list") as HTMLElement; 60 ) as NodeListOf<HTMLElement>;
43 const searchbar = document.querySelector("#searchbar") as HTMLInputElement; 61 const span = document.querySelectorAll(
44 const uploadRunBtn = document.querySelector("#upload-run") as HTMLInputElement; 62 'button.sidebar-button>span'
45 const uploadRunSpan = document.querySelector("#upload-run>span") as HTMLInputElement; 63 ) as NodeListOf<HTMLElement>;
64 const side = document.querySelector('#sidebar-list') as HTMLElement;
65 const searchbar = document.querySelector('#searchbar') as HTMLInputElement;
66 const uploadRunBtn = document.querySelector(
67 '#upload-run'
68 ) as HTMLInputElement;
69 const uploadRunSpan = document.querySelector(
70 '#upload-run>span'
71 ) as HTMLInputElement;
46 72
47 if (isSidebarOpen) { 73 if (isSidebarOpen) {
48 if (profile) { 74 if (profile) {
49 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 75 const login = document.querySelectorAll(
50 login.style.opacity = "1" 76 '.login>button'
51 uploadRunBtn.style.width = "310px" 77 )[1] as HTMLElement;
52 uploadRunBtn.style.padding = "0.4em 0 0 11px" 78 login.style.opacity = '1';
53 uploadRunSpan.style.opacity = "0" 79 uploadRunBtn.style.width = '310px';
80 uploadRunBtn.style.padding = '0.4em 0 0 11px';
81 uploadRunSpan.style.opacity = '0';
54 setTimeout(() => { 82 setTimeout(() => {
55 uploadRunSpan.style.opacity = "1" 83 uploadRunSpan.style.opacity = '1';
56 }, 100) 84 }, 100);
57 } 85 }
58 setSidebarOpen(false); 86 setSidebarOpen(false);
59 side.style.width = "320px" 87 side.style.width = '320px';
60 btn.forEach((e, i) => { 88 btn.forEach((e, i) => {
61 e.style.width = "310px" 89 e.style.width = '310px';
62 e.style.padding = "0.4em 0 0 11px" 90 e.style.padding = '0.4em 0 0 11px';
63 setTimeout(() => { 91 setTimeout(() => {
64 span[i].style.opacity = "1" 92 span[i].style.opacity = '1';
65 }, 100) 93 }, 100);
66 }); 94 });
67 side.style.zIndex = "2" 95 side.style.zIndex = '2';
68 } else { 96 } else {
69 if (profile) { 97 if (profile) {
70 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 98 const login = document.querySelectorAll(
71 login.style.opacity = "0" 99 '.login>button'
72 uploadRunBtn.style.width = "40px" 100 )[1] as HTMLElement;
73 uploadRunBtn.style.padding = "0.4em 0 0 5px" 101 login.style.opacity = '0';
74 uploadRunSpan.style.opacity = "0" 102 uploadRunBtn.style.width = '40px';
103 uploadRunBtn.style.padding = '0.4em 0 0 5px';
104 uploadRunSpan.style.opacity = '0';
75 } 105 }
76 setSidebarOpen(true); 106 setSidebarOpen(true);
77 side.style.width = "40px"; 107 side.style.width = '40px';
78 searchbar.focus(); 108 searchbar.focus();
79 btn.forEach((e, i) => { 109 btn.forEach((e, i) => {
80 e.style.width = "40px" 110 e.style.width = '40px';
81 e.style.padding = "0.4em 0 0 5px" 111 e.style.padding = '0.4em 0 0 5px';
82 span[i].style.opacity = "0" 112 span[i].style.opacity = '0';
83 }) 113 });
84 setTimeout(() => { 114 setTimeout(() => {
85 side.style.zIndex = "0" 115 side.style.zIndex = '0';
86 }, 300); 116 }, 300);
87 } 117 }
88 }; 118 };
89 119
90 const _handle_sidebar_lock = () => { 120 const _handle_sidebar_lock = () => {
91 if (!isSidebarLocked) { 121 if (!isSidebarLocked) {
92 _handle_sidebar_hide() 122 _handle_sidebar_hide();
93 setIsSidebarLocked(true); 123 setIsSidebarLocked(true);
94 setTimeout(() => setIsSidebarLocked(false), 300); 124 setTimeout(() => setIsSidebarLocked(false), 300);
95 } 125 }
@@ -101,98 +131,148 @@ const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUplo
101 }; 131 };
102 132
103 React.useEffect(() => { 133 React.useEffect(() => {
104 if (path === "/") { handle_sidebar_click(1) } 134 if (path === '/') {
105 else if (path.includes("games")) { handle_sidebar_click(2) } 135 handle_sidebar_click(1);
106 else if (path.includes("rankings")) { handle_sidebar_click(3) } 136 } else if (path.includes('games')) {
137 handle_sidebar_click(2);
138 } else if (path.includes('rankings')) {
139 handle_sidebar_click(3);
140 }
107 // else if (path.includes("news")) { handle_sidebar_click(4) } 141 // else if (path.includes("news")) { handle_sidebar_click(4) }
108 // else if (path.includes("scorelog")) { handle_sidebar_click(5) } 142 // else if (path.includes("scorelog")) { handle_sidebar_click(5) }
109 else if (path.includes("profile")) { handle_sidebar_click(4) } 143 else if (path.includes('profile')) {
110 else if (path.includes("rules")) { handle_sidebar_click(5) } 144 handle_sidebar_click(4);
111 else if (path.includes("about")) { handle_sidebar_click(6) } 145 } else if (path.includes('rules')) {
146 handle_sidebar_click(5);
147 } else if (path.includes('about')) {
148 handle_sidebar_click(6);
149 }
112 }, [path]); 150 }, [path]);
113 151
114 return ( 152 return (
115 <div id='sidebar'> 153 <div id="sidebar">
116 <Link to="/" tabIndex={-1}> 154 <Link to="/" tabIndex={-1}>
117 <div id='logo'> {/* logo */} 155 <div id="logo">
118 <img src={LogoIcon} alt="" height={"80px"} /> 156 {' '}
119 <div id='logo-text'> 157 {/* logo */}
120 <span><b>PORTAL 2</b></span><br /> 158 <img src={LogoIcon} alt="" height={'80px'} />
159 <div id="logo-text">
160 <span>
161 <b>PORTAL 2</b>
162 </span>
163 <br />
121 <span>Least Portals Hub</span> 164 <span>Least Portals Hub</span>
122 </div> 165 </div>
123 </div> 166 </div>
124 </Link> 167 </Link>
125 <div id='sidebar-list'> {/* List */} 168 <div id="sidebar-list">
126 <div id='sidebar-toplist'> {/* Top */} 169 {' '}
127 170 {/* List */}
128 <button className='sidebar-button' onClick={() => _handle_sidebar_lock()}><img src={SearchIcon} alt="" /><span>Search</span></button> 171 <div id="sidebar-toplist">
129 172 {' '}
173 {/* Top */}
174 <button
175 className="sidebar-button"
176 onClick={() => _handle_sidebar_lock()}
177 >
178 <img src={SearchIcon} alt="" />
179 <span>Search</span>
180 </button>
130 <span></span> 181 <span></span>
131
132 <Link to="/" tabIndex={-1}> 182 <Link to="/" tabIndex={-1}>
133 <button className='sidebar-button'><img src={HomeIcon} alt="homepage" /><span>Home&nbsp;Page</span></button> 183 <button className="sidebar-button">
184 <img src={HomeIcon} alt="homepage" />
185 <span>Home&nbsp;Page</span>
186 </button>
134 </Link> 187 </Link>
135
136 <Link to="/games" tabIndex={-1}> 188 <Link to="/games" tabIndex={-1}>
137 <button className='sidebar-button'><img src={PortalIcon} alt="games" /><span>Games</span></button> 189 <button className="sidebar-button">
190 <img src={PortalIcon} alt="games" />
191 <span>Games</span>
192 </button>
138 </Link> 193 </Link>
139
140 <Link to="/rankings" tabIndex={-1}> 194 <Link to="/rankings" tabIndex={-1}>
141 <button className='sidebar-button'><img src={FlagIcon} alt="rankings" /><span>Rankings</span></button> 195 <button className="sidebar-button">
196 <img src={FlagIcon} alt="rankings" />
197 <span>Rankings</span>
198 </button>
142 </Link> 199 </Link>
143
144 {/* <Link to="/news" tabIndex={-1}> 200 {/* <Link to="/news" tabIndex={-1}>
145 <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button> 201 <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button>
146 </Link> */} 202 </Link> */}
147
148 {/* <Link to="/scorelog" tabIndex={-1}> 203 {/* <Link to="/scorelog" tabIndex={-1}>
149 <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button> 204 <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button>
150 </Link> */} 205 </Link> */}
151 </div> 206 </div>
152 <div id='sidebar-bottomlist'> 207 <div id="sidebar-bottomlist">
153 <span></span> 208 <span></span>
154 209
155 { 210 {profile && profile.profile ? (
156 profile && profile.profile ? 211 <button
157 <button id='upload-run' className='submit-run-button' onClick={() => onUploadRun()}><img src={UploadIcon} alt="upload" /><span>Upload&nbsp;Record</span></button> 212 id="upload-run"
158 : 213 className="submit-run-button"
159 <span></span> 214 onClick={() => onUploadRun()}
160 } 215 >
216 <img src={UploadIcon} alt="upload" />
217 <span>Upload&nbsp;Record</span>
218 </button>
219 ) : (
220 <span></span>
221 )}
161 222
162 <Login setToken={setToken} profile={profile} setProfile={setProfile} /> 223 <Login
224 setToken={setToken}
225 profile={profile}
226 setProfile={setProfile}
227 />
163 228
164 <Link to="/rules" tabIndex={-1}> 229 <Link to="/rules" tabIndex={-1}>
165 <button className='sidebar-button'><img src={BookIcon} alt="rules" /><span>Leaderboard&nbsp;Rules</span></button> 230 <button className="sidebar-button">
231 <img src={BookIcon} alt="rules" />
232 <span>Leaderboard&nbsp;Rules</span>
233 </button>
166 </Link> 234 </Link>
167 235
168 <Link to="/about" tabIndex={-1}> 236 <Link to="/about" tabIndex={-1}>
169 <button className='sidebar-button'><img src={HelpIcon} alt="about" /><span>About&nbsp;LPHUB</span></button> 237 <button className="sidebar-button">
238 <img src={HelpIcon} alt="about" />
239 <span>About&nbsp;LPHUB</span>
240 </button>
170 </Link> 241 </Link>
171 </div> 242 </div>
172 </div> 243 </div>
173 <div> 244 <div>
174 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} /> 245 <input
175 246 type="text"
176 <div id='search-data'> 247 id="searchbar"
248 placeholder="Search for map or a player..."
249 onChange={e => _handle_search_change(e.target.value)}
250 />
177 251
252 <div id="search-data">
178 {searchData?.maps.map((q, index) => ( 253 {searchData?.maps.map((q, index) => (
179 <Link to={`/maps/${q.id}`} className='search-map' key={index}> 254 <Link to={`/maps/${q.id}`} className="search-map" key={index}>
180 <span>{q.game}</span> 255 <span>{q.game}</span>
181 <span>{q.chapter}</span> 256 <span>{q.chapter}</span>
182 <span>{q.map}</span> 257 <span>{q.map}</span>
183 </Link> 258 </Link>
184 ))} 259 ))}
185 {searchData?.players.map((q, index) => 260 {searchData?.players.map((q, index) => (
186 ( 261 <Link
187 <Link to={ 262 to={
188 profile && q.steam_id === profile.steam_id ? `/profile` : 263 profile && q.steam_id === profile.steam_id
189 `/users/${q.steam_id}` 264 ? `/profile`
190 } className='search-player' key={index}> 265 : `/users/${q.steam_id}`
191 <img src={q.avatar_link} alt='pfp'></img> 266 }
192 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span> 267 className="search-player"
268 key={index}
269 >
270 <img src={q.avatar_link} alt="pfp"></img>
271 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>
272 {q.user_name}
273 </span>
193 </Link> 274 </Link>
194 ))} 275 ))}
195
196 </div> 276 </div>
197 </div> 277 </div>
198 </div> 278 </div>
diff --git a/frontend/src/components/Summary.tsx b/frontend/src/components/Summary.tsx
index 4bcaa6a..1ba166a 100644
--- a/frontend/src/components/Summary.tsx
+++ b/frontend/src/components/Summary.tsx
@@ -2,49 +2,67 @@ import React from 'react';
2import ReactMarkdown from 'react-markdown'; 2import ReactMarkdown from 'react-markdown';
3 3
4import { MapSummary } from '@customTypes/Map'; 4import { MapSummary } from '@customTypes/Map';
5import "@css/Maps.css" 5import '@css/Maps.css';
6 6
7interface SummaryProps { 7interface SummaryProps {
8 selectedRun: number 8 selectedRun: number;
9 setSelectedRun: (x: number) => void; 9 setSelectedRun: (x: number) => void;
10 data: MapSummary; 10 data: MapSummary;
11} 11}
12 12
13const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data }) => { 13const Summary: React.FC<SummaryProps> = ({
14 14 selectedRun,
15 setSelectedRun,
16 data,
17}) => {
15 const [selectedCategory, setSelectedCategory] = React.useState<number>(1); 18 const [selectedCategory, setSelectedCategory] = React.useState<number>(1);
16 const [historySelected, setHistorySelected] = React.useState<boolean>(false); 19 const [historySelected, setHistorySelected] = React.useState<boolean>(false);
17 20
18 function _select_run(idx: number, category_id: number) { 21 function _select_run(idx: number, category_id: number) {
19 let r = document.querySelectorAll("button.record"); 22 let r = document.querySelectorAll('button.record');
20 r.forEach(e => (e as HTMLElement).style.backgroundColor = "#2b2e46"); 23 r.forEach(e => ((e as HTMLElement).style.backgroundColor = '#2b2e46'));
21 (r[idx] as HTMLElement).style.backgroundColor = "#161723" 24 (r[idx] as HTMLElement).style.backgroundColor = '#161723';
22
23 25
24 if (data && data.summary.routes.length !== 0) { 26 if (data && data.summary.routes.length !== 0) {
25 idx += data.summary.routes.filter(e => e.category.id < category_id).length // lethimcook 27 idx += data.summary.routes.filter(
28 e => e.category.id < category_id
29 ).length; // lethimcook
26 setSelectedRun(idx); 30 setSelectedRun(idx);
27 } 31 }
28 }; 32 }
29 33
30 function _get_youtube_id(url: string): string { 34 function _get_youtube_id(url: string): string {
31 const urlArray = url.split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/); 35 const urlArray = url.split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
32 return (urlArray[2] !== undefined) ? urlArray[2].split(/[^0-9a-z_-]/i)[0] : urlArray[0]; 36 return urlArray[2] !== undefined
33 }; 37 ? urlArray[2].split(/[^0-9a-z_-]/i)[0]
38 : urlArray[0];
39 }
34 40
35 function _category_change() { 41 function _category_change() {
36 const btn = document.querySelectorAll("#section3 #category span button"); 42 const btn = document.querySelectorAll('#section3 #category span button');
37 btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); 43 btn.forEach(e => {
44 (e as HTMLElement).style.backgroundColor = '#2b2e46';
45 });
38 // heavenly father forgive me for i have sinned. TODO: fix this bullshit with dynamic categories 46 // heavenly father forgive me for i have sinned. TODO: fix this bullshit with dynamic categories
39 const idx = selectedCategory === 1 ? 0 : data.map.is_coop ? selectedCategory - 3 : selectedCategory - 1; 47 const idx =
40 (btn[idx] as HTMLElement).style.backgroundColor = "#202232"; 48 selectedCategory === 1
41 }; 49 ? 0
50 : data.map.is_coop
51 ? selectedCategory - 3
52 : selectedCategory - 1;
53 (btn[idx] as HTMLElement).style.backgroundColor = '#202232';
54 }
42 55
43 function _history_change() { 56 function _history_change() {
44 const btn = document.querySelectorAll("#section3 #history span button"); 57 const btn = document.querySelectorAll('#section3 #history span button');
45 btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); 58 btn.forEach(e => {
46 (historySelected ? btn[1] as HTMLElement : btn[0] as HTMLElement).style.backgroundColor = "#202232"; 59 (e as HTMLElement).style.backgroundColor = '#2b2e46';
47 }; 60 });
61 (historySelected
62 ? (btn[1] as HTMLElement)
63 : (btn[0] as HTMLElement)
64 ).style.backgroundColor = '#202232';
65 }
48 66
49 React.useEffect(() => { 67 React.useEffect(() => {
50 _history_change(); 68 _history_change();
@@ -61,119 +79,188 @@ const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data })
61 79
62 return ( 80 return (
63 <> 81 <>
64 <section id='section3' className='summary1'> 82 <section id="section3" className="summary1">
65 <div id='category' 83 <div
66 style={data.map.image === "" ? { backgroundColor: "#202232" } : {}}> 84 id="category"
67 <img src={data.map.image} alt="" id='category-image'></img> 85 style={data.map.image === '' ? { backgroundColor: '#202232' } : {}}
68 <p><span className='portal-count'>{data.summary.routes[selectedRun].history.score_count}</span> 86 >
69 {data.summary.routes[selectedRun].history.score_count === 1 ? ` portal` : ` portals`}</p> 87 <img src={data.map.image} alt="" id="category-image"></img>
70 {data.map.is_coop ? // TODO: make this part dynamic 88 <p>
71 ( 89 <span className="portal-count">
72 <span style={{ gridTemplateColumns: "1fr 1fr 1fr" }}> 90 {data.summary.routes[selectedRun].history.score_count}
73 <button onClick={() => setSelectedCategory(1)}>CM</button> 91 </span>
74 <button onClick={() => setSelectedCategory(4)}>Any%</button> 92 {data.summary.routes[selectedRun].history.score_count === 1
75 <button onClick={() => setSelectedCategory(5)}>All Courses</button> 93 ? ` portal`
76 </span> 94 : ` portals`}
77 ) 95 </p>
78 : 96 {data.map.is_coop ? ( // TODO: make this part dynamic
79 ( 97 <span style={{ gridTemplateColumns: '1fr 1fr 1fr' }}>
80 <span style={{ gridTemplateColumns: "1fr 1fr 1fr 1fr" }}> 98 <button onClick={() => setSelectedCategory(1)}>CM</button>
81 99 <button onClick={() => setSelectedCategory(4)}>Any%</button>
82 <button onClick={() => setSelectedCategory(1)}>CM</button> 100 <button onClick={() => setSelectedCategory(5)}>
83 <button onClick={() => setSelectedCategory(2)}>NoSLA</button> 101 All Courses
84 <button onClick={() => setSelectedCategory(3)}>Inbounds SLA</button> 102 </button>
85 <button onClick={() => setSelectedCategory(4)}>Any%</button> 103 </span>
86 </span> 104 ) : (
87 ) 105 <span style={{ gridTemplateColumns: '1fr 1fr 1fr 1fr' }}>
88 } 106 <button onClick={() => setSelectedCategory(1)}>CM</button>
89 107 <button onClick={() => setSelectedCategory(2)}>NoSLA</button>
108 <button onClick={() => setSelectedCategory(3)}>
109 Inbounds SLA
110 </button>
111 <button onClick={() => setSelectedCategory(4)}>Any%</button>
112 </span>
113 )}
90 </div> 114 </div>
91 115
92 <div id='history'> 116 <div id="history">
93 117 <div style={{ display: historySelected ? 'none' : 'block' }}>
94 <div style={{ display: historySelected ? "none" : "block" }}> 118 {data.summary.routes.filter(e => e.category.id === selectedCategory)
95 {data.summary.routes.filter(e => e.category.id === selectedCategory).length === 0 ? <h5>There are no records for this map.</h5> : 119 .length === 0 ? (
120 <h5>There are no records for this map.</h5>
121 ) : (
96 <> 122 <>
97 <div className='record-top'> 123 <div className="record-top">
98 <span>Date</span> 124 <span>Date</span>
99 <span>Record</span> 125 <span>Record</span>
100 <span>First Completion</span> 126 <span>First Completion</span>
101 </div> 127 </div>
102 <hr /> 128 <hr />
103 <div id='records'> 129 <div id="records">
104
105 {data.summary.routes 130 {data.summary.routes
106 .filter(e => e.category.id === selectedCategory) 131 .filter(e => e.category.id === selectedCategory)
107 .map((r, index) => ( 132 .map((r, index) => (
108 <button className='record' key={index} onClick={() => { 133 <button
109 _select_run(index, r.category.id); 134 className="record"
110 }}> 135 key={index}
111 <span>{new Date(r.history.date).toLocaleDateString( 136 onClick={() => {
112 "en-US", { month: 'long', day: 'numeric', year: 'numeric' } 137 _select_run(index, r.category.id);
113 )}</span> 138 }}
139 >
140 <span>
141 {new Date(r.history.date).toLocaleDateString(
142 'en-US',
143 { month: 'long', day: 'numeric', year: 'numeric' }
144 )}
145 </span>
114 <span>{r.history.score_count}</span> 146 <span>{r.history.score_count}</span>
115 <span>{r.history.runner_name}</span> 147 <span>{r.history.runner_name}</span>
116 </button> 148 </button>
117 ))} 149 ))}
118 </div> 150 </div>
119 </> 151 </>
120 } 152 )}
121 </div> 153 </div>
122 154
123 <div style={{ display: historySelected ? "block" : "none" }}> 155 <div style={{ display: historySelected ? 'block' : 'none' }}>
124 {data.summary.routes.filter(e => e.category.id === selectedCategory).length === 0 ? <h5>There are no records for this map.</h5> : 156 {data.summary.routes.filter(e => e.category.id === selectedCategory)
125 <div id='graph'> 157 .length === 0 ? (
158 <h5>There are no records for this map.</h5>
159 ) : (
160 <div id="graph">
126 {/* <div>{graph(1)}</div> 161 {/* <div>{graph(1)}</div>
127 <div>{graph(2)}</div> 162 <div>{graph(2)}</div>
128 <div>{graph(3)}</div> */} 163 <div>{graph(3)}</div> */}
129 </div> 164 </div>
130 } 165 )}
131 </div> 166 </div>
132 <span> 167 <span>
133 <button onClick={() => setHistorySelected(false)}>List</button> 168 <button onClick={() => setHistorySelected(false)}>List</button>
134 <button onClick={() => setHistorySelected(true)}>Graph</button> 169 <button onClick={() => setHistorySelected(true)}>Graph</button>
135 </span> 170 </span>
136 </div> 171 </div>
137 172 </section>
138 173 <section id="section4" className="summary1">
139 </section > 174 <div id="difficulty">
140 <section id='section4' className='summary1'>
141 <div id='difficulty'>
142 <span>Difficulty</span> 175 <span>Difficulty</span>
143 {data.summary.routes[selectedRun].rating === 0 && (<span>N/A</span>)} 176 {data.summary.routes[selectedRun].rating === 0 && <span>N/A</span>}
144 {data.summary.routes[selectedRun].rating === 1 && (<span style={{ color: "lime" }}>Very easy</span>)} 177 {data.summary.routes[selectedRun].rating === 1 && (
145 {data.summary.routes[selectedRun].rating === 2 && (<span style={{ color: "green" }}>Easy</span>)} 178 <span style={{ color: 'lime' }}>Very easy</span>
146 {data.summary.routes[selectedRun].rating === 3 && (<span style={{ color: "yellow" }}>Medium</span>)} 179 )}
147 {data.summary.routes[selectedRun].rating === 4 && (<span style={{ color: "orange" }}>Hard</span>)} 180 {data.summary.routes[selectedRun].rating === 2 && (
148 {data.summary.routes[selectedRun].rating === 5 && (<span style={{ color: "red" }}>Very hard</span>)} 181 <span style={{ color: 'green' }}>Easy</span>
182 )}
183 {data.summary.routes[selectedRun].rating === 3 && (
184 <span style={{ color: 'yellow' }}>Medium</span>
185 )}
186 {data.summary.routes[selectedRun].rating === 4 && (
187 <span style={{ color: 'orange' }}>Hard</span>
188 )}
189 {data.summary.routes[selectedRun].rating === 5 && (
190 <span style={{ color: 'red' }}>Very hard</span>
191 )}
149 <div> 192 <div>
150 {data.summary.routes[selectedRun].rating === 1 ? (<div className='difficulty-rating' style={{ backgroundColor: "lime" }}></div>) : (<div className='difficulty-rating'></div>)} 193 {data.summary.routes[selectedRun].rating === 1 ? (
151 {data.summary.routes[selectedRun].rating === 2 ? (<div className='difficulty-rating' style={{ backgroundColor: "green" }}></div>) : (<div className='difficulty-rating'></div>)} 194 <div
152 {data.summary.routes[selectedRun].rating === 3 ? (<div className='difficulty-rating' style={{ backgroundColor: "yellow" }}></div>) : (<div className='difficulty-rating'></div>)} 195 className="difficulty-rating"
153 {data.summary.routes[selectedRun].rating === 4 ? (<div className='difficulty-rating' style={{ backgroundColor: "orange" }}></div>) : (<div className='difficulty-rating'></div>)} 196 style={{ backgroundColor: 'lime' }}
154 {data.summary.routes[selectedRun].rating === 5 ? (<div className='difficulty-rating' style={{ backgroundColor: "red" }}></div>) : (<div className='difficulty-rating'></div>)} 197 ></div>
198 ) : (
199 <div className="difficulty-rating"></div>
200 )}
201 {data.summary.routes[selectedRun].rating === 2 ? (
202 <div
203 className="difficulty-rating"
204 style={{ backgroundColor: 'green' }}
205 ></div>
206 ) : (
207 <div className="difficulty-rating"></div>
208 )}
209 {data.summary.routes[selectedRun].rating === 3 ? (
210 <div
211 className="difficulty-rating"
212 style={{ backgroundColor: 'yellow' }}
213 ></div>
214 ) : (
215 <div className="difficulty-rating"></div>
216 )}
217 {data.summary.routes[selectedRun].rating === 4 ? (
218 <div
219 className="difficulty-rating"
220 style={{ backgroundColor: 'orange' }}
221 ></div>
222 ) : (
223 <div className="difficulty-rating"></div>
224 )}
225 {data.summary.routes[selectedRun].rating === 5 ? (
226 <div
227 className="difficulty-rating"
228 style={{ backgroundColor: 'red' }}
229 ></div>
230 ) : (
231 <div className="difficulty-rating"></div>
232 )}
155 </div> 233 </div>
156 </div> 234 </div>
157 <div id='count'> 235 <div id="count">
158 <span>Completion Count</span> 236 <span>Completion Count</span>
159 <div>{data.summary.routes[selectedRun].completion_count}</div> 237 <div>{data.summary.routes[selectedRun].completion_count}</div>
160 </div> 238 </div>
161 </section> 239 </section>
162 240
163 <section id='section5' className='summary1'> 241 <section id="section5" className="summary1">
164 <div id='description'> 242 <div id="description">
165 {data.summary.routes[selectedRun].showcase !== "" ? 243 {data.summary.routes[selectedRun].showcase !== '' ? (
166 <iframe title='Showcase video' src={"https://www.youtube.com/embed/" + _get_youtube_id(data.summary.routes[selectedRun].showcase)}> </iframe> 244 <iframe
167 : ""} 245 title="Showcase video"
246 src={
247 'https://www.youtube.com/embed/' +
248 _get_youtube_id(data.summary.routes[selectedRun].showcase)
249 }
250 >
251 {' '}
252 </iframe>
253 ) : (
254 ''
255 )}
168 <h3>Route Description</h3> 256 <h3>Route Description</h3>
169 <span id='description-text'> 257 <span id="description-text">
170 <ReactMarkdown> 258 <ReactMarkdown>
171 {data.summary.routes[selectedRun].description} 259 {data.summary.routes[selectedRun].description}
172 </ReactMarkdown> 260 </ReactMarkdown>
173 </span> 261 </span>
174 </div> 262 </div>
175 </section> 263 </section>
176
177 </> 264 </>
178 ); 265 );
179}; 266};
diff --git a/frontend/src/components/UploadRunDialog.tsx b/frontend/src/components/UploadRunDialog.tsx
index c02fdb8..227a564 100644
--- a/frontend/src/components/UploadRunDialog.tsx
+++ b/frontend/src/components/UploadRunDialog.tsx
@@ -8,7 +8,7 @@ import { API } from '@api/Api';
8import { useNavigate } from 'react-router-dom'; 8import { useNavigate } from 'react-router-dom';
9import useMessage from '@hooks/UseMessage'; 9import useMessage from '@hooks/UseMessage';
10import useConfirm from '@hooks/UseConfirm'; 10import useConfirm from '@hooks/UseConfirm';
11import useMessageLoad from "@hooks/UseMessageLoad"; 11import useMessageLoad from '@hooks/UseMessageLoad';
12import { MapNames } from '@customTypes/MapNames'; 12import { MapNames } from '@customTypes/MapNames';
13 13
14interface UploadRunDialogProps { 14interface UploadRunDialogProps {
@@ -18,21 +18,27 @@ interface UploadRunDialogProps {
18 games: Game[]; 18 games: Game[];
19} 19}
20 20
21const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose, games }) => { 21const UploadRunDialog: React.FC<UploadRunDialogProps> = ({
22 22 token,
23 open,
24 onClose,
25 games,
26}) => {
23 const { message, MessageDialogComponent } = useMessage(); 27 const { message, MessageDialogComponent } = useMessage();
24 const { confirm, ConfirmDialogComponent } = useConfirm(); 28 const { confirm, ConfirmDialogComponent } = useConfirm();
25 const { messageLoad, messageLoadClose, MessageDialogLoadComponent } = useMessageLoad(); 29 const { messageLoad, messageLoadClose, MessageDialogLoadComponent } =
30 useMessageLoad();
26 31
27 const navigate = useNavigate(); 32 const navigate = useNavigate();
28 33
29 const [uploadRunContent, setUploadRunContent] = React.useState<UploadRunContent>({ 34 const [uploadRunContent, setUploadRunContent] =
30 host_demo: null, 35 React.useState<UploadRunContent>({
31 partner_demo: null, 36 host_demo: null,
32 }); 37 partner_demo: null,
38 });
33 39
34 const [selectedGameID, setSelectedGameID] = React.useState<number>(0); 40 const [selectedGameID, setSelectedGameID] = React.useState<number>(0);
35 const [selectedGameName, setSelectedGameName] = React.useState<string>(""); 41 const [selectedGameName, setSelectedGameName] = React.useState<string>('');
36 42
37 // dropdowns 43 // dropdowns
38 const [dropdown1Vis, setDropdown1Vis] = React.useState<boolean>(false); 44 const [dropdown1Vis, setDropdown1Vis] = React.useState<boolean>(false);
@@ -41,7 +47,8 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
41 const [loading, setLoading] = React.useState<boolean>(false); 47 const [loading, setLoading] = React.useState<boolean>(false);
42 48
43 const [dragHightlight, setDragHighlight] = React.useState<boolean>(false); 49 const [dragHightlight, setDragHighlight] = React.useState<boolean>(false);
44 const [dragHightlightPartner, setDragHighlightPartner] = React.useState<boolean>(false); 50 const [dragHightlightPartner, setDragHighlightPartner] =
51 React.useState<boolean>(false);
45 52
46 const fileInputRef = React.useRef<HTMLInputElement>(null); 53 const fileInputRef = React.useRef<HTMLInputElement>(null);
47 const fileInputRefPartner = React.useRef<HTMLInputElement>(null); 54 const fileInputRefPartner = React.useRef<HTMLInputElement>(null);
@@ -52,9 +59,12 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
52 } else { 59 } else {
53 fileInputRefPartner.current?.click(); 60 fileInputRefPartner.current?.click();
54 } 61 }
55 } 62 };
56 63
57 const _handle_drag_over = (e: React.DragEvent<HTMLDivElement>, host: boolean) => { 64 const _handle_drag_over = (
65 e: React.DragEvent<HTMLDivElement>,
66 host: boolean
67 ) => {
58 e.preventDefault(); 68 e.preventDefault();
59 e.stopPropagation(); 69 e.stopPropagation();
60 if (host) { 70 if (host) {
@@ -62,9 +72,12 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
62 } else { 72 } else {
63 setDragHighlightPartner(true); 73 setDragHighlightPartner(true);
64 } 74 }
65 } 75 };
66 76
67 const _handle_drag_leave = (e: React.DragEvent<HTMLDivElement>, host: boolean) => { 77 const _handle_drag_leave = (
78 e: React.DragEvent<HTMLDivElement>,
79 host: boolean
80 ) => {
68 e.preventDefault(); 81 e.preventDefault();
69 e.stopPropagation(); 82 e.stopPropagation();
70 if (host) { 83 if (host) {
@@ -72,7 +85,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
72 } else { 85 } else {
73 setDragHighlightPartner(false); 86 setDragHighlightPartner(false);
74 } 87 }
75 } 88 };
76 89
77 const _handle_drop = (e: React.DragEvent<HTMLDivElement>, host: boolean) => { 90 const _handle_drop = (e: React.DragEvent<HTMLDivElement>, host: boolean) => {
78 e.preventDefault(); 91 e.preventDefault();
@@ -80,7 +93,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
80 setDragHighlight(true); 93 setDragHighlight(true);
81 94
82 _handle_file_change(e.dataTransfer.files, host); 95 _handle_file_change(e.dataTransfer.files, host);
83 } 96 };
84 97
85 const _handle_dropdowns = (dropdown: number) => { 98 const _handle_dropdowns = (dropdown: number) => {
86 setDropdown1Vis(false); 99 setDropdown1Vis(false);
@@ -89,9 +102,9 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
89 setDropdown1Vis(!dropdown1Vis); 102 setDropdown1Vis(!dropdown1Vis);
90 } else if (dropdown == 2) { 103 } else if (dropdown == 2) {
91 setDropdown2Vis(!dropdown2Vis); 104 setDropdown2Vis(!dropdown2Vis);
92 document.querySelector("#dropdown2")?.scrollTo(0, 0); 105 document.querySelector('#dropdown2')?.scrollTo(0, 0);
93 } 106 }
94 } 107 };
95 108
96 const _handle_game_select = async (game_id: string, game_name: string) => { 109 const _handle_game_select = async (game_id: string, game_name: string) => {
97 setLoading(true); 110 setLoading(true);
@@ -120,62 +133,85 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
120 if (token) { 133 if (token) {
121 if (games[selectedGameID].is_coop) { 134 if (games[selectedGameID].is_coop) {
122 if (uploadRunContent.host_demo === null) { 135 if (uploadRunContent.host_demo === null) {
123 await message("Error", "You must select a host demo to upload.") 136 await message('Error', 'You must select a host demo to upload.');
124 return 137 return;
125 } else if (uploadRunContent.partner_demo === null) { 138 } else if (uploadRunContent.partner_demo === null) {
126 await message("Error", "You must select a partner demo to upload.") 139 await message('Error', 'You must select a partner demo to upload.');
127 return 140 return;
128 } 141 }
129 } else { 142 } else {
130 if (uploadRunContent.host_demo === null) { 143 if (uploadRunContent.host_demo === null) {
131 await message("Error", "You must select a demo to upload.") 144 await message('Error', 'You must select a demo to upload.');
132 return 145 return;
133 } 146 }
134 } 147 }
135 const demo = SourceDemoParser.default() 148 const demo = SourceDemoParser.default()
136 .setOptions({ packets: true, header: true }) 149 .setOptions({ packets: true, header: true })
137 .parse(await uploadRunContent.host_demo.arrayBuffer()); 150 .parse(await uploadRunContent.host_demo.arrayBuffer());
138 const scoreboard = demo.findPacket<NetMessages.SvcUserMessage>((msg) => { 151 const scoreboard = demo.findPacket<NetMessages.SvcUserMessage>(msg => {
139 return msg instanceof NetMessages.SvcUserMessage && msg.userMessage instanceof ScoreboardTempUpdate; 152 return (
140 }) 153 msg instanceof NetMessages.SvcUserMessage &&
154 msg.userMessage instanceof ScoreboardTempUpdate
155 );
156 });
141 157
142 if (!scoreboard) { 158 if (!scoreboard) {
143 await message("Error", "Error while processing demo: Unable to get scoreboard result. Either there is a demo that is corrupt or haven't been recorded in challenge mode.") 159 await message(
144 return 160 'Error',
161 "Error while processing demo: Unable to get scoreboard result. Either there is a demo that is corrupt or haven't been recorded in challenge mode."
162 );
163 return;
145 } 164 }
146 165
147 if (!demo.mapName || !MapNames[demo.mapName]) { 166 if (!demo.mapName || !MapNames[demo.mapName]) {
148 await message("Error", "Error while processing demo: Invalid map name.") 167 await message(
149 return 168 'Error',
169 'Error while processing demo: Invalid map name.'
170 );
171 return;
150 } 172 }
151 173
152 if (selectedGameID === 0 && MapNames[demo.mapName] > 60) { 174 if (selectedGameID === 0 && MapNames[demo.mapName] > 60) {
153 await message("Error", "Error while processing demo: Invalid cooperative demo in singleplayer submission.") 175 await message(
154 return 176 'Error',
177 'Error while processing demo: Invalid cooperative demo in singleplayer submission.'
178 );
179 return;
155 } else if (selectedGameID === 1 && MapNames[demo.mapName] <= 60) { 180 } else if (selectedGameID === 1 && MapNames[demo.mapName] <= 60) {
156 await message("Error", "Error while processing demo: Invalid singleplayer demo in cooperative submission.") 181 await message(
157 return 182 'Error',
183 'Error while processing demo: Invalid singleplayer demo in cooperative submission.'
184 );
185 return;
158 } 186 }
159 187
160 const { portalScore, timeScore } = scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {}; 188 const { portalScore, timeScore } =
189 scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {};
161 190
162 const userConfirmed = await confirm("Upload Record", `Map Name: ${demo.mapName}\nPortal Count: ${portalScore}\nTicks: ${timeScore}\n\nAre you sure you want to upload this demo?`); 191 const userConfirmed = await confirm(
192 'Upload Record',
193 `Map Name: ${demo.mapName}\nPortal Count: ${portalScore}\nTicks: ${timeScore}\n\nAre you sure you want to upload this demo?`
194 );
163 195
164 if (!userConfirmed) { 196 if (!userConfirmed) {
165 return; 197 return;
166 } 198 }
167 199
168 messageLoad("Uploading..."); 200 messageLoad('Uploading...');
169 const [success, response] = await API.post_record(token, uploadRunContent, MapNames[demo.mapName]); 201 const [success, response] = await API.post_record(
202 token,
203 uploadRunContent,
204 MapNames[demo.mapName]
205 );
170 messageLoadClose(); 206 messageLoadClose();
171 await message("Upload Record", response); 207 await message('Upload Record', response);
172 if (success) { 208 if (success) {
173 setUploadRunContent({ 209 setUploadRunContent({
174 host_demo: null, 210 host_demo: null,
175 partner_demo: null, 211 partner_demo: null,
176 }); 212 });
177 onClose(success); 213 onClose(success);
178 navigate("/profile"); 214 navigate('/profile');
179 } 215 }
180 } 216 }
181 }; 217 };
@@ -184,7 +220,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
184 if (open) { 220 if (open) {
185 setDragHighlightPartner(false); 221 setDragHighlightPartner(false);
186 setDragHighlight(false); 222 setDragHighlight(false);
187 _handle_game_select("1", "Portal 2 - Singleplayer"); // a different approach?. 223 _handle_game_select('1', 'Portal 2 - Singleplayer'); // a different approach?.
188 } 224 }
189 }, [open]); 225 }, [open]);
190 226
@@ -196,84 +232,191 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
196 {MessageDialogLoadComponent} 232 {MessageDialogLoadComponent}
197 {ConfirmDialogComponent} 233 {ConfirmDialogComponent}
198 234
199 <div id='upload-run-menu'> 235 <div id="upload-run-menu">
200 <div id='upload-run-menu-add'> 236 <div id="upload-run-menu-add">
201 <div id='upload-run-route-category'> 237 <div id="upload-run-route-category">
202 <div style={{ padding: "15px 0px" }} className='upload-run-dropdown-container upload-run-item'> 238 <div
203 <h3 style={{ margin: "0px 0px" }}>Select Game</h3> 239 style={{ padding: '15px 0px' }}
204 <div onClick={() => _handle_dropdowns(1)} style={{ display: "flex", alignItems: "center", cursor: "pointer", justifyContent: "space-between", margin: "10px 0px" }}> 240 className="upload-run-dropdown-container upload-run-item"
205 <div className='dropdown-cur'>{selectedGameName}</div> 241 >
206 <i style={{ rotate: "-90deg", transform: "translate(-5px, 10px)" }} className="triangle"></i> 242 <h3 style={{ margin: '0px 0px' }}>Select Game</h3>
243 <div
244 onClick={() => _handle_dropdowns(1)}
245 style={{
246 display: 'flex',
247 alignItems: 'center',
248 cursor: 'pointer',
249 justifyContent: 'space-between',
250 margin: '10px 0px',
251 }}
252 >
253 <div className="dropdown-cur">{selectedGameName}</div>
254 <i
255 style={{
256 rotate: '-90deg',
257 transform: 'translate(-5px, 10px)',
258 }}
259 className="triangle"
260 ></i>
207 </div> 261 </div>
208 <div style={{ top: "110px" }} className={dropdown1Vis ? "upload-run-dropdown" : "upload-run-dropdown hidden"}> 262 <div
209 {games.map((game) => ( 263 style={{ top: '110px' }}
210 <div onClick={() => { _handle_game_select(game.id.toString(), game.name); _handle_dropdowns(1) }} key={game.id}>{game.name}</div> 264 className={
265 dropdown1Vis
266 ? 'upload-run-dropdown'
267 : 'upload-run-dropdown hidden'
268 }
269 >
270 {games.map(game => (
271 <div
272 onClick={() => {
273 _handle_game_select(game.id.toString(), game.name);
274 _handle_dropdowns(1);
275 }}
276 key={game.id}
277 >
278 {game.name}
279 </div>
211 ))} 280 ))}
212 </div> 281 </div>
213 </div> 282 </div>
214 283
215 { 284 {!loading && (
216 !loading && 285 <>
217 ( 286 <div>
218 <> 287 <h3 style={{ margin: '10px 0px' }}>Host Demo</h3>
219 288 <div
220 <div> 289 onClick={() => {
221 <h3 style={{ margin: "10px 0px" }}>Host Demo</h3> 290 _handle_file_click(true);
222 <div onClick={() => { _handle_file_click(true) }} onDragOver={(e) => { _handle_drag_over(e, true) }} onDrop={(e) => { _handle_drop(e, true) }} onDragLeave={(e) => { _handle_drag_leave(e, true) }} className={`upload-run-drag-area ${dragHightlight ? "upload-run-drag-area-highlight" : ""} ${uploadRunContent.host_demo ? "upload-run-drag-area-hidden" : ""}`}> 291 }}
223 <input ref={fileInputRef} type="file" name="host_demo" id="host_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, true)} /> 292 onDragOver={e => {
224 {!uploadRunContent.host_demo ? 293 _handle_drag_over(e, true);
294 }}
295 onDrop={e => {
296 _handle_drop(e, true);
297 }}
298 onDragLeave={e => {
299 _handle_drag_leave(e, true);
300 }}
301 className={`upload-run-drag-area ${dragHightlight ? 'upload-run-drag-area-highlight' : ''} ${uploadRunContent.host_demo ? 'upload-run-drag-area-hidden' : ''}`}
302 >
303 <input
304 ref={fileInputRef}
305 type="file"
306 name="host_demo"
307 id="host_demo"
308 accept=".dem"
309 onChange={e =>
310 _handle_file_change(e.target.files, true)
311 }
312 />
313 {!uploadRunContent.host_demo ? (
314 <div>
315 <span>Drag and drop</span>
225 <div> 316 <div>
226 <span>Drag and drop</span> 317 <span
227 <div> 318 style={{
228 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br /> 319 fontFamily: 'BarlowSemiCondensed-Regular',
229 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button> 320 }}
230 </div> 321 >
322 Or click here
323 </span>
324 <br />
325 <button
326 style={{
327 borderRadius: '24px',
328 padding: '5px 8px',
329 margin: '5px 0px',
330 }}
331 >
332 Upload
333 </button>
231 </div> 334 </div>
232 : null} 335 </div>
233 336 ) : null}
234 <span className="upload-run-demo-name">{uploadRunContent.host_demo?.name}</span>
235 </div>
236 {
237 games[selectedGameID].is_coop &&
238 (
239 <>
240 <div>
241 <h3 style={{ margin: "10px 0px" }}>Partner Demo</h3>
242 <div onClick={() => { _handle_file_click(false) }} onDragOver={(e) => { _handle_drag_over(e, false) }} onDrop={(e) => { _handle_drop(e, false) }} onDragLeave={(e) => { _handle_drag_leave(e, false) }} className={`upload-run-drag-area ${dragHightlightPartner ? "upload-run-drag-area-highlight-partner" : ""} ${uploadRunContent.partner_demo ? "upload-run-drag-area-hidden" : ""}`}>
243 <input ref={fileInputRefPartner} type="file" name="partner_demo" id="partner_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, false)} /> {!uploadRunContent.partner_demo ?
244 <div>
245 <span>Drag and drop</span>
246 <div>
247 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
248 <button style={{ borderRadius: "24px", padding: "5px 8px", margin: "5px 0px" }}>Upload</button>
249 </div>
250 </div>
251 : null}
252
253 <span className="upload-run-demo-name">{uploadRunContent.partner_demo?.name}</span>
254 </div>
255 </div>
256 </>
257 )
258 }
259 </div>
260 <div className='search-container'>
261 337
338 <span className="upload-run-demo-name">
339 {uploadRunContent.host_demo?.name}
340 </span>
262 </div> 341 </div>
263 342 {games[selectedGameID].is_coop && (
264 </> 343 <>
265 ) 344 <div>
266 } 345 <h3 style={{ margin: '10px 0px' }}>Partner Demo</h3>
346 <div
347 onClick={() => {
348 _handle_file_click(false);
349 }}
350 onDragOver={e => {
351 _handle_drag_over(e, false);
352 }}
353 onDrop={e => {
354 _handle_drop(e, false);
355 }}
356 onDragLeave={e => {
357 _handle_drag_leave(e, false);
358 }}
359 className={`upload-run-drag-area ${dragHightlightPartner ? 'upload-run-drag-area-highlight-partner' : ''} ${uploadRunContent.partner_demo ? 'upload-run-drag-area-hidden' : ''}`}
360 >
361 <input
362 ref={fileInputRefPartner}
363 type="file"
364 name="partner_demo"
365 id="partner_demo"
366 accept=".dem"
367 onChange={e =>
368 _handle_file_change(e.target.files, false)
369 }
370 />{' '}
371 {!uploadRunContent.partner_demo ? (
372 <div>
373 <span>Drag and drop</span>
374 <div>
375 <span
376 style={{
377 fontFamily: 'BarlowSemiCondensed-Regular',
378 }}
379 >
380 Or click here
381 </span>
382 <br />
383 <button
384 style={{
385 borderRadius: '24px',
386 padding: '5px 8px',
387 margin: '5px 0px',
388 }}
389 >
390 Upload
391 </button>
392 </div>
393 </div>
394 ) : null}
395 <span className="upload-run-demo-name">
396 {uploadRunContent.partner_demo?.name}
397 </span>
398 </div>
399 </div>
400 </>
401 )}
402 </div>
403 <div className="search-container"></div>
404 </>
405 )}
267 </div> 406 </div>
268 <div className='upload-run-buttons-container'> 407 <div className="upload-run-buttons-container">
269 <button onClick={_upload_run}>Submit</button> 408 <button onClick={_upload_run}>Submit</button>
270 <button onClick={() => { 409 <button
271 onClose(false); 410 onClick={() => {
272 setUploadRunContent({ 411 onClose(false);
273 host_demo: null, 412 setUploadRunContent({
274 partner_demo: null, 413 host_demo: null,
275 }); 414 partner_demo: null,
276 }}>Cancel</button> 415 });
416 }}
417 >
418 Cancel
419 </button>
277 </div> 420 </div>
278 </div> 421 </div>
279 </div> 422 </div>
@@ -281,10 +424,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
281 ); 424 );
282 } 425 }
283 426
284 return ( 427 return <></>;
285 <></>
286 );
287
288}; 428};
289 429
290export default UploadRunDialog; 430export default UploadRunDialog;