aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/components/Sidebar.tsx
diff options
context:
space:
mode:
authorWolfboy248 <georgejvindkarlsen@gmail.com>2024-11-25 09:20:01 +0100
committerWolfboy248 <georgejvindkarlsen@gmail.com>2024-11-25 09:20:01 +0100
commit207a2540101b2f216bde94ae53286d2e52f044e3 (patch)
tree0d36c7d3ea8b87f654aa47384d16f1f8f96a0157 /frontend/src/components/Sidebar.tsx
parentfeat/rankings: optimize Steam ID comparison (#236) (diff)
downloadlphub-207a2540101b2f216bde94ae53286d2e52f044e3.tar.gz
lphub-207a2540101b2f216bde94ae53286d2e52f044e3.tar.bz2
lphub-207a2540101b2f216bde94ae53286d2e52f044e3.zip
frontend: begin port to css modules, sidebar refactor
Diffstat (limited to 'frontend/src/components/Sidebar.tsx')
-rw-r--r--frontend/src/components/Sidebar.tsx321
1 files changed, 134 insertions, 187 deletions
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 67f7f3d..beff4f0 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1,12 +1,15 @@
1import React from 'react'; 1import React from "react";
2import { Link, useLocation } from 'react-router-dom'; 2import { Link, useLocation } from 'react-router-dom';
3
4import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; 3import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images';
5import Login from '@components/Login';
6import { UserProfile } from '@customTypes/Profile'; 4import { UserProfile } from '@customTypes/Profile';
7import { Search } from '@customTypes/Search'; 5import sidebar from "@css/Sidebar.module.css";
6import { Button, Buttons } from "@customTypes/Sidebar";
7import btn from "@css/Button.module.css";
8import { abort } from "process";
9import Login from "@components/Login";
8import { API } from '@api/Api'; 10import { API } from '@api/Api';
9import "@css/Sidebar.css"; 11import inp from "@css/Input.module.css";
12import { Search } from '@customTypes/Search';
10 13
11interface SidebarProps { 14interface SidebarProps {
12 setToken: React.Dispatch<React.SetStateAction<string | undefined>>; 15 setToken: React.Dispatch<React.SetStateAction<string | undefined>>;
@@ -16,187 +19,131 @@ interface SidebarProps {
16}; 19};
17 20
18const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => { 21const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => {
19 22 const location = useLocation();
20 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined); 23 const [load, setLoad] = React.useState<boolean>(false);
21 const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false); 24 const [searchData, setSearchData] = React.useState<Search | undefined>(undefined);
22 const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(true); 25 const [hasClickedSearch, setHasClickedSearch] = React.useState<boolean>(false);
23 26 const [isSearching, setIsSearching] = React.useState<boolean>(false);
24 const location = useLocation(); 27 const [buttonsList, setButtonsList] = React.useState<Buttons>({
25 const path = location.pathname; 28 top: [
26 29 {img: HomeIcon, text: "Home", url: "/"},
27 const handle_sidebar_click = (clicked_sidebar_idx: number) => { 30 {img: PortalIcon, text: "Games", url: "/games"},
28 const btn = document.querySelectorAll("button.sidebar-button"); 31 {img: FlagIcon, text: "Rankings", url: "/rankings"}
29 if (isSidebarOpen) { setSidebarOpen(false); _handle_sidebar_hide() } 32 ],
30 // clusterfuck 33 bottom: [
31 btn.forEach((e, i) => { 34 {img: BookIcon, text: "Rules", url: "/rules"},
32 btn[i].classList.remove("sidebar-button-selected") 35 {img: HelpIcon, text: "About LPHUB", url: "/about"}
33 btn[i].classList.add("sidebar-button-deselected") 36 ]
34 }) 37 });
35 btn[clicked_sidebar_idx].classList.add("sidebar-button-selected") 38
36 btn[clicked_sidebar_idx].classList.remove("sidebar-button-deselected") 39 const _handle_search = () => {
37 }; 40 if (!hasClickedSearch) {
38 41 _handle_search_change("");
39 const _handle_sidebar_hide = () => { 42 }
40 var btn = document.querySelectorAll("button.sidebar-button") as NodeListOf<HTMLElement> 43 setHasClickedSearch(true);
41 const span = document.querySelectorAll("button.sidebar-button>span") as NodeListOf<HTMLElement> 44 setIsSearching(!isSearching);
42 const side = document.querySelector("#sidebar-list") as HTMLElement; 45 document.querySelector<HTMLInputElement>("#searchInput")!.focus();
43 const searchbar = document.querySelector("#searchbar") as HTMLInputElement; 46 }
44 const uploadRunBtn = document.querySelector("#upload-run") as HTMLInputElement; 47
45 const uploadRunSpan = document.querySelector("#upload-run>span") as HTMLInputElement; 48 const _handle_search_change = async (query: string) => {
46 49 const response = await API.get_search(query);
47 if (isSidebarOpen) { 50 setSearchData(response);
48 if (profile) { 51 }
49 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 52
50 login.style.opacity = "1" 53 const _get_index_load = () => {
51 uploadRunBtn.style.width = "310px" 54 const pathname = window.location.pathname;
52 uploadRunBtn.style.padding = "0.4em 0 0 11px" 55 const btnObj = buttonsList.top.find(obj => obj.url === pathname);
53 uploadRunSpan.style.opacity = "0" 56 let btnIndex = buttonsList.top.findIndex(obj => obj.url === pathname);
54 setTimeout(() => { 57 if (btnIndex != -1) {
55 uploadRunSpan.style.opacity = "1" 58 return btnIndex;
56 }, 100) 59 } else if (buttonsList.top.findIndex(obj => obj.url === pathname) == -1 && buttonsList.bottom.findIndex(obj => obj.url === pathname) != -1) {
57 } 60 btnIndex = buttonsList.bottom.findIndex(obj => obj.url === pathname);
58 setSidebarOpen(false); 61 return btnIndex + buttonsList.top.length + 1;
59 side.style.width = "320px" 62 } else if (load) {
60 btn.forEach((e, i) => { 63 return currentBtn;
61 e.style.width = "310px" 64 } else {
62 e.style.padding = "0.4em 0 0 11px" 65 return 0;
63 setTimeout(() => { 66 }
64 span[i].style.opacity = "1" 67 }
65 }, 100) 68 const [currentBtn, setCurrentBtn] = React.useState<number>(_get_index_load);
66 }); 69
67 side.style.zIndex = "2" 70 React.useEffect(() => {
68 } else { 71 setCurrentBtn(_get_index_load);
69 if (profile) { 72 setLoad(true);
70 const login = document.querySelectorAll(".login>button")[1] as HTMLElement; 73 }, [location])
71 login.style.opacity = "0" 74
72 uploadRunBtn.style.width = "40px" 75 return (
73 uploadRunBtn.style.padding = "0.4em 0 0 5px" 76 <section className={sidebar.sidebar}>
74 uploadRunSpan.style.opacity = "0" 77 <div className={sidebar.logo}>
75 } 78 <Link onClick={isSearching ? _handle_search : () => {}} to={"/"}>
76 setSidebarOpen(true); 79 <img src={LogoIcon}/>
77 side.style.width = "40px"; 80 <div>
78 searchbar.focus(); 81 <span className={sidebar.logoTitle}><b>PORTAL 2</b></span>
79 btn.forEach((e, i) => { 82 <span>Least Portals Hub</span>
80 e.style.width = "40px" 83 </div>
81 e.style.padding = "0.4em 0 0 5px" 84 </Link>
82 span[i].style.opacity = "0" 85 </div>
83 }) 86
84 setTimeout(() => { 87 <div className={sidebar.btnsContainer} style={{height: "calc(100% - 104px)"}}>
85 side.style.zIndex = "0" 88 <div className={`${sidebar.btns} ${isSearching ? sidebar.min : ""}`}>
86 }, 300); 89 <div className={sidebar.topBtns}>
87 } 90 <button onClick={_handle_search} className={`${btn.sidebar} ${isSearching ? btn.min : ""}`}>
88 }; 91 <img src={SearchIcon}/>
89 92 <span>Search</span>
90 const _handle_sidebar_lock = () => { 93 </button>
91 if (!isSidebarLocked) { 94
92 _handle_sidebar_hide() 95 <span></span>
93 setIsSidebarLocked(true); 96
94 setTimeout(() => setIsSidebarLocked(false), 300); 97 {buttonsList.top.map((e: any, i: any) => {
95 } 98 return <Link to={e.url}><button onClick={isSearching ? _handle_search : () => {}} className={`${btn.sidebar} ${currentBtn == i ? btn.selected : ""} ${isSearching ? btn.min : ""}`} key={i}>
96 }; 99 <img src={e.img}/>
97 100 <span>{e.text}</span>
98 const _handle_search_change = async (q: string) => { 101 </button></Link>
99 const searchResponse = await API.get_search(q); 102 })
100 setSearchData(searchResponse); 103
101 }; 104 }
102 105 </div>
103 React.useEffect(() => { 106 <div className={sidebar.bottomBtns}>
104 if (path === "/") { handle_sidebar_click(1) } 107 <Login isSearching={isSearching} setCurrentBtn={setCurrentBtn} currentBtn={currentBtn} buttonsList={buttonsList} setToken={setToken} profile={profile} setProfile={setProfile}/>
105 else if (path.includes("games")) { handle_sidebar_click(2) } 108
106 else if (path.includes("rankings")) { handle_sidebar_click(3) } 109 {buttonsList.bottom.map((e: any, i: any) => {
107 // else if (path.includes("news")) { handle_sidebar_click(4) } 110 return <Link to={e.url}><button onClick={isSearching ? _handle_search : () => {}} key={i} className={`${btn.sidebar} ${currentBtn == i + buttonsList.top.length + 1 ? btn.selected : ""} ${isSearching ? btn.min : ""}`}>
108 // else if (path.includes("scorelog")) { handle_sidebar_click(5) } 111 <img src={e.img}/>
109 else if (path.includes("profile")) { handle_sidebar_click(4) } 112 <span>{e.text}</span>
110 else if (path.includes("rules")) { handle_sidebar_click(5) } 113 </button></Link>
111 else if (path.includes("about")) { handle_sidebar_click(6) } 114 })
112 }, [path]); 115
113 116 }
114 return ( 117 </div>
115 <div id='sidebar'> 118 </div>
116 <Link to="/" tabIndex={-1}> 119
117 <div id='logo'> {/* logo */} 120 <div className={`${sidebar.searchContainer} ${isSearching ? sidebar.min : ""}`}>
118 <img src={LogoIcon} alt="" height={"80px"} /> 121 <div className={sidebar.inpContainer}>
119 <div id='logo-text'> 122 <input onChange={(e) => {_handle_search_change(e.target.value)}} id="searchInput" className={inp.sidebar} type="text" placeholder='Search for map or a player...'/>
120 <span><b>PORTAL 2</b></span><br /> 123 </div>
121 <span>Least Portals Hub</span> 124
122 </div> 125 <div className={sidebar.searchResults}>
123 </div> 126 {searchData?.maps.map((map, i) => {
124 </Link> 127 return <Link style={{animationDelay: `${i < 30 ? i * 0.05 : 0}s`}} className={sidebar.result} to={`/maps/${map.id}`} key={i}>
125 <div id='sidebar-list'> {/* List */} 128 <span>{map.game}</span>
126 <div id='sidebar-toplist'> {/* Top */} 129 <span>{map.chapter}</span>
127 130 <span>{map.map}</span>
128 <button className='sidebar-button' onClick={() => _handle_sidebar_lock()}><img src={SearchIcon} alt="" /><span>Search</span></button> 131 </Link>
129 132 })}
130 <span></span> 133
131 134 {searchData?.players.map((player, i) => {
132 <Link to="/" tabIndex={-1}> 135 return <Link className={`${sidebar.result} ${sidebar.player}`} to={`/users/${player.steam_id}`}>
133 <button className='sidebar-button'><img src={HomeIcon} alt="homepage" /><span>Home&nbsp;Page</span></button> 136 <img src={player.avatar_link}/>
134 </Link> 137 <span>{player.user_name}</span>
135 138 </Link>
136 <Link to="/games" tabIndex={-1}> 139 })}
137 <button className='sidebar-button'><img src={PortalIcon} alt="games" /><span>Games</span></button> 140 </div>
138 </Link> 141 </div>
139 142
140 <Link to="/rankings" tabIndex={-1}> 143 </div>
141 <button className='sidebar-button'><img src={FlagIcon} alt="rankings" /><span>Rankings</span></button> 144 </section>
142 </Link> 145 )
143 146}
144 {/* <Link to="/news" tabIndex={-1}>
145 <button className='sidebar-button'><img src={NewsIcon} alt="news" /><span>News</span></button>
146 </Link> */}
147
148 {/* <Link to="/scorelog" tabIndex={-1}>
149 <button className='sidebar-button'><img src={TableIcon} alt="scorelogs" /><span>Score&nbsp;Logs</span></button>
150 </Link> */}
151 </div>
152 <div id='sidebar-bottomlist'>
153 <span></span>
154
155 {
156 profile && profile.profile ?
157 <button id='upload-run' className='submit-run-button' onClick={() => onUploadRun()}><img src={UploadIcon} alt="upload" /><span>Upload&nbsp;Record</span></button>
158 :
159 <span></span>
160 }
161
162 <Login setToken={setToken} profile={profile} setProfile={setProfile} />
163
164 <Link to="/rules" tabIndex={-1}>
165 <button className='sidebar-button'><img src={BookIcon} alt="rules" /><span>Leaderboard&nbsp;Rules</span></button>
166 </Link>
167
168 <Link to="/about" tabIndex={-1}>
169 <button className='sidebar-button'><img src={HelpIcon} alt="about" /><span>About&nbsp;LPHUB</span></button>
170 </Link>
171 </div>
172 </div>
173 <div>
174 <input type="text" id='searchbar' placeholder='Search for map or a player...' onChange={(e) => _handle_search_change(e.target.value)} />
175
176 <div id='search-data'>
177
178 {searchData?.maps.map((q, index) => (
179 <Link to={`/maps/${q.id}`} className='search-map' key={index}>
180 <span>{q.game}</span>
181 <span>{q.chapter}</span>
182 <span>{q.map}</span>
183 </Link>
184 ))}
185 {searchData?.players.map((q, index) =>
186 (
187 <Link to={
188 profile && q.steam_id === profile.steam_id ? `/profile` :
189 `/users/${q.steam_id}`
190 } className='search-player' key={index}>
191 <img src={q.avatar_link} alt='pfp'></img>
192 <span style={{ fontSize: `${36 - q.user_name.length * 0.8}px` }}>{q.user_name}</span>
193 </Link>
194 ))}
195
196 </div>
197 </div>
198 </div>
199 );
200};
201 147
202export default Sidebar; 148export default Sidebar;
149