aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/ModMenu.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/ModMenu.tsx')
-rw-r--r--frontend/src/components/ModMenu.tsx461
1 files changed, 309 insertions, 152 deletions
diff --git a/frontend/src/components/ModMenu.tsx b/frontend/src/components/ModMenu.tsx
index 925b8a8..a0d7eb7 100644
--- a/frontend/src/components/ModMenu.tsx
+++ b/frontend/src/components/ModMenu.tsx
@@ -1,12 +1,11 @@
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 useConfirm from "@hooks/UseConfirm";
9import useConfirm from '@hooks/UseConfirm';
10 9
11interface ModMenuProps { 10interface ModMenuProps {
12 token?: string; 11 token?: string;
@@ -15,8 +14,12 @@ interface ModMenuProps {
15 mapID: string; 14 mapID: string;
16} 15}
17 16
18const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) => { 17const ModMenu: React.FC<ModMenuProps> = ({
19 18 token,
19 data,
20 selectedRun,
21 mapID,
22}) => {
20 const { confirm, ConfirmDialogComponent } = useConfirm(); 23 const { confirm, ConfirmDialogComponent } = useConfirm();
21 24
22 const [menu, setMenu] = React.useState<number>(0); 25 const [menu, setMenu] = React.useState<number>(0);
@@ -55,74 +58,99 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
55 width *= 320 / height; 58 width *= 320 / height;
56 height = 320; 59 height = 320;
57 } 60 }
58 const canvas = document.createElement('canvas'); 61 const canvas = document.createElement("canvas");
59 canvas.width = width; 62 canvas.width = width;
60 canvas.height = height; 63 canvas.height = height;
61 canvas.getContext('2d')!.drawImage(img, 0, 0, width, height); 64 canvas.getContext("2d")!.drawImage(img, 0, 0, width, height);
62 resolve(canvas.toDataURL(file.type, 0.6)); 65 resolve(canvas.toDataURL(file.type, 0.6));
63 }; 66 };
64 } 67 }
65 }; 68 };
66 }); 69 });
67 }; 70 }
68 71
69 const _edit_map_summary_image = async () => { 72 const _edit_map_summary_image = async () => {
70 if (await confirm("Edit Map Summary Image", "Are you sure you want to submit this to the database?")) { 73 if (
74 await confirm(
75 "Edit Map Summary Image",
76 "Are you sure you want to submit this to the database?"
77 )
78 ) {
71 if (token) { 79 if (token) {
72 const success = await API.put_map_image(token, mapID, image); 80 const success = await API.put_map_image(token, mapID, image);
73 if (success) { 81 if (success) {
74 navigate(0); 82 navigate(0);
75 } else { 83 } else {
76 alert("Error. Check logs.") 84 alert("Error. Check logs.");
77 } 85 }
78 } 86 }
79 } 87 }
80 }; 88 };
81 89
82 const _edit_map_summary_route = async () => { 90 const _edit_map_summary_route = async () => {
83 if (await confirm("Edit Map Summary Route", "Are you sure you want to submit this to the database?")) { 91 if (
92 await confirm(
93 "Edit Map Summary Route",
94 "Are you sure you want to submit this to the database?"
95 )
96 ) {
84 if (token) { 97 if (token) {
85 routeContent.date += "T00:00:00Z"; 98 routeContent.date += "T00:00:00Z";
86 const success = await API.put_map_summary(token, mapID, routeContent); 99 const success = await API.put_map_summary(token, mapID, routeContent);
87 if (success) { 100 if (success) {
88 navigate(0); 101 navigate(0);
89 } else { 102 } else {
90 alert("Error. Check logs.") 103 alert("Error. Check logs.");
91 } 104 }
92 } 105 }
93 } 106 }
94 }; 107 };
95 108
96 const _create_map_summary_route = async () => { 109 const _create_map_summary_route = async () => {
97 if (await confirm("Create Map Summary Route", "Are you sure you want to submit this to the database?")) { 110 if (
111 await confirm(
112 "Create Map Summary Route",
113 "Are you sure you want to submit this to the database?"
114 )
115 ) {
98 if (token) { 116 if (token) {
99 routeContent.date += "T00:00:00Z"; 117 routeContent.date += "T00:00:00Z";
100 const success = await API.post_map_summary(token, mapID, routeContent); 118 const success = await API.post_map_summary(token, mapID, routeContent);
101 if (success) { 119 if (success) {
102 navigate(0); 120 navigate(0);
103 } else { 121 } else {
104 alert("Error. Check logs.") 122 alert("Error. Check logs.");
105 } 123 }
106 } 124 }
107 } 125 }
108 }; 126 };
109 127
110 const _delete_map_summary_route = async () => { 128 const _delete_map_summary_route = async () => {
111 if (await confirm("Delete Map Summary Route", `Are you sure you want to submit this to the database?\n 129 if (
112 ${data.summary.routes[selectedRun].category.name}\n${data.summary.routes[selectedRun].history.score_count} portals\n${data.summary.routes[selectedRun].history.runner_name}`)) { 130 await confirm(
131 "Delete Map Summary Route",
132 `Are you sure you want to submit this to the database?\n
133 ${data.summary.routes[selectedRun].category.name}\n${data.summary.routes[selectedRun].history.score_count} portals\n${data.summary.routes[selectedRun].history.runner_name}`
134 )
135 ) {
113 if (token) { 136 if (token) {
114 const success = await API.delete_map_summary(token, mapID, data.summary.routes[selectedRun].route_id); 137 const success = await API.delete_map_summary(
138 token,
139 mapID,
140 data.summary.routes[selectedRun].route_id
141 );
115 if (success) { 142 if (success) {
116 navigate(0); 143 navigate(0);
117 } else { 144 } else {
118 alert("Error. Check logs.") 145 alert("Error. Check logs.");
119 } 146 }
120 } 147 }
121 } 148 }
122 }; 149 };
123 150
124 React.useEffect(() => { 151 React.useEffect(() => {
125 if (menu === 3) { // add route 152 if (menu === 3) {
153 // add route
126 setRouteContent({ 154 setRouteContent({
127 id: 0, 155 id: 0,
128 name: "", 156 name: "",
@@ -134,7 +162,8 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
134 }); 162 });
135 setMd("No description available."); 163 setMd("No description available.");
136 } 164 }
137 if (menu === 2) { // edit route 165 if (menu === 2) {
166 // edit route
138 setRouteContent({ 167 setRouteContent({
139 id: data.summary.routes[selectedRun].route_id, 168 id: data.summary.routes[selectedRun].route_id,
140 name: data.summary.routes[selectedRun].history.runner_name, 169 name: data.summary.routes[selectedRun].history.runner_name,
@@ -146,207 +175,335 @@ const ModMenu: React.FC<ModMenuProps> = ({ token, data, selectedRun, mapID }) =>
146 }); 175 });
147 setMd(data.summary.routes[selectedRun].description); 176 setMd(data.summary.routes[selectedRun].description);
148 } 177 }
149 }, [menu]); 178 }, [menu, data.summary.routes, selectedRun]);
150 179
151 React.useEffect(() => { 180 React.useEffect(() => {
152 const modview = document.querySelector("div#modview") as HTMLElement 181 const modview = document.querySelector("div#modview") as HTMLElement;
153 if (modview) { 182 if (modview) {
154 showButton ? modview.style.transform = "translateY(-68%)" 183 showButton
155 : modview.style.transform = "translateY(0%)" 184 ? (modview.style.transform = "translateY(-68%)")
185 : (modview.style.transform = "translateY(0%)");
156 } 186 }
157 187
158 const modview_block = document.querySelector("#modview_block") as HTMLElement 188 const modview_block = document.querySelector(
189 "#modview_block"
190 ) as HTMLElement;
159 if (modview_block) { 191 if (modview_block) {
160 showButton ? modview_block.style.display = "none" : modview_block.style.display = "block" 192 showButton
193 ? (modview_block.style.display = "none")
194 : (modview_block.style.display = "block");
161 } 195 }
162 }, [showButton]) 196 }, [showButton]);
163 197
164 return ( 198 return (
165 <> 199 <>
166 {ConfirmDialogComponent} 200 {ConfirmDialogComponent}
167 <div id="modview_block" /> 201 <div id="modview_block" />
168 <div id='modview'> 202 <div id="modview">
169 <div> 203 <div>
170 <button onClick={() => setMenu(1)}>Edit Image</button> 204 <button onClick={() => setMenu(1)}>Edit Image</button>
171 <button onClick={() => setMenu(2)}>Edit Selected Route</button> 205 <button onClick={() => setMenu(2)}>Edit Selected Route</button>
172 <button onClick={() => setMenu(3)}>Add New Route</button> 206 <button onClick={() => setMenu(3)}>Add New Route</button>
173 <button onClick={() => _delete_map_summary_route()}>Delete Selected Route</button> 207 <button onClick={() => _delete_map_summary_route()}>
208 Delete Selected Route
209 </button>
174 </div> 210 </div>
175 <div> 211 <div>
176 {showButton ? ( 212 {showButton ? (
177 <button onClick={() => setShowButton(false)}>Show</button> 213 <button onClick={() => setShowButton(false)}>Show</button>
178 ) : ( 214 ) : (
179 <button onClick={() => { setShowButton(true); setMenu(0); }}>Hide</button> 215 <button
216 onClick={() => {
217 setShowButton(true);
218 setMenu(0);
219 }}
220 >
221 Hide
222 </button>
180 )} 223 )}
181 </div> 224 </div>
182 </div><div id='modview-menu'> 225 </div>
183 {// Edit Image 226 <div id="modview-menu">
227 {
228 // Edit Image
184 menu === 1 && ( 229 menu === 1 && (
185 <div id='modview-menu-image'> 230 <div id="modview-menu-image">
186 <div> 231 <div>
187 <span>Current Image:</span> 232 <span>Current Image:</span>
188 <img src={data.map.image} alt="missing" /> 233 <img src={data.map.image} alt="missing" />
189 </div> 234 </div>
190 235
191 <div> 236 <div>
192 <span>New Image: 237 <span>
193 <input type="file" accept='image/*' onChange={e => { 238 New Image:
194 if (e.target.files) { 239 <input
195 compressImage(e.target.files[0]) 240 type="file"
196 .then(d => setImage(d)); 241 accept="image/*"
197 } 242 onChange={e => {
198 }} /></span> 243 if (e.target.files) {
199 {image ? (<button onClick={() => _edit_map_summary_image()}>upload</button>) : <span></span>} 244 compressImage(e.target.files[0]).then(d => setImage(d));
200 <img src={image} alt="" id='modview-menu-image-file' /> 245 }
201 246 }}
247 />
248 </span>
249 {image ? (
250 <button onClick={() => _edit_map_summary_image()}>
251 upload
252 </button>
253 ) : (
254 <span></span>
255 )}
256 <img src={image} alt="" id="modview-menu-image-file" />
202 </div> 257 </div>
203 </div> 258 </div>
204 )} 259 )
260 }
205 261
206 {// Edit Route 262 {
263 // Edit Route
207 menu === 2 && ( 264 menu === 2 && (
208 <div id='modview-menu-edit'> 265 <div id="modview-menu-edit">
209 <div id='modview-route-id'> 266 <div id="modview-route-id">
210 <span>Route ID:</span> 267 <span>Route ID:</span>
211 <input type="number" value={routeContent.id} disabled /> 268 <input type="number" value={routeContent.id} disabled />
212 </div> 269 </div>
213 <div id='modview-route-name'> 270 <div id="modview-route-name">
214 <span>Runner Name:</span> 271 <span>Runner Name:</span>
215 <input type="text" value={routeContent.name} onChange={(e) => { 272 <input
216 setRouteContent({ 273 type="text"
217 ...routeContent, 274 value={routeContent.name}
218 name: e.target.value, 275 onChange={e => {
219 }); 276 setRouteContent({
220 }} /> 277 ...routeContent,
278 name: e.target.value,
279 });
280 }}
281 />
221 </div> 282 </div>
222 <div id='modview-route-score'> 283 <div id="modview-route-score">
223 <span>Score:</span> 284 <span>Score:</span>
224 <input type="number" value={routeContent.score} onChange={(e) => { 285 <input
225 setRouteContent({ 286 type="number"
226 ...routeContent, 287 value={routeContent.score}
227 score: parseInt(e.target.value), 288 onChange={e => {
228 }); 289 setRouteContent({
229 }} /> 290 ...routeContent,
291 score: parseInt(e.target.value),
292 });
293 }}
294 />
230 </div> 295 </div>
231 <div id='modview-route-date'> 296 <div id="modview-route-date">
232 <span>Date:</span> 297 <span>Date:</span>
233 <input type="date" value={routeContent.date} onChange={(e) => { 298 <input
234 setRouteContent({ 299 type="date"
235 ...routeContent, 300 value={routeContent.date}
236 date: e.target.value, 301 onChange={e => {
237 }); 302 setRouteContent({
238 }} /> 303 ...routeContent,
304 date: e.target.value,
305 });
306 }}
307 />
239 </div> 308 </div>
240 <div id='modview-route-showcase'> 309 <div id="modview-route-showcase">
241 <span>Showcase Video:</span> 310 <span>Showcase Video:</span>
242 <input type="text" value={routeContent.showcase} onChange={(e) => { 311 <input
243 setRouteContent({ 312 type="text"
244 ...routeContent, 313 value={routeContent.showcase}
245 showcase: e.target.value, 314 onChange={e => {
246 }); 315 setRouteContent({
247 }} /> 316 ...routeContent,
317 showcase: e.target.value,
318 });
319 }}
320 />
248 </div> 321 </div>
249 <div id='modview-route-description' style={{ height: "180px", gridColumn: "1 / span 5" }}> 322 <div
323 id="modview-route-description"
324 style={{ height: "180px", gridColumn: "1 / span 5" }}
325 >
250 <span>Description:</span> 326 <span>Description:</span>
251 <textarea value={routeContent.description} onChange={(e) => { 327 <textarea
252 setRouteContent({ 328 value={routeContent.description}
253 ...routeContent, 329 onChange={e => {
254 description: e.target.value, 330 setRouteContent({
255 }); 331 ...routeContent,
256 setMd(routeContent.description); 332 description: e.target.value,
257 }} /> 333 });
334 setMd(routeContent.description);
335 }}
336 />
258 </div> 337 </div>
259 <button style={{ gridColumn: "2 / span 3", height: "40px" }} onClick={_edit_map_summary_route}>Apply</button> 338 <button
339 style={{ gridColumn: "2 / span 3", height: "40px" }}
340 onClick={_edit_map_summary_route}
341 >
342 Apply
343 </button>
260 344
261 <div id='modview-md'> 345 <div id="modview-md">
262 <span>Markdown Preview</span> 346 <span>Markdown Preview</span>
263 <span><a href="https://commonmark.org/help/" rel="noreferrer" target='_blank'>Documentation</a></span> 347 <span>
264 <span><a href="https://remarkjs.github.io/react-markdown/" rel="noreferrer" target='_blank'>Demo</a></span> 348 <a
349 href="https://commonmark.org/help/"
350 rel="noreferrer"
351 target="_blank"
352 >
353 Documentation
354 </a>
355 </span>
356 <span>
357 <a
358 href="https://remarkjs.github.io/react-markdown/"
359 rel="noreferrer"
360 target="_blank"
361 >
362 Demo
363 </a>
364 </span>
265 <p> 365 <p>
266 <ReactMarkdown>{md} 366 <ReactMarkdown>{md}</ReactMarkdown>
267 </ReactMarkdown>
268 </p> 367 </p>
269 </div> 368 </div>
270 </div> 369 </div>
271 )} 370 )
371 }
272 372
273 {// Add Route 373 {
374 // Add Route
274 menu === 3 && ( 375 menu === 3 && (
275 <div id='modview-menu-add'> 376 <div id="modview-menu-add">
276 <div id='modview-route-category'> 377 <div id="modview-route-category">
277 <span>Category:</span> 378 <span>Category:</span>
278 <select onChange={(e) => { 379 <select
279 setRouteContent({ 380 onChange={e => {
280 ...routeContent, 381 setRouteContent({
281 category_id: parseInt(e.target.value), 382 ...routeContent,
282 }); 383 category_id: parseInt(e.target.value),
283 }}> 384 });
284 <option value="1" key="1">CM</option> 385 }}
285 <option value="2" key="2">No SLA</option> 386 >
286 {data.map.game_name === "Portal 2 - Cooperative" ? "" : ( 387 <option value="1" key="1">
287 <option value="3" key="3">Inbounds SLA</option>)} 388 CM
288 <option value="4" key="4">Any%</option> 389 </option>
390 <option value="2" key="2">
391 No SLA
392 </option>
393 {data.map.game_name === "Portal 2 - Cooperative" ? (
394 ""
395 ) : (
396 <option value="3" key="3">
397 Inbounds SLA
398 </option>
399 )}
400 <option value="4" key="4">
401 Any%
402 </option>
289 </select> 403 </select>
290 </div> 404 </div>
291 <div id='modview-route-name'> 405 <div id="modview-route-name">
292 <span>Runner Name:</span> 406 <span>Runner Name:</span>
293 <input type="text" value={routeContent.name} onChange={(e) => { 407 <input
294 setRouteContent({ 408 type="text"
295 ...routeContent, 409 value={routeContent.name}
296 name: e.target.value, 410 onChange={e => {
297 }); 411 setRouteContent({
298 }} /> 412 ...routeContent,
413 name: e.target.value,
414 });
415 }}
416 />
299 </div> 417 </div>
300 <div id='modview-route-score'> 418 <div id="modview-route-score">
301 <span>Score:</span> 419 <span>Score:</span>
302 <input type="number" value={routeContent.score} onChange={(e) => { 420 <input
303 setRouteContent({ 421 type="number"
304 ...routeContent, 422 value={routeContent.score}
305 score: parseInt(e.target.value), 423 onChange={e => {
306 }); 424 setRouteContent({
307 }} /> 425 ...routeContent,
426 score: parseInt(e.target.value),
427 });
428 }}
429 />
308 </div> 430 </div>
309 <div id='modview-route-date'> 431 <div id="modview-route-date">
310 <span>Date:</span> 432 <span>Date:</span>
311 <input type="date" value={routeContent.date} onChange={(e) => { 433 <input
312 setRouteContent({ 434 type="date"
313 ...routeContent, 435 value={routeContent.date}
314 date: e.target.value, 436 onChange={e => {
315 }); 437 setRouteContent({
316 }} /> 438 ...routeContent,
439 date: e.target.value,
440 });
441 }}
442 />
317 </div> 443 </div>
318 <div id='modview-route-showcase'> 444 <div id="modview-route-showcase">
319 <span>Showcase Video:</span> 445 <span>Showcase Video:</span>
320 <input type="text" value={routeContent.showcase} onChange={(e) => { 446 <input
321 setRouteContent({ 447 type="text"
322 ...routeContent, 448 value={routeContent.showcase}
323 showcase: e.target.value, 449 onChange={e => {
324 }); 450 setRouteContent({
325 }} /> 451 ...routeContent,
452 showcase: e.target.value,
453 });
454 }}
455 />
326 </div> 456 </div>
327 <div id='modview-route-description' style={{ height: "180px", gridColumn: "1 / span 5" }}> 457 <div
458 id="modview-route-description"
459 style={{ height: "180px", gridColumn: "1 / span 5" }}
460 >
328 <span>Description:</span> 461 <span>Description:</span>
329 <textarea value={routeContent.description} onChange={(e) => { 462 <textarea
330 setRouteContent({ 463 value={routeContent.description}
331 ...routeContent, 464 onChange={e => {
332 description: e.target.value, 465 setRouteContent({
333 }); 466 ...routeContent,
334 setMd(routeContent.description); 467 description: e.target.value,
335 }} /> 468 });
469 setMd(routeContent.description);
470 }}
471 />
336 </div> 472 </div>
337 <button style={{ gridColumn: "2 / span 3", height: "40px" }} onClick={_create_map_summary_route}>Apply</button> 473 <button
474 style={{ gridColumn: "2 / span 3", height: "40px" }}
475 onClick={_create_map_summary_route}
476 >
477 Apply
478 </button>
338 479
339 <div id='modview-md'> 480 <div id="modview-md">
340 <span>Markdown preview</span> 481 <span>Markdown preview</span>
341 <span><a href="https://commonmark.org/help/" rel="noreferrer" target='_blank'>documentation</a></span> 482 <span>
342 <span><a href="https://remarkjs.github.io/react-markdown/" rel="noreferrer" target='_blank'>demo</a></span> 483 <a
484 href="https://commonmark.org/help/"
485 rel="noreferrer"
486 target="_blank"
487 >
488 documentation
489 </a>
490 </span>
491 <span>
492 <a
493 href="https://remarkjs.github.io/react-markdown/"
494 rel="noreferrer"
495 target="_blank"
496 >
497 demo
498 </a>
499 </span>
343 <p> 500 <p>
344 <ReactMarkdown>{md} 501 <ReactMarkdown>{md}</ReactMarkdown>
345 </ReactMarkdown>
346 </p> 502 </p>
347 </div> 503 </div>
348 </div> 504 </div>
349 )} 505 )
506 }
350 </div> 507 </div>
351 </> 508 </>
352 ); 509 );