aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2025-07-24 14:40:22 +0300
committerArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2025-07-24 14:40:22 +0300
commitb0d199936b546c75d4b19d99591237f0bf97fe55 (patch)
treee9391880e7db2bd1ea8ff25d91aeea8dd98f186e /frontend/src/components
parentfix/frontend: fixed sidebar title size, removed unnecessary imports (diff)
parentfeat/backend: add newrelic integration (#274) (diff)
downloadlphub-b0d199936b546c75d4b19d99591237f0bf97fe55.tar.gz
lphub-b0d199936b546c75d4b19d99591237f0bf97fe55.tar.bz2
lphub-b0d199936b546c75d4b19d99591237f0bf97fe55.zip
Merge branch 'main' into css-overhaulcss-overhaul
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/Leaderboards.tsx21
-rw-r--r--frontend/src/components/Summary.tsx36
-rw-r--r--frontend/src/components/UploadRunDialog.tsx106
3 files changed, 89 insertions, 74 deletions
diff --git a/frontend/src/components/Leaderboards.tsx b/frontend/src/components/Leaderboards.tsx
index 4a8b463..fb614fa 100644
--- a/frontend/src/components/Leaderboards.tsx
+++ b/frontend/src/components/Leaderboards.tsx
@@ -1,20 +1,33 @@
1import React from 'react'; 1import React from 'react';
2import { Link } 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 useMessage from "@hooks/UseMessage"; 8import useMessage from "@hooks/UseMessage";
8import "@css/Maps.css" 9import "@css/Maps.css"
9 10
10interface LeaderboardsProps { 11interface LeaderboardsProps {
11 data?: MapLeaderboard; 12 mapID: string;
12} 13}
13 14
14const Leaderboards: React.FC<LeaderboardsProps> = ({ data }) => { 15const Leaderboards: React.FC<LeaderboardsProps> = ({ mapID }) => {
16 const navigate = useNavigate();
17 const [data, setData] = React.useState<MapLeaderboard | undefined>(undefined);
18 const [pageNumber, setPageNumber] = React.useState<number>(1);
19
20 const _fetch_map_leaderboards = async () => {
21 const mapLeaderboards = await API.get_map_leaderboard(mapID, pageNumber.toString());
22 setData(mapLeaderboards);
23 };
15 24
16 const { message, MessageDialogComponent } = useMessage(); 25 const { message, MessageDialogComponent } = useMessage();
17 const [pageNumber, setPageNumber] = React.useState<number>(1); 26
27 React.useEffect(() => {
28 _fetch_map_leaderboards();
29 console.log(data);
30 }, [pageNumber, navigate])
18 31
19 if (!data) { 32 if (!data) {
20 return ( 33 return (
diff --git a/frontend/src/components/Summary.tsx b/frontend/src/components/Summary.tsx
index 4bcaa6a..7da2f1e 100644
--- a/frontend/src/components/Summary.tsx
+++ b/frontend/src/components/Summary.tsx
@@ -140,20 +140,34 @@ const Summary: React.FC<SummaryProps> = ({ selectedRun, setSelectedRun, data })
140 <section id='section4' className='summary1'> 140 <section id='section4' className='summary1'>
141 <div id='difficulty'> 141 <div id='difficulty'>
142 <span>Difficulty</span> 142 <span>Difficulty</span>
143 {data.summary.routes[selectedRun].rating === 0 && (<span>N/A</span>)} 143 {data.map.difficulty <= 2 && (<span style={{ color: "lime" }}>Very easy</span>)}
144 {data.summary.routes[selectedRun].rating === 1 && (<span style={{ color: "lime" }}>Very easy</span>)} 144 {data.map.difficulty > 2 && data.map.difficulty <= 4 && (<span style={{ color: "green" }}>Easy</span>)}
145 {data.summary.routes[selectedRun].rating === 2 && (<span style={{ color: "green" }}>Easy</span>)} 145 {data.map.difficulty > 4 && data.map.difficulty <= 6 && (<span style={{ color: "yellow" }}>Medium</span>)}
146 {data.summary.routes[selectedRun].rating === 3 && (<span style={{ color: "yellow" }}>Medium</span>)} 146 {data.map.difficulty > 6 && data.map.difficulty <= 8 && (<span style={{ color: "orange" }}>Hard</span>)}
147 {data.summary.routes[selectedRun].rating === 4 && (<span style={{ color: "orange" }}>Hard</span>)} 147 {data.map.difficulty > 8 && data.map.difficulty <= 10 && (<span style={{ color: "red" }}>Very hard</span>)}
148 {data.summary.routes[selectedRun].rating === 5 && (<span style={{ color: "red" }}>Very hard</span>)}
149 <div> 148 <div>
150 {data.summary.routes[selectedRun].rating === 1 ? (<div className='difficulty-rating' style={{ backgroundColor: "lime" }}></div>) : (<div className='difficulty-rating'></div>)} 149 {data.map.difficulty <= 2 ? (<div className='difficulty-rating' style={{ backgroundColor: "lime" }}></div>) : (<div className='difficulty-rating'></div>)}
151 {data.summary.routes[selectedRun].rating === 2 ? (<div className='difficulty-rating' style={{ backgroundColor: "green" }}></div>) : (<div className='difficulty-rating'></div>)} 150 {data.map.difficulty > 2 && data.map.difficulty <= 4 ? (<div className='difficulty-rating' style={{ backgroundColor: "green" }}></div>) : (<div className='difficulty-rating'></div>)}
152 {data.summary.routes[selectedRun].rating === 3 ? (<div className='difficulty-rating' style={{ backgroundColor: "yellow" }}></div>) : (<div className='difficulty-rating'></div>)} 151 {data.map.difficulty > 4 && data.map.difficulty <= 6 ? (<div className='difficulty-rating' style={{ backgroundColor: "yellow" }}></div>) : (<div className='difficulty-rating'></div>)}
153 {data.summary.routes[selectedRun].rating === 4 ? (<div className='difficulty-rating' style={{ backgroundColor: "orange" }}></div>) : (<div className='difficulty-rating'></div>)} 152 {data.map.difficulty > 6 && data.map.difficulty <= 8 ? (<div className='difficulty-rating' style={{ backgroundColor: "orange" }}></div>) : (<div className='difficulty-rating'></div>)}
154 {data.summary.routes[selectedRun].rating === 5 ? (<div className='difficulty-rating' style={{ backgroundColor: "red" }}></div>) : (<div className='difficulty-rating'></div>)} 153 {data.map.difficulty > 8 && data.map.difficulty <= 10 ? (<div className='difficulty-rating' style={{ backgroundColor: "red" }}></div>) : (<div className='difficulty-rating'></div>)}
155 </div> 154 </div>
156 </div> 155 </div>
156 {/* <div id='difficulty'>
157 <span>Difficulty</span>
158 {data.summary.routes[selectedRun].rating <= 2 && (<span style={{ color: "lime" }}>Very easy</span>)}
159 {data.summary.routes[selectedRun].rating > 2 && data.summary.routes[selectedRun].rating <= 4 && (<span style={{ color: "green" }}>Easy</span>)}
160 {data.summary.routes[selectedRun].rating > 4 && data.summary.routes[selectedRun].rating <= 6 && (<span style={{ color: "yellow" }}>Medium</span>)}
161 {data.summary.routes[selectedRun].rating > 6 && data.summary.routes[selectedRun].rating <= 8 && (<span style={{ color: "orange" }}>Hard</span>)}
162 {data.summary.routes[selectedRun].rating > 8 && data.summary.routes[selectedRun].rating <= 10 && (<span style={{ color: "red" }}>Very hard</span>)}
163 <div>
164 {data.summary.routes[selectedRun].rating <= 2 ? (<div className='difficulty-rating' style={{ backgroundColor: "lime" }}></div>) : (<div className='difficulty-rating'></div>)}
165 {data.summary.routes[selectedRun].rating > 2 && data.summary.routes[selectedRun].rating <= 4 ? (<div className='difficulty-rating' style={{ backgroundColor: "green" }}></div>) : (<div className='difficulty-rating'></div>)}
166 {data.summary.routes[selectedRun].rating > 4 && data.summary.routes[selectedRun].rating <= 6 ? (<div className='difficulty-rating' style={{ backgroundColor: "yellow" }}></div>) : (<div className='difficulty-rating'></div>)}
167 {data.summary.routes[selectedRun].rating > 6 && data.summary.routes[selectedRun].rating <= 8 ? (<div className='difficulty-rating' style={{ backgroundColor: "orange" }}></div>) : (<div className='difficulty-rating'></div>)}
168 {data.summary.routes[selectedRun].rating > 8 && data.summary.routes[selectedRun].rating <= 10 ? (<div className='difficulty-rating' style={{ backgroundColor: "red" }}></div>) : (<div className='difficulty-rating'></div>)}
169 </div>
170 </div> */}
157 <div id='count'> 171 <div id='count'>
158 <span>Completion Count</span> 172 <span>Completion Count</span>
159 <div>{data.summary.routes[selectedRun].completion_count}</div> 173 <div>{data.summary.routes[selectedRun].completion_count}</div>
diff --git a/frontend/src/components/UploadRunDialog.tsx b/frontend/src/components/UploadRunDialog.tsx
index 951944b..971a747 100644
--- a/frontend/src/components/UploadRunDialog.tsx
+++ b/frontend/src/components/UploadRunDialog.tsx
@@ -5,12 +5,12 @@ import { ScoreboardTempUpdate, SourceDemoParser, NetMessages } from '@nekz/sdp';
5import btn from "@css/Button.module.css"; 5import btn from "@css/Button.module.css";
6import '@css/UploadRunDialog.css'; 6import '@css/UploadRunDialog.css';
7import { Game } from '@customTypes/Game'; 7import { Game } from '@customTypes/Game';
8import { Map } from '@customTypes/Map';
9import { API } from '@api/Api'; 8import { API } from '@api/Api';
10import { useNavigate } from 'react-router-dom'; 9import { useNavigate } from 'react-router-dom';
11import useMessage from '@hooks/UseMessage'; 10import useMessage from '@hooks/UseMessage';
12import useConfirm from '@hooks/UseConfirm'; 11import useConfirm from '@hooks/UseConfirm';
13import useMessageLoad from "@hooks/UseMessageLoad"; 12import useMessageLoad from "@hooks/UseMessageLoad";
13import { MapNames } from '@customTypes/MapNames';
14 14
15interface UploadRunDialogProps { 15interface UploadRunDialogProps {
16 token?: string; 16 token?: string;
@@ -28,19 +28,11 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
28 const navigate = useNavigate(); 28 const navigate = useNavigate();
29 29
30 const [uploadRunContent, setUploadRunContent] = React.useState<UploadRunContent>({ 30 const [uploadRunContent, setUploadRunContent] = React.useState<UploadRunContent>({
31 map_id: 0,
32 host_demo: null, 31 host_demo: null,
33 partner_demo: null, 32 partner_demo: null,
34 }); 33 });
35 34
36 const [currentMap, setCurrentMap] = React.useState<string>("");
37
38 const _set_current_map = (game_name: string) => {
39 setCurrentMap(game_name);
40 }
41
42 const [selectedGameID, setSelectedGameID] = React.useState<number>(0); 35 const [selectedGameID, setSelectedGameID] = React.useState<number>(0);
43 const [selectedGameMaps, setSelectedGameMaps] = React.useState<Map[]>([]);
44 const [selectedGameName, setSelectedGameName] = React.useState<string>(""); 36 const [selectedGameName, setSelectedGameName] = React.useState<string>("");
45 37
46 // dropdowns 38 // dropdowns
@@ -51,6 +43,7 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
51 43
52 const [dragHightlight, setDragHighlight] = React.useState<boolean>(false); 44 const [dragHightlight, setDragHighlight] = React.useState<boolean>(false);
53 const [dragHightlightPartner, setDragHighlightPartner] = React.useState<boolean>(false); 45 const [dragHightlightPartner, setDragHighlightPartner] = React.useState<boolean>(false);
46
54 const fileInputRef = React.useRef<HTMLInputElement>(null); 47 const fileInputRef = React.useRef<HTMLInputElement>(null);
55 const fileInputRefPartner = React.useRef<HTMLInputElement>(null); 48 const fileInputRefPartner = React.useRef<HTMLInputElement>(null);
56 49
@@ -103,14 +96,6 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
103 96
104 const _handle_game_select = async (game_id: string, game_name: string) => { 97 const _handle_game_select = async (game_id: string, game_name: string) => {
105 setLoading(true); 98 setLoading(true);
106 const gameMaps = await API.get_game_maps(game_id);
107 setSelectedGameMaps(gameMaps);
108 setUploadRunContent({
109 map_id: gameMaps.find((map) => !map.is_disabled)!.id, //gameMaps[0].id,
110 host_demo: null,
111 partner_demo: null,
112 });
113 _set_current_map(gameMaps.find((map) => !map.is_disabled)!.name);
114 setSelectedGameID(parseInt(game_id) - 1); 99 setSelectedGameID(parseInt(game_id) - 1);
115 setSelectedGameName(game_name); 100 setSelectedGameName(game_name);
116 setLoading(false); 101 setLoading(false);
@@ -159,6 +144,20 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
159 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.") 144 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.")
160 return 145 return
161 } 146 }
147
148 if (!demo.mapName || !MapNames[demo.mapName]) {
149 await message("Error", "Error while processing demo: Invalid map name.")
150 return
151 }
152
153 if (selectedGameID === 0 && MapNames[demo.mapName] > 60) {
154 await message("Error", "Error while processing demo: Invalid cooperative demo in singleplayer submission.")
155 return
156 } else if (selectedGameID === 1 && MapNames[demo.mapName] <= 60) {
157 await message("Error", "Error while processing demo: Invalid singleplayer demo in cooperative submission.")
158 return
159 }
160
162 const { portalScore, timeScore } = scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {}; 161 const { portalScore, timeScore } = scoreboard.userMessage?.as<ScoreboardTempUpdate>() ?? {};
163 162
164 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?`); 163 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?`);
@@ -168,10 +167,14 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
168 } 167 }
169 168
170 messageLoad("Uploading..."); 169 messageLoad("Uploading...");
171 const [success, response] = await API.post_record(token, uploadRunContent); 170 const [success, response] = await API.post_record(token, uploadRunContent, MapNames[demo.mapName]);
172 messageLoadClose(); 171 messageLoadClose();
173 await message("Upload Record", response); 172 await message("Upload Record", response);
174 if (success) { 173 if (success) {
174 setUploadRunContent({
175 host_demo: null,
176 partner_demo: null,
177 });
175 onClose(success); 178 onClose(success);
176 navigate("/profile"); 179 navigate("/profile");
177 } 180 }
@@ -180,7 +183,6 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
180 183
181 React.useEffect(() => { 184 React.useEffect(() => {
182 if (open) { 185 if (open) {
183
184 setDragHighlightPartner(false); 186 setDragHighlightPartner(false);
185 setDragHighlight(false); 187 setDragHighlight(false);
186 _handle_game_select("1", "Portal 2 - Singleplayer"); // a different approach?. 188 _handle_game_select("1", "Portal 2 - Singleplayer"); // a different approach?.
@@ -204,37 +206,20 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
204 <div className='dropdown-cur'>{selectedGameName}</div> 206 <div className='dropdown-cur'>{selectedGameName}</div>
205 <i style={{ rotate: "-90deg", transform: "translate(-5px, 10px)" }} className="triangle"></i> 207 <i style={{ rotate: "-90deg", transform: "translate(-5px, 10px)" }} className="triangle"></i>
206 </div> 208 </div>
207 <div style={{top: "110px"}} className={dropdown1Vis ? "upload-run-dropdown" : "upload-run-dropdown hidden"}> 209 <div style={{ top: "110px" }} className={dropdown1Vis ? "upload-run-dropdown" : "upload-run-dropdown hidden"}>
208 {games.map((game) => ( 210 {games.map((game) => (
209 <div onClick={() => { _handle_game_select(game.id.toString(), game.name); _handle_dropdowns(1) }} key={game.id}>{game.name}</div> 211 <div onClick={() => { _handle_game_select(game.id.toString(), game.name); _handle_dropdowns(1) }} key={game.id}>{game.name}</div>
210 ))} 212 ))}
211 </div> 213 </div>
212 {!loading && ( 214 </div>
213 <>
214 <div style={{ padding: "25px 0px" }}>
215 <h3 style={{ margin: "0px 0px" }}>Select Map</h3>
216 <div onClick={() => _handle_dropdowns(2)} style={{ display: "flex", alignItems: "center", cursor: "pointer", justifyContent: "space-between", margin: "10px 0px" }}>
217 <span style={{ userSelect: "none" }}>{currentMap}</span>
218 <i style={{ rotate: "-90deg", transform: "translate(-5px, 10px)" }} className="triangle"></i>
219 </div>
220 </div>
221 <div style={{top: "220px"}} id='dropdown2' className={dropdown2Vis ? "upload-run-dropdown" : "upload-run-dropdown hidden"}>
222 {selectedGameMaps && selectedGameMaps.filter(gameMap => !gameMap.is_disabled).map((gameMap) => (
223 <div onClick={() => { setUploadRunContent({ ...uploadRunContent, map_id: gameMap.id }); _set_current_map(gameMap.name); _handle_dropdowns(2); }} key={gameMap.id}>{gameMap.name}</div>
224 ))}
225 </div>
226 </>
227
228 )}
229 </div>
230 215
231 { 216 {
232 !loading && 217 !loading &&
233 ( 218 (
234 <> 219 <>
235 220
236 <div> 221 <div>
237 <h3 style={{margin: "10px 0px"}}>Host Demo</h3> 222 <h3 style={{ margin: "10px 0px" }}>Host Demo</h3>
238 <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" : ""}`}> 223 <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" : ""}`}>
239 <input ref={fileInputRef} type="file" name="host_demo" id="host_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, true)} /> 224 <input ref={fileInputRef} type="file" name="host_demo" id="host_demo" accept=".dem" onChange={(e) => _handle_file_change(e.target.files, true)} />
240 {!uploadRunContent.host_demo ? 225 {!uploadRunContent.host_demo ?
@@ -253,38 +238,41 @@ const UploadRunDialog: React.FC<UploadRunDialogProps> = ({ token, open, onClose,
253 games[selectedGameID].is_coop && 238 games[selectedGameID].is_coop &&
254 ( 239 (
255 <> 240 <>
256 <div> 241 <div>
257 <h3 style={{margin: "10px 0px"}}>Partner Demo</h3> 242 <h3 style={{ margin: "10px 0px" }}>Partner Demo</h3>
258 <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 <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" : ""}`}>
259 <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 <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 ?
260 <div>
261 <span>Drag and drop</span>
262 <div> 245 <div>
263 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br /> 246 <span style={{ fontFamily: "BarlowSemiCondensed-Regular" }}>Or click here</span><br />
264 <button className={btn.default}>Upload</button> 247 <button className={btn.default}>Upload</button>
265 </div> 248 </div>
266 </div> 249 : null}
267 : null}
268 250
269 <span className="upload-run-demo-name">{uploadRunContent.partner_demo?.name}</span> 251 <span className="upload-run-demo-name">{uploadRunContent.partner_demo?.name}</span>
252 </div>
270 </div> 253 </div>
271 </div>
272 </> 254 </>
273 ) 255 )
274 } 256 }
275 </div> 257 </div>
276 <div className='search-container'> 258 <div className='search-container'>
259
260 </div>
277 261
278 </div>
279
280 </> 262 </>
281 ) 263 )
282 } 264 }
283 </div> 265 </div>
284 <div className='upload-run-buttons-container'> 266 <div className='upload-run-buttons-container'>
285 <button className={`${btn.defaultWide}`} onClick={_upload_run}>Submit</button> 267 <button className={`${btn.defaultWide}`} onClick={_upload_run}>Submit</button>
286 <button className={`${btn.defaultWide}`} onClick={() => onClose(false)}>Cancel</button> 268 <button className={`${btn.defaultWide}`} onClick={() => {
287 </div> 269 onClose(false);
270 setUploadRunContent({
271 host_demo: null,
272 partner_demo: null,
273 });
274 }}>Cancel</button>
275 </div>
288 </div> 276 </div>
289 </div> 277 </div>
290 </> 278 </>