diff options
Diffstat (limited to 'frontend/src/components/Sidebar.tsx')
| -rw-r--r-- | frontend/src/components/Sidebar.tsx | 321 |
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 @@ | |||
| 1 | import React from 'react'; | 1 | import React from "react"; |
| 2 | import { Link, useLocation } from 'react-router-dom'; | 2 | import { Link, useLocation } from 'react-router-dom'; |
| 3 | |||
| 4 | import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; | 3 | import { BookIcon, FlagIcon, HelpIcon, HomeIcon, LogoIcon, PortalIcon, SearchIcon, UploadIcon } from '@images/Images'; |
| 5 | import Login from '@components/Login'; | ||
| 6 | import { UserProfile } from '@customTypes/Profile'; | 4 | import { UserProfile } from '@customTypes/Profile'; |
| 7 | import { Search } from '@customTypes/Search'; | 5 | import sidebar from "@css/Sidebar.module.css"; |
| 6 | import { Button, Buttons } from "@customTypes/Sidebar"; | ||
| 7 | import btn from "@css/Button.module.css"; | ||
| 8 | import { abort } from "process"; | ||
| 9 | import Login from "@components/Login"; | ||
| 8 | import { API } from '@api/Api'; | 10 | import { API } from '@api/Api'; |
| 9 | import "@css/Sidebar.css"; | 11 | import inp from "@css/Input.module.css"; |
| 12 | import { Search } from '@customTypes/Search'; | ||
| 10 | 13 | ||
| 11 | interface SidebarProps { | 14 | interface 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 | ||
| 18 | const Sidebar: React.FC<SidebarProps> = ({ setToken, profile, setProfile, onUploadRun }) => { | 21 | const 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 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 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 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 Rules</span></button> | ||
| 166 | </Link> | ||
| 167 | |||
| 168 | <Link to="/about" tabIndex={-1}> | ||
| 169 | <button className='sidebar-button'><img src={HelpIcon} alt="about" /><span>About 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 | ||
| 202 | export default Sidebar; | 148 | export default Sidebar; |
| 149 | |||