aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
authorNidboj132 <lol2s@vp.plm>2023-07-12 17:58:04 +0200
committerNidboj132 <lol2s@vp.plm>2023-07-12 17:58:04 +0200
commit26cdd0d363b1e279b14f7c5c3c12bb7d5e6d87d8 (patch)
tree548bd55dca7c3b1ae3ef86036b2eef3ec361b455 /frontend/src/components
parentlogin (diff)
downloadlphub-26cdd0d363b1e279b14f7c5c3c12bb7d5e6d87d8.tar.gz
lphub-26cdd0d363b1e279b14f7c5c3c12bb7d5e6d87d8.tar.bz2
lphub-26cdd0d363b1e279b14f7c5c3c12bb7d5e6d87d8.zip
summary
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/login.css2
-rw-r--r--frontend/src/components/login.js15
-rw-r--r--frontend/src/components/main.css12
-rw-r--r--frontend/src/components/pages/summary.css322
-rw-r--r--frontend/src/components/pages/summary.js225
-rw-r--r--frontend/src/components/pages/summary_modview.css112
-rw-r--r--frontend/src/components/pages/summary_modview.js254
-rw-r--r--frontend/src/components/sidebar.css6
-rw-r--r--frontend/src/components/sidebar.js33
9 files changed, 951 insertions, 30 deletions
diff --git a/frontend/src/components/login.css b/frontend/src/components/login.css
index 633145e..b46be10 100644
--- a/frontend/src/components/login.css
+++ b/frontend/src/components/login.css
@@ -1,6 +1,6 @@
1span>img { 1span>img {
2 scale: 1.1; 2 scale: 1.1;
3 padding-top: 4px; 3 padding: 4px 0 0 8px;
4} 4}
5.login>button>span{ 5.login>button>span{
6 max-width: 22ch; 6 max-width: 22ch;
diff --git a/frontend/src/components/login.js b/frontend/src/components/login.js
index 7bff5f0..204007f 100644
--- a/frontend/src/components/login.js
+++ b/frontend/src/components/login.js
@@ -7,8 +7,8 @@ import img2 from "../imgs/10.png"
7import img3 from "../imgs/11.png" 7import img3 from "../imgs/11.png"
8 8
9 9
10export default function Login() { 10export default function Login(prop) {
11 11const {token,setToken} = prop
12function login() { 12function login() {
13 window.location.href="https://lp.ardapektezol.com/api/v1/login" 13 window.location.href="https://lp.ardapektezol.com/api/v1/login"
14} 14}
@@ -19,7 +19,6 @@ function logout() {
19 fetch(`/api/v1/token`,{'method':'DELETE'}) 19 fetch(`/api/v1/token`,{'method':'DELETE'})
20 .then(r=>window.location.href="/") 20 .then(r=>window.location.href="/")
21} 21}
22const [token, setToken] = React.useState(null);
23const [isLoggedIn, setIsLoggedIn] = React.useState(false); 22const [isLoggedIn, setIsLoggedIn] = React.useState(false);
24React.useEffect(() => { 23React.useEffect(() => {
25 fetch(`/api/v1/token`) 24 fetch(`/api/v1/token`)
@@ -46,19 +45,19 @@ return (
46 <> 45 <>
47 {isLoggedIn ? ( 46 {isLoggedIn ? (
48 <Link to="/profile" tabIndex={-1} className='login'> 47 <Link to="/profile" tabIndex={-1} className='login'>
49 <button> 48 <button className='sidebar-button'>
50 <img src={profile.avatar_link} alt="" /> 49 <img src={profile.avatar_link} alt="" />
51 <span>{profile.user_name}</span> 50 <span>{profile.user_name}</span>
52 </button> 51 </button>
53 <button onClick={logout}><img src={img3} alt="" /><span></span></button> 52 <button className='sidebar-button' onClick={logout}><img src={img3} alt="" /><span></span></button>
54 </Link> 53 </Link>
55 ) : ( 54 ) : (
56 <Link tabIndex={-1} className='login'> 55 <Link tabIndex={-1} className='login' >
57 <button onClick={login}> 56 <button className='sidebar-button' onClick={login}>
58 <img src={img2} alt="" /> 57 <img src={img2} alt="" />
59 <span><img src={img1} alt="Sign in through Steam" /></span> 58 <span><img src={img1} alt="Sign in through Steam" /></span>
60 </button> 59 </button>
61 <button disabled><span></span></button> 60 <button className='sidebar-button' disabled><span></span></button>
62 </Link> 61 </Link>
63 )} 62 )}
64 </> 63 </>
diff --git a/frontend/src/components/main.css b/frontend/src/components/main.css
index 990bc41..48e6379 100644
--- a/frontend/src/components/main.css
+++ b/frontend/src/components/main.css
@@ -1,11 +1,17 @@
1
1main { 2main {
2 overflow: auto; 3 overflow: auto;
4 overflow-x: hidden;
3 position: relative; 5 position: relative;
4 width: calc(100% - 350px); 6
7 width: calc(100% - 380px);
5 height: 100vh; 8 height: 100vh;
6 left: 350px; 9 left: 350px;
7 /* background-color: red; */ 10
11 padding-right: 30px;
12
8 font-size: 40px; 13 font-size: 40px;
9 font-family: BarlowSemiCondensed-Regular; 14 font-family: BarlowSemiCondensed-Regular;
10 color: white; 15 color: #cdcfdf;
16
11} 17}
diff --git a/frontend/src/components/pages/summary.css b/frontend/src/components/pages/summary.css
new file mode 100644
index 0000000..e97dc0f
--- /dev/null
+++ b/frontend/src/components/pages/summary.css
@@ -0,0 +1,322 @@
1
2#background-image{
3 z-index: -1;
4 position: absolute;
5 opacity: 10%;
6 height: 50%;
7 width: 100%
8}
9#background-image>img{
10 object-fit: cover;
11 width: 100%;
12 height: 100%;
13}
14#background-image::before{
15 content: "";
16 position: absolute;
17 width: 100%;
18 height: 100%;
19 background: linear-gradient(to top, #161723, #0000);
20}
21
22/* Section 1: map name + difficulty*/
23
24#section1{
25 margin: 20px 0 0 0;
26 width: 100%;
27
28 display:grid;
29 grid-template-columns: auto 470px;
30 cursor: default;
31}
32
33.nav-button{
34 height: 40px;
35 background-color: #2b2e46;
36
37 color: #cdcfdf;;
38 font-size: 18px;
39 font-family: inherit;
40 border: none;
41 transition: background-color .1s;
42}
43#section1>div>.nav-button:nth-child(1){border-radius: 20px 0 0 20px;}
44#section1>div>.nav-button:nth-child(2){border-radius: 0 20px 20px 0;margin-left: 2px;}
45.nav-button>span{padding: 0 8px 0 8px;}
46.nav-button:hover{background-color: #202232;cursor: pointer;}
47
48
49
50/* Section 2: navbar */
51
52#section2{
53 margin: 40px 0 0 0;
54 width: 100%;
55
56 display: grid;
57 grid-template-columns: auto auto auto;
58}
59
60#section2>.nav-button{
61 height: 50px;
62 font-size: 22px;
63 display: flex;
64 justify-content: center;
65 place-items: center;
66
67}
68#section2>.nav-button>img{scale: 1.2;}
69#section2>.nav-button:nth-child(1){border-radius: 30px 0 0 30px;}
70#section2>.nav-button:nth-child(2){margin-left:2px;}
71#section2>.nav-button:nth-child(3){border-radius: 0 30px 30px 0;margin-left: 2px;}
72
73
74/* Section 3: category + history */
75
76#section3{
77 margin: 40px 0 0 0;
78 height: auto;
79
80 display: grid;
81 grid-template-columns: 50% 50%;
82 gap: 20px;
83
84}
85
86#category{
87 display: grid;
88 height: 350px;
89 border-radius: 30px;
90 overflow: hidden;
91
92}
93#category>p{
94 margin-bottom: 20px;
95 display: inline;
96 text-align: center;
97 font-size: 50px;
98 cursor: default;
99 color: white;
100}
101
102p>span.portal-count{font-weight: bold;font-size: 100px;vertical-align: -15%;}
103
104#category-image{
105 transform: translate(-20%, -15%);
106 z-index: -1;
107 border-radius: 20px;
108 overflow: hidden;
109 width: 125%;
110 margin: 22px;
111 filter: blur(4px) contrast(80%) brightness(80%);
112}
113
114#category>span{
115 margin-top: 70px;
116 background-color: #202232;
117
118 display: grid;
119 grid-template-columns: auto auto auto auto;
120 gap: 2px;
121}
122#category>span>button{
123 font-family: BarlowSemiCondensed-Regular;
124 font-size: 18px;
125 border: none;
126 height: 40px;
127 color: #cdcfdf;
128 background-color: #2b2e46;
129
130 cursor: pointer;
131 transition: background-color .1s;
132}
133#category>span>button:hover{background-color: #202232;}
134
135
136
137#history>div>hr{border: 1px solid #2b2e46;margin: 4px 20px 4px 20px;}
138#history{
139 height: 350px;
140 min-width: 560px;
141 background-color: #202232;
142 border-radius: 30px;
143
144}
145
146#history>div{height: 290px;}
147#records{overflow-y: auto; height: 255px;}
148.record-top, .record{
149 font-size: 18px;
150
151 display: grid;
152 text-align: center;
153 grid-template-columns: calc(100%/3) calc(100%/3) calc(100%/3);
154}
155.record-top{font-weight: bold;margin: 20px 20px 0 20px;}
156.record{
157 margin: 10px 20px 0 20px;
158 height: 44px; width: calc(100% - 40px);
159
160
161 background-color: #2b2e46;
162 color: inherit;
163 border-radius: 40px;
164 place-items: center;
165
166 border: 0;
167 cursor: pointer;
168 transition: background-color .1s;
169}
170.record:hover{background-color: #161723;}
171
172#history>span{
173 display: grid;
174 grid-template-columns: 50% 50%;
175}
176#history>span>button{
177
178 width: 100%; height: 40px;
179 font-family: BarlowSemiCondensed-Regular;
180 font-size: 18px;
181 border: none;
182
183 color: #cdcfdf;
184 background-color: #2b2e46;
185
186 cursor: pointer;
187 transition: background-color .1s;
188}
189#history>span>button:hover{background-color: #202232 !important;}
190#history>span>button:nth-child(1){border-radius: 0 0 0 30px;}
191#history>span>button:nth-child(2){border-radius: 0 0 30px 0;}
192
193/* Section 4: Difficulty + count */
194
195#section4{
196 display: grid;
197 grid-template-columns: 50% 50%;
198 margin: 40px 0 20px 0;
199 width: 100%;
200 gap: 8px;
201}
202
203#difficulty,
204#count {
205 background-color: #202232;
206 width: 100%; height: 100px;
207 min-width: 250px;
208 margin: 10px;
209
210 text-align: center;
211
212 border-radius: 20px;
213 display: grid;
214 grid-template-rows: 20px 40px 40px;
215}
216#difficulty>span:nth-child(1),
217#count>span:nth-child(1){
218 padding-top:10px;
219 font-size: 18px;
220 color:#cdcfdf
221}
222#difficulty>span:nth-child(2){
223 font-size: 40px;
224 color:lime
225}
226#difficulty>div{
227 width: 250px;
228 display: grid;
229 grid-template-columns: auto auto auto auto auto;
230 padding: 0 calc(50% - 125px) 0 calc(50% - 125px);
231 place-items: center;
232}
233
234.difficulty-rating{
235 border-radius: 20px;
236 width: 40px; height: 3px;
237 background-color: #2b2e46;
238}
239
240#count>div{
241 padding-top:10px;
242 font-size: 50px;
243 color:white
244}
245
246/* Section 5: route desc + video */
247#section5{
248 margin: 40px 0 20px 0;
249 width: 100%;
250}
251
252#description{
253 width: 100%; height: auto;
254 min-height: 342px;
255}
256
257
258
259
260#description>iframe{
261 margin: 4px;
262 float:right;
263 border: 0;
264 border-radius: 20px;
265 width: 608px; height: 342px;
266}
267
268#description>h3{margin: 0 0 10px 0; color: white;}
269#description-text{
270 display: block;
271 font-size: 21px;
272 word-wrap: break-word;
273}
274#description-text>b{font-size: inherit;}
275#description-text>a{font-size: inherit;color: #3c91e6;}
276
277.triangle{
278 display: inline-block;
279 width: 8px; height: 0;
280 border-top: 7px solid transparent;
281 border-right: 8px solid #cdcfdf;
282 border-bottom: 7px solid transparent;
283}
284
285 /* such responsive, very mobile */
286@media screen and (max-width: 1480px) {
287 #section3{grid-template-columns: auto;}
288 #category{min-width: 608px;}
289 #history{min-width: 608px;}
290 #description{min-width: 608px;}
291 #section4{min-width: 588px;}
292
293 #description>iframe{
294 padding: 0 0 0 calc(50% - 304px);
295 float:none;
296 justify-content: center;
297 align-items: center;
298 }
299/* }
300
301@media screen and (max-width: 1280px) { */
302 #section1{
303 grid-template-columns: auto;
304 place-items: center;
305 text-align: center;
306
307 }
308
309 #section2{
310 grid-template-columns: auto;
311 width: 450px;
312 margin: 40px auto 0 auto;
313 }
314 #section2>.nav-button{
315 margin: 1px 0 1px 0 !important;
316 }
317 #section2>.nav-button:nth-child(1){border-radius: 30px 30px 0 0;}
318 #section2>.nav-button:nth-child(2){border-radius: 0;}
319 #section2>.nav-button:nth-child(3){border-radius: 0 0 30px 30px;}
320
321
322} \ No newline at end of file
diff --git a/frontend/src/components/pages/summary.js b/frontend/src/components/pages/summary.js
new file mode 100644
index 0000000..c628dd4
--- /dev/null
+++ b/frontend/src/components/pages/summary.js
@@ -0,0 +1,225 @@
1import React from 'react';
2import { useLocation } from "react-router-dom";
3import ReactMarkdown from 'react-markdown'
4
5import "./summary.css";
6
7import img4 from "../../imgs/4.png"
8import img5 from "../../imgs/5.png"
9import img6 from "../../imgs/6.png"
10import Modview from "./summary_modview.js"
11
12export default function Summary(prop) {
13const {token,mod} = prop
14const fakedata={} //for debug
15
16 const location = useLocation()
17
18 //fetching data
19 const [data, setData] = React.useState(null);
20 React.useEffect(() => {
21 fetch(`/api/v1/maps/${location.pathname.split('/')[2]}/summary`)
22 .then(r => r.json())
23 .then(d => {
24 if(Object.keys(fakedata).length!==0){setData(fakedata)}
25 else{setData(d.data)}
26 if(d.data.summary.routes.length===0){d.data.summary.routes[0]={"category": "","history": {"score_count": 0,},"rating": 0,"description": "","showcase": ""}}
27 })
28 // eslint-disable-next-line
29 }, []);
30
31
32
33
34
35const [navState, setNavState] = React.useState(0); // eslint-disable-next-line
36React.useEffect(() => {NavClick();}, [[],navState]);
37
38function NavClick() {
39 if(data!==null){
40 const btn = document.querySelectorAll("#section2 button.nav-button");
41 btn.forEach((e) => {e.style.backgroundColor = "#2b2e46"});
42 btn[navState].style.backgroundColor = "#202232";
43}}
44
45
46const [catState, setCatState] = React.useState(1); // eslint-disable-next-line
47React.useEffect(() => {CatClick();}, [[],catState]);
48
49function CatClick() {
50 if(data!==null){
51 const btn = document.querySelectorAll("#section3 #category span button");
52 btn.forEach((e) => {e.style.backgroundColor = "#2b2e46"});
53 btn[catState-1].style.backgroundColor = "#202232";
54}}
55React.useEffect(()=>{
56 if(data!==null && data.summary.routes.filter(e=>e.category.id===catState).length!==0){
57 selectRun(0,catState)} // eslint-disable-next-line
58},[catState,data])
59
60
61const [hisState, setHisState] = React.useState(0); // eslint-disable-next-line
62React.useEffect(() => {HisClick();}, [[],hisState]);
63
64function HisClick() {
65 if(data!==null){
66 const btn = document.querySelectorAll("#section3 #history span button");
67 btn.forEach((e) => {e.style.backgroundColor = "#2b2e46"});
68 btn[hisState].style.backgroundColor = "#202232";
69}}
70
71const [selectedRun,setSelectedRun] = React.useState(0)
72
73function selectRun(x,y){
74 let r = document.querySelectorAll("button.record")
75 r.forEach(e=>e.style.backgroundColor="#2b2e46")
76 r[x].style.backgroundColor="#161723"
77
78
79 if(data!==null && data.summary.routes.length!==0 && data.summary.routes.length!==0){
80 if(y===2){x+=data.summary.routes.filter(e=>e.category.id<2).length}
81 if(y===3){x+=data.summary.routes.filter(e=>e.category.id<3).length}
82 if(y===4){x+=data.summary.routes.filter(e=>e.category.id<4).length}
83 setSelectedRun(x)
84 }
85}
86
87const [vid,setVid] = React.useState("")
88React.useEffect(()=>{
89 if(data!==null){
90 let showcase = data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].showcase
91 showcase.length>6 ? setVid("https://www.youtube.com/embed/"+YouTubeGetID(showcase))
92 : setVid("")
93 } // eslint-disable-next-line
94},[[],selectedRun])
95
96function YouTubeGetID(url){
97 url = url.split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
98 return (url[2] !== undefined) ? url[2].split(/[^0-9a-z_]/i)[0] : url[0];
99 }
100
101if(data!==null){
102return (
103 <>
104 {token!==null?mod===true?<Modview selectedRun={selectedRun} data={data} token={token}/>:"":""}
105
106 <div id='background-image'>
107 <img src={data.map.image} alt="" />
108 </div>
109 <main>
110 <section id='section1'>
111 <div>
112 <button className='nav-button'><i className='triangle'></i><span>{data.map.game_name}</span></button>
113 <button className='nav-button'><i className='triangle'></i><span>{data.map.chapter_name}</span></button>
114 <br/><span><b>{data.map.map_name}</b></span>
115 </div>
116
117
118 </section>
119
120 <section id='section2'>
121 <button className='nav-button' onClick={()=>setNavState(0)}><img src={img4} alt="" /><span>Summary</span></button>
122 <button className='nav-button' onClick={()=>setNavState(1)}><img src={img5} alt="" /><span>Leaderboards</span></button>
123 <button className='nav-button' onClick={()=>setNavState(2)}><img src={img6} alt="" /><span>Discussions</span></button>
124 </section>
125
126 <section id='section3'>
127 <div id='category'
128 style={data.map.image===""?{backgroundColor:"#202232"}:{}}>
129 <img src={data.map.image} alt="" id='category-image'></img>
130 <p><span className='portal-count'>{data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].history.score_count}</span>
131 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].history.score_count === 1 ? ` portal` : ` portals` }</p>
132 <span>
133 <button onClick={()=>setCatState(1)}>CM</button>
134 <button onClick={()=>setCatState(2)}>NoSLA</button>
135 {data.map.is_coop?<button onClick={()=>setCatState(3)}>SLA</button>
136 :<button onClick={()=>setCatState(3)}>Inbounds SLA</button>}
137 <button onClick={()=>setCatState(4)}>Any%</button>
138 </span>
139
140 </div>
141
142 <div id='history'>
143 <div>
144 <div className='record-top'>
145 <span>Date</span>
146 <span>Record</span>
147 <span>First completion</span>
148 </div>
149 <hr/>
150 <div id='records'>
151
152 {data.summary.routes
153 .sort((a, b) => a.history.score_count - b.history.score_count)
154 .filter(e=>e.category.id===catState)
155 .map((r, index) => (
156 <button className='record' key={index} onClick={()=>{
157 selectRun(index,r.category.id);
158 }}>
159 <span>{ new Date(r.history.date).toLocaleDateString(
160 "en-US", { month: 'long', day: 'numeric', year: 'numeric' }
161 )}</span>
162 <span>{r.history.score_count}</span>
163 <span>{r.history.runner_name}</span>
164 </button>
165 ))}
166
167 </div>
168 </div>
169 <span>
170 <button onClick={()=>setHisState(0)}>List</button>
171 <button onClick={()=>setHisState(1)}>Graph</button>
172 </span>
173 </div>
174
175 </section>
176
177 <section id='section4'>
178 <div id='difficulty'>
179 <span>Difficulty</span>
180 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 0 ? (<span style={{color:"lime"}}>Very easy</span>):null}
181 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 1 ? (<span style={{color:"green"}}>Easy</span>):null}
182 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 2 ? (<span style={{color:"yellow"}}>Medium</span>):null}
183 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 3 ? (<span style={{color:"orange"}}>Hard</span>):null}
184 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 4 ? (<span style={{color:"red"}}>Very hard</span>):null}
185 <div>
186 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 0 ? (<div className='difficulty-rating' style={{backgroundColor:"lime"}}></div>) : (<div className='difficulty-rating'></div>)}
187 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 1 ? (<div className='difficulty-rating' style={{backgroundColor:"green"}}></div>) : (<div className='difficulty-rating'></div>)}
188 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 2 ? (<div className='difficulty-rating' style={{backgroundColor:"yellow"}}></div>) : (<div className='difficulty-rating'></div>)}
189 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 3 ? (<div className='difficulty-rating' style={{backgroundColor:"orange"}}></div>) : (<div className='difficulty-rating'></div>)}
190 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].rating === 4 ? (<div className='difficulty-rating' style={{backgroundColor:"red"}}></div>) : (<div className='difficulty-rating'></div>)}
191 </div>
192 </div>
193 <div id='count'>
194 <span>Completion count</span>
195 <div>6275</div>
196 </div>
197 </section>
198
199 <section id='section5'>
200 <div id='description'>
201 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].showcase!=="" ?
202 <iframe title='Showcase video' src={vid}> </iframe>
203 : ""}
204 <h3>Route description</h3>
205 <span id='description-text'>
206 <ReactMarkdown>
207 {data.summary.routes.sort((a,b)=>a.category.id - b.category.id)[selectedRun].description}
208 </ReactMarkdown>
209 </span>
210 </div>
211
212 </section>
213 </main>
214 </>
215 )
216}else{
217 return (
218 <main></main>
219 )
220}
221
222
223}
224
225
diff --git a/frontend/src/components/pages/summary_modview.css b/frontend/src/components/pages/summary_modview.css
new file mode 100644
index 0000000..c6d3d8d
--- /dev/null
+++ b/frontend/src/components/pages/summary_modview.css
@@ -0,0 +1,112 @@
1div#modview{
2 position: absolute;
3 left: 50%;
4 z-index: 20;
5 width: 320px; height: auto;
6 /* background-color: red; */
7
8 transform: translateY(-68%);
9}
10div#modview>div>button{
11 height: 30px;
12}
13
14div#modview>div:nth-child(1){
15 display: grid;
16 grid-template-columns: 50% 50%;
17}
18
19div#modview>div:nth-child(2){
20 display: grid;
21 place-items: center;
22}
23
24#modview-menu{
25 position: absolute;
26 left: calc(50% + 160px); top: 130px;
27 transform: translateX(-50%);
28 background-color: #2b2e46;
29 z-index: 2; color: white;
30}
31
32#modview-menu-image{
33 box-shadow: 0 0 40px 16px black;
34 outline: 8px solid #2b2e46;
35 border-radius: 20px;
36 font-size: 40px;
37 display: grid;
38 grid-template-columns: 50% 50%;
39 /* place-items: center; */
40
41}
42#modview-menu-image>div:nth-child(1){
43 height: 400px; width: 500px;
44 display: grid;
45 grid-template-rows: 30% 70%;
46}
47#modview-menu-image>div:nth-child(2){
48 height: 400px; width: 500px;
49 display: grid;
50 grid-template-rows: 20% 10%;
51}
52
53#modview-menu-image>div>button{width: 300px;margin-left:100px;}
54#modview-menu-image>div>img{width: 500px;}
55#modview-menu-image>div>button{font-size: 20px;}
56#modview-menu-image>div>span>input[type="file"]{font-size: 15px;}
57
58
59#modview-menu-add,
60#modview-menu-edit{
61 box-shadow: 0 0 40px 16px black;
62 outline: 8px solid #2b2e46;
63 border-radius: 20px;
64 font-size: 40px;
65 display: grid;
66 grid-template-columns: 20% 20% 20% 20% 20%;
67}
68
69#modview-menu-add>div,
70#modview-menu-edit>div{
71 display: grid;
72 margin: 20px;
73 width: 200px;
74 font-size: 20px;
75}
76#modview-route-description>textarea{
77 resize: none;
78 height: 160px;
79 width: 1160px;
80}
81#modview-route-showcase>input::placeholder{opacity: .5;}
82#modview_block{
83 position: absolute;
84 background-color: black;
85 opacity: .3;
86 left: 320px;
87 width: calc(100% - 320px);
88 height: 100%;
89 z-index: 2;
90 cursor: no-drop;
91}
92#modview-md{
93 box-shadow: 0 0 40px 16px black;
94 background-color: #2b2e46;
95 outline: 8px solid #2b2e46;
96
97 border-radius: 20px;
98 position: absolute;
99 padding: 10px; top: 400px;
100 width: 1180px; height: 300px;
101 overflow-y: auto;
102 word-wrap: break-word;
103}
104#modview-md>span>a{
105 padding-left: 20px;
106 color:aqua;
107}
108#modview-md>p{
109 font-family: BarlowSemiCondensed-Regular;
110 color: #cdcfdf;
111 font-size: 21px;
112} \ No newline at end of file
diff --git a/frontend/src/components/pages/summary_modview.js b/frontend/src/components/pages/summary_modview.js
new file mode 100644
index 0000000..3541c48
--- /dev/null
+++ b/frontend/src/components/pages/summary_modview.js
@@ -0,0 +1,254 @@
1import React from 'react';
2import { useLocation } from "react-router-dom";
3import ReactMarkdown from 'react-markdown'
4
5import "./summary_modview.css";
6
7
8export default function Modview(prop) {
9const {selectedRun,data,token} = prop
10
11const [menu,setMenu] = React.useState(0)
12React.useEffect(()=>{
13if(menu===3){ // add
14 document.querySelector("#modview-route-name>input").value=""
15 document.querySelector("#modview-route-score>input").value=""
16 document.querySelector("#modview-route-date>input").value=""
17 document.querySelector("#modview-route-showcase>input").value=""
18 document.querySelector("#modview-route-description>textarea").value=""
19 }
20if(menu===2){ // edit
21 document.querySelector("#modview-route-id>input").value=data.summary.routes[selectedRun].route_id
22 document.querySelector("#modview-route-name>input").value=data.summary.routes[selectedRun].history.runner_name
23 document.querySelector("#modview-route-score>input").value=data.summary.routes[selectedRun].history.score_count
24 document.querySelector("#modview-route-date>input").value=data.summary.routes[selectedRun].history.date.split("T")[0]
25 document.querySelector("#modview-route-showcase>input").value=data.summary.routes[selectedRun].showcase
26 document.querySelector("#modview-route-description>textarea").value=data.summary.routes[selectedRun].description
27} // eslint-disable-next-line
28},[menu])
29
30function compressImage(file) {
31 const reader = new FileReader();
32 reader.readAsDataURL(file);
33 return new Promise(resolve => {
34 reader.onload = () => {
35 const img = new Image();
36 img.src = reader.result;
37 img.onload = () => {
38 let {width, height} = img;
39 if (width > 550) {
40 height *= 550 / width;
41 width = 550;
42 }
43 if (height > 320) {
44 width *= 320 / height;
45 height = 320;
46 }
47 const canvas = document.createElement('canvas');
48 canvas.width = width;
49 canvas.height = height;
50 canvas.getContext('2d').drawImage(img, 0, 0, width, height);
51 resolve(canvas.toDataURL(file.type, 0.6));
52 };
53 };
54 });
55}
56const [image,setImage] = React.useState(null)
57function uploadImage(){
58 if(window.confirm("Are you sure you want to submit this to the database?")){
59 fetch(`/api/v1/maps/${location.pathname.split('/')[2]}/image`,{
60 method: 'PUT',
61 headers: {Authorization: token},
62 body: JSON.stringify({"image": image})
63 }).then(r=>window.location.reload())
64 }
65}
66const location = useLocation()
67function editRoute(){
68if(window.confirm("Are you sure you want to submit this to the database?")){
69 let payload = {
70 "description": document.querySelector("#modview-route-description>textarea").value===""?"No description available.":document.querySelector("#modview-route-description>textarea").value,
71 "record_date": document.querySelector("#modview-route-date>input").value+"T00:00:00Z",
72 "route_id": parseInt(document.querySelector("#modview-route-id>input").value),
73 "score_count": parseInt(document.querySelector("#modview-route-score>input").value),
74 "showcase": document.querySelector("#modview-route-showcase>input").value,
75 "user_name": document.querySelector("#modview-route-name>input").value
76 }
77 fetch(`/api/v1/maps/${location.pathname.split('/')[2]}/summary`,{
78 method: 'PUT',
79 headers: {Authorization: token},
80 body: JSON.stringify(payload)
81 }).then(r=>window.location.reload())
82 }
83}
84
85
86function addRoute(){
87 if(window.confirm("Are you sure you want to submit this to the database?")){
88 let payload = {
89 "category_id": parseInt(document.querySelector("#modview-route-category>select").value),
90 "description": document.querySelector("#modview-route-description>textarea").value===""?"No description available.":document.querySelector("#modview-route-description>textarea").value,
91 "record_date": document.querySelector("#modview-route-date>input").value+"T00:00:00Z",
92 "score_count": parseInt(document.querySelector("#modview-route-score>input").value),
93 "showcase": document.querySelector("#modview-route-showcase>input").value,
94 "user_name": document.querySelector("#modview-route-name>input").value
95 }
96 fetch(`/api/v1/maps/${location.pathname.split('/')[2]}/summary`,{
97 method: 'POST',
98 headers: {Authorization: token},
99 body: JSON.stringify(payload)
100 }).then(r=>window.location.reload())
101 }
102}
103
104function deleteRoute(){
105if(data.summary.routes[0].category==='')
106{window.alert("no run selected")}else{
107if(window.confirm(`Are you sure you want to delete this run from the database?
108${data.summary.routes[selectedRun].category.name} ${data.summary.routes[selectedRun].history.score_count} portals ${data.summary.routes[selectedRun].history.runner_name}`)===true){
109 console.log("deleted:",selectedRun)
110 fetch(`/api/v1/maps/${location.pathname.split('/')[2]}/summary`,{
111 method: 'DELETE',
112 headers: {Authorization: token},
113 body: JSON.stringify({"route_id":data.summary.routes[selectedRun].route_id})
114 }).then(r=>window.location.reload())
115}}
116
117}
118
119const [showButton, setShowButton] = React.useState(1)
120const modview = document.querySelector("div#modview")
121React.useEffect(()=>{
122 if(modview!==null){
123 showButton ? modview.style.transform="translateY(-68%)"
124 : modview.style.transform="translateY(0%)"
125 }
126 let modview_block = document.querySelector("#modview_block")
127 showButton===1?modview_block.style.display="none":modview_block.style.display="block"// eslint-disable-next-line
128},[showButton])
129
130const [md,setMd] = React.useState("")
131
132return (
133 <>
134 <div id="modview_block"></div>
135 <div id='modview'>
136 <div>
137 <button onClick={()=>setMenu(1)}>edit image</button>
138 <button onClick={
139 data.summary.routes[0].category===''?()=>window.alert("no run selected"):()=>setMenu(2)}>edit selected route</button>
140 <button onClick={()=>setMenu(3)}>add new route</button>
141 <button onClick={()=>deleteRoute()}>delete selected route</button>
142 </div>
143 <div>
144 {showButton ?(
145 <button onClick={()=>setShowButton(0)}>Show</button>
146 ) : (
147 <button onClick={()=>{setShowButton(1);setMenu(0)}}>Hide</button>
148 )}
149 </div>
150 </div>
151 {menu!==0? (
152 <div id='modview-menu'>
153 {menu===1? (
154 // image
155 <div id='modview-menu-image'>
156 <div>
157 <span>current image:</span>
158 <img src={data.map.image} alt="missing" />
159 </div>
160
161 <div>
162 <span>new image:
163 <input type="file" accept='image/*' onChange={e=>
164 compressImage(e.target.files[0])
165 .then(d=>setImage(d))
166 }/></span>
167 {image!==null?(<button onClick={()=>uploadImage()}>upload</button>):<span></span>}
168 <img src={image} alt="" id='modview-menu-image-file'/>
169
170 </div>
171 </div>
172 ):menu===2?(
173 // edit route
174 <div id='modview-menu-edit'>
175 <div id='modview-route-id'>
176 <span>route id:</span>
177 <input type="number" disabled/>
178 </div>
179 <div id='modview-route-name'>
180 <span>runner name:</span>
181 <input type="text"/>
182 </div>
183 <div id='modview-route-score'>
184 <span>score:</span>
185 <input type="number"/>
186 </div>
187 <div id='modview-route-date'>
188 <span>date:</span>
189 <input type="date"/>
190 </div>
191 <div id='modview-route-showcase'>
192 <span>showcase video:</span>
193 <input type="text"/>
194 </div>
195 <div id='modview-route-description' style={{height:"180px",gridColumn:"1 / span 5"}}>
196 <span>description:</span>
197 <textarea onChange={()=>setMd(document.querySelector("#modview-route-description>textarea").value)}></textarea>
198 </div>
199 <button style={{gridColumn:"2 / span 3",height:"40px"}} onClick={editRoute}>Apply</button>
200 </div>
201 ):menu===3?(
202 // add route
203 <div id='modview-menu-add'>
204 <div id='modview-route-category'>
205 <span>category:</span>
206 <select>
207 <option value="1" key="1">CM</option>
208 <option value="2" key="2">No SLA</option>
209 {data.map.game_name==="Portal 2 - Cooperative"?"":(
210 <option value="3" key="3">Inbounds SLA</option>)}
211 <option value="4" key="4">Any%</option>
212 </select>
213 </div>
214 <div id='modview-route-name'>
215 <span>runner name:</span>
216 <input type="text" />
217 </div>
218 <div id='modview-route-score'>
219 <span>score:</span>
220 <input type="number" />
221 </div>
222 <div id='modview-route-date'>
223 <span>date:</span>
224 <input type="date" />
225 </div>
226 <div id='modview-route-showcase'>
227 <span>showcase video:</span>
228 <input type="text" />
229 </div>
230 <div id='modview-route-description' style={{height:"180px",gridColumn:"1 / span 5"}}>
231 <span>description:</span>
232 <textarea defaultValue={"No description available."} onChange={()=>setMd(document.querySelector("#modview-route-description>textarea").value)}></textarea>
233 </div>
234 <button style={{gridColumn:"2 / span 3",height:"40px"}} onClick={addRoute}>Apply</button>
235 </div>
236 ):("error")}
237
238 {menu!==1?(
239 <div id='modview-md'>
240 <span>Markdown preview</span>
241 <span><a href="https://commonmark.org/help/" rel="noreferrer" target='_blank'>documentation</a></span>
242 <span><a href="https://remarkjs.github.io/react-markdown/" rel="noreferrer" target='_blank'>demo</a></span>
243 <p>
244 <ReactMarkdown>{md}
245 </ReactMarkdown>
246 </p>
247 </div>
248 ):""}
249 </div>):""}
250
251 </>
252)
253}
254
diff --git a/frontend/src/components/sidebar.css b/frontend/src/components/sidebar.css
index 84600fb..8011a6e 100644
--- a/frontend/src/components/sidebar.css
+++ b/frontend/src/components/sidebar.css
@@ -55,7 +55,7 @@ span>b{
55 justify-items: left; 55 justify-items: left;
56 grid-template-rows: calc(100vh - 670px) 50px 50px 50px; 56 grid-template-rows: calc(100vh - 670px) 50px 50px 50px;
57} 57}
58button>span{ 58.sidebar-button>span{
59 font-family: BarlowSemiCondensed-Regular; 59 font-family: BarlowSemiCondensed-Regular;
60 font-size: 18px; 60 font-size: 18px;
61 color: #CDCFDF; 61 color: #CDCFDF;
@@ -63,7 +63,7 @@ button>span{
63 line-height: 28px; 63 line-height: 28px;
64 transition: opacity .1s; 64 transition: opacity .1s;
65} 65}
66button{ 66.sidebar-button{
67 display: grid; 67 display: grid;
68 grid-template-columns: 50px auto; 68 grid-template-columns: 50px auto;
69 place-items: left; 69 place-items: left;
@@ -100,7 +100,7 @@ button>img {
100 width: 268px; height: calc(100vh - 120px); 100 width: 268px; height: calc(100vh - 120px);
101 min-height: 550px; 101 min-height: 550px;
102} 102}
103input[type=text]{ 103input#searchbar[type=text]{
104 margin: 10px 0 0 6px; 104 margin: 10px 0 0 6px;
105 padding: 1px 0px 1px 16px; 105 padding: 1px 0px 1px 16px;
106 width: 240px; 106 width: 240px;
diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js
index aaaf196..77800bd 100644
--- a/frontend/src/components/sidebar.js
+++ b/frontend/src/components/sidebar.js
@@ -15,7 +15,8 @@ import img8 from "../imgs/8.png"
15import img9 from "../imgs/9.png" 15import img9 from "../imgs/9.png"
16import Login from "./login.js" 16import Login from "./login.js"
17 17
18export default function Sidebar() { 18export default function Sidebar(prop) {
19const {token,setToken} = prop
19 20
20// Locks search button for 300ms before it can be clicked again, prevents spam 21// Locks search button for 300ms before it can be clicked again, prevents spam
21const [isLocked, setIsLocked] = React.useState(false); 22const [isLocked, setIsLocked] = React.useState(false);
@@ -30,7 +31,7 @@ if (!isLocked) {
30 31
31// Clicked buttons 32// Clicked buttons
32function SidebarClick(x){ 33function SidebarClick(x){
33const btn = document.querySelectorAll("button"); 34const btn = document.querySelectorAll("button.sidebar-button");
34 35
35if(sidebar===1){setSidebar(0);SidebarHide()} 36if(sidebar===1){setSidebar(0);SidebarHide()}
36 37
@@ -46,8 +47,8 @@ btn[x].style.backgroundColor="#202232"
46const [sidebar, setSidebar] = React.useState(); 47const [sidebar, setSidebar] = React.useState();
47 48
48function SidebarHide(){ 49function SidebarHide(){
49const btn = document.querySelectorAll("button"); 50const btn = document.querySelectorAll("button.sidebar-button")
50const span = document.querySelectorAll("button>span"); 51const span = document.querySelectorAll("button.sidebar-button>span");
51const side = document.querySelector("#sidebar-list"); 52const side = document.querySelector("#sidebar-list");
52const login = document.querySelectorAll(".login>button")[1]; 53const login = document.querySelectorAll(".login>button")[1];
53 54
@@ -106,49 +107,51 @@ return (
106 </div> 107 </div>
107 <div id='sidebar-list'> {/* List */} 108 <div id='sidebar-list'> {/* List */}
108 <div id='sidebar-toplist'> {/* Top */} 109 <div id='sidebar-toplist'> {/* Top */}
109 <button onClick={()=>HandleLock()}><img src={img1} alt="" /><span>Search</span></button> 110
111 <button className='sidebar-button' onClick={()=>HandleLock()}><img src={img1} alt="" /><span>Search</span></button>
112
110 <span></span> 113 <span></span>
111 114
112 <Link to="/" tabIndex={-1}> 115 <Link to="/" tabIndex={-1}>
113 <button><img src={img2} alt="" /><span>Home&nbsp;Page</span></button> 116 <button className='sidebar-button'><img src={img2} alt="" /><span>Home&nbsp;Page</span></button>
114 </Link> 117 </Link>
115 118
116 <Link to="/news" tabIndex={-1}> 119 <Link to="/news" tabIndex={-1}>
117 <button><img src={img3} alt="" /><span>News</span></button> 120 <button className='sidebar-button'><img src={img3} alt="" /><span>News</span></button>
118 </Link> 121 </Link>
119 122
120 <Link to="/records" tabIndex={-1}> 123 <Link to="/records" tabIndex={-1}>
121 <button><img src={img4} alt="" /><span>Records</span></button> 124 <button className='sidebar-button'><img src={img4} alt="" /><span>Records</span></button>
122 </Link> 125 </Link>
123 126
124 <Link to="/leaderboards" tabIndex={-1}> 127 <Link to="/leaderboards" tabIndex={-1}>
125 <button><img src={img5} alt="" /><span>Leaderboards</span></button> 128 <button className='sidebar-button'><img src={img5} alt="" /><span>Leaderboards</span></button>
126 </Link> 129 </Link>
127 130
128 <Link to="/discussions" tabIndex={-1}> 131 <Link to="/discussions" tabIndex={-1}>
129 <button><img src={img6} alt="" /><span>Discussions</span></button> 132 <button className='sidebar-button'><img src={img6} alt="" /><span>Discussions</span></button>
130 </Link> 133 </Link>
131 134
132 <Link to="/scorelog" tabIndex={-1}> 135 <Link to="/scorelog" tabIndex={-1}>
133 <button><img src={img7} alt="" /><span>Score&nbsp;Logs</span></button> 136 <button className='sidebar-button'><img src={img7} alt="" /><span>Score&nbsp;Logs</span></button>
134 </Link> 137 </Link>
135 </div> 138 </div>
136 <div id='sidebar-bottomlist'> 139 <div id='sidebar-bottomlist'>
137 <span></span> 140 <span></span>
138 141
139 <Login/> 142 <Login token={token} setToken={setToken}/>
140 143
141 <Link to="/rules" tabIndex={-1}> 144 <Link to="/rules" tabIndex={-1}>
142 <button><img src={img8} alt="" /><span>Leaderboard&nbsp;Rules</span></button> 145 <button className='sidebar-button'><img src={img8} alt="" /><span>Leaderboard&nbsp;Rules</span></button>
143 </Link> 146 </Link>
144 147
145 <Link to="/about" tabIndex={-1}> 148 <Link to="/about" tabIndex={-1}>
146 <button><img src={img9} alt="" /><span>About&nbsp;P2LP</span></button> 149 <button className='sidebar-button'><img src={img9} alt="" /><span>About&nbsp;P2LP</span></button>
147 </Link> 150 </Link>
148 </div> 151 </div>
149 </div> 152 </div>
150 <div> 153 <div>
151 <input type="text" placeholder='Search for map or a player...'/> 154 <input type="text" id='searchbar' placeholder='Search for map or a player...'/>
152 </div> 155 </div>
153 </div> 156 </div>
154 ) 157 )