index.jsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. import styles from './styles.module.scss';
  2. // components
  3. import SidebarTrigger from '@ui/SidebarTrigger';
  4. import Headroom from 'react-headroom';
  5. import Logo from '@components/Logo';
  6. import AppBar from "@mui/material/AppBar";
  7. import {useSidebar} from "@contexts/sidebarContext";
  8. import {useThemeProvider} from "@contexts/themeContext";
  9. import {useEffect, useRef, useState} from 'react';
  10. import styled from 'styled-components/macro';
  11. import {Tab, Tabs} from "@mui/material";
  12. // import LINKS from "@constants/links";
  13. import Button from "@mui/material/Button";
  14. import {NavLink, useLocation, useNavigate} from "react-router-dom";
  15. import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
  16. import ToggleButton from "@mui/material/ToggleButton";
  17. import symbol from "@assets/symbol-defs.svg";
  18. import symbol_m from "@assets/symbol-mobile.svg";
  19. import {string} from "prop-types";
  20. import IconButton from "@mui/material/IconButton";
  21. import {animated,easings} from "@react-spring/web";
  22. import {useSpring} from "@react-spring/core";
  23. import {useWindowSize} from "react-use";
  24. import ResizeObserver from "resize-observer-polyfill";
  25. import {useSelector} from "react-redux";
  26. import {allTopNavs, getRoute, getRouteChain, getUserInfo, isLogin, isLogined} from "@features/mainSlice";
  27. import LazyImage from "@components/LazyImage";
  28. import {numFormatter, sleep} from "@utils/helpers";
  29. import {ROUTE_PATH} from "@constants/routes";
  30. import {toast} from "react-toastify";
  31. import LazyImgix from "@components/Imgix/LazyImgix";
  32. const i18n=global.i18n;
  33. const StyledToggleGroup= styled(ToggleButtonGroup)`
  34. border-radius: 0px !important;
  35. gap: 12px;
  36. `;
  37. const StyledToggle=styled(ToggleButton)`
  38. gap: 12px;
  39. text-transform:none !important;
  40. border: none !important;
  41. border-radius: var(--border-radius) !important;
  42. padding: 0px !important;
  43. &:hover, &:focus ,&.Mui-selected, &.active {
  44. background-color: var(--nav-item-hover) !important;
  45. a {
  46. color: var(--header);
  47. font-weight: 600;
  48. .icon {
  49. fill:${props => props.fill};
  50. }
  51. }
  52. }
  53. a {
  54. display: flex;
  55. align-items: center;
  56. color: var(--text);
  57. padding: 12px !important;
  58. transition: all var(--transition);
  59. font-family: var(--heading-font);
  60. gap: 8px;
  61. .icon {
  62. display: flex;
  63. align-items: center;
  64. justify-content: center;
  65. width: 18px;
  66. height: 18px;
  67. fill: var(--text);
  68. }
  69. }
  70. `;
  71. const DesktopNavToggle=()=>{
  72. const LINKS=useSelector(allTopNavs);
  73. const links=LINKS.slice(0,4);
  74. const {pathname} = useLocation();
  75. const [alignment, setAlignment] = useState(pathname);
  76. const currRoute=useSelector(getRoute(pathname));
  77. const getRootRoute=(pathname)=>{
  78. let route="";
  79. if (currRoute){
  80. let clv=currRoute.lv;
  81. if (clv==null)return route;
  82. for (let i = 0; i < LINKS.length; i++) {
  83. let link=LINKS[i];
  84. let lv=link.lv;
  85. if (clv.startsWith(lv)){
  86. route=link.path;
  87. break;
  88. }
  89. }
  90. }
  91. return route;
  92. }
  93. useEffect(() => {
  94. setAlignment(getRootRoute(pathname));
  95. }, [pathname]);
  96. const handleChange = (event, newAlignment) => {
  97. // setAlignment(newAlignment);
  98. };
  99. return (
  100. <StyledToggleGroup
  101. value={alignment}
  102. exclusive
  103. onChange={handleChange}
  104. >
  105. {
  106. links.map((link, index) => (
  107. <StyledToggle value={link.path} fill={link.fill.split(";").pop()} key={link.title}>
  108. <NavLink className="h4" to={link.path}>
  109. <svg className={"icon"}>
  110. <use xlinkHref={`${symbol}#${link.icon}`} />
  111. </svg>
  112. {i18n.t(link.title)}
  113. </NavLink>
  114. </StyledToggle>
  115. ))
  116. }
  117. </StyledToggleGroup>
  118. )
  119. }
  120. const DesktopSignInUp=()=>{
  121. const navigate=useNavigate();
  122. const isLogin=useSelector(isLogined);
  123. const {setIsShowLang} = useThemeProvider();
  124. return (
  125. <div className="d-flex align-items-center justify-content-end">
  126. {/*<IconButton aria-label="search">*/}
  127. {/* <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>*/}
  128. {/* <use xlinkHref={`${symbol}#icon_Search`} />*/}
  129. {/* </svg>*/}
  130. {/*</IconButton>*/}
  131. {
  132. !isLogin?(
  133. <>
  134. <Button className={`m-button ${styles.btn_sign_in}`} onClick={()=>{
  135. navigate(ROUTE_PATH.SIGN_IN);
  136. }}>{i18n.t('sign_in')}</Button>
  137. <Button className={`${styles.btn_sign_up}`} onClick={()=>{
  138. navigate(ROUTE_PATH.SIGN_UP);
  139. }}>{i18n.t('sign_up')}</Button>
  140. </>
  141. ):(
  142. <>
  143. </>
  144. )
  145. }
  146. {/*<Button className="m-button">*/}
  147. {/* <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>*/}
  148. {/* <use xlinkHref={`${symbol}#icon_Chat`} />*/}
  149. {/* </svg>*/}
  150. {/* <div className="chat-green-dont"></div>*/}
  151. {/*</Button>*/}
  152. <Button className="m-button" style={{marginLeft: '0.5rem',marginRight:'0.5rem'}} onClick={()=>{
  153. setIsShowLang(true);
  154. }}>
  155. <svg className={"ls-icon"} style={{width:'20px',height:'20px'}}>
  156. <use xlinkHref={`${symbol}#icon_Language`} />
  157. </svg>
  158. </Button>
  159. {
  160. isLogin?(<UserInfo />):(<></>)
  161. }
  162. </div>
  163. )
  164. }
  165. const DesktopNav = () => {
  166. const {open} = useSidebar();
  167. const navstyle=open?styles.nav_open:"";
  168. return (
  169. <div
  170. className={`d-flex align-items-center justify-content-between ${styles.header} ${navstyle}`}>
  171. <DesktopNavToggle />
  172. <DesktopSignInUp/>
  173. </div>
  174. );
  175. }
  176. const MobileSignInUp=()=>{
  177. const navigate=useNavigate();
  178. const isLogin=useSelector(isLogined);
  179. return (
  180. <div className="d-flex align-items-center justify-content-end">
  181. {
  182. !isLogin?(
  183. <>
  184. <Button className={`m-button ${styles.btn_sign_in} ${styles.btn_sign_in_mobile}`} onClick={()=>{
  185. navigate(ROUTE_PATH.SIGN_IN);
  186. }}>{i18n.t('sign_in')}</Button>
  187. <Button className={`${styles.btn_sign_up} ${styles.btn_sign_up_mobile}`} onClick={()=>{
  188. navigate(ROUTE_PATH.SIGN_UP);
  189. }}>{i18n.t('sign_up')}</Button>
  190. </>
  191. ):(
  192. <></>
  193. )
  194. }
  195. {/*<Button className="m-button">*/}
  196. {/* <svg className={"ls-icon"} style={{width:'1.4rem',height:'1.4rem'}}>*/}
  197. {/* <use xlinkHref={`${symbol}#icon_Language`} />*/}
  198. {/* </svg>*/}
  199. {/*</Button>*/}
  200. </div>
  201. );
  202. }
  203. const MobileNav=()=>{
  204. return (
  205. <div
  206. className={`d-flex align-items-center justify-content-between ${styles.header} ${styles.header_mobile}`}>
  207. <div className={`d-flex flex-row flex-1`} style={{maxWidth:"55%"}}>
  208. <SidebarTrigger/>
  209. <Logo size="sm"/>
  210. </div>
  211. <MobileSignInUp/>
  212. <UserInfo />
  213. </div>
  214. )
  215. }
  216. const UserInfo=()=>{
  217. const isLogin=useSelector(isLogined);
  218. const user=useSelector(getUserInfo);
  219. const navigate=useNavigate();
  220. return (
  221. <>
  222. {
  223. isLogin?(
  224. <div className={`d-flex align-items-center ${styles.user_info}`} onClick={()=>{
  225. navigate(ROUTE_PATH.SETTINGS);
  226. }}>
  227. <div className={styles.info}>
  228. <div className={styles.nick}>{user.NickName}</div>
  229. <div className={styles.score}>{global.DOLLAR+" "+numFormatter(user.Score)}</div>
  230. </div>
  231. {/*<LazyImage wrapperClassName={styles.avatar} src={user.img} alt={"Head"}/>*/}
  232. <LazyImgix className={styles.avatar} src={user.img} alt={"Head"} width={80}/>
  233. </div>
  234. ):(<></>)
  235. }
  236. </>
  237. )
  238. }
  239. const PageHeader = () => {
  240. const LINKS=useSelector(allTopNavs);
  241. const {initHeaderHeight,installOpen} = useThemeProvider();
  242. const {pathname} = useLocation();
  243. const navigate=useNavigate();
  244. const barRef = useRef(null);
  245. const currRoute=useSelector(getRoute(pathname));
  246. const chain=useSelector(getRouteChain(pathname));
  247. const {isMobile,showHeader,setShowHeader} = useThemeProvider();
  248. const [showMHeader, setShowMHeader] = useState(false);
  249. const isLogin=useSelector(isLogined);
  250. let lastscroll=0;
  251. const {open} = useSidebar();
  252. const navstyle=open?styles.nav_open:"";
  253. const checkNavActive=(lv)=>{
  254. if (currRoute&&currRoute.lv)return currRoute.lv.startsWith(lv);
  255. else return false;
  256. }
  257. const getHeaderHeight = () => {
  258. // console.log(document.querySelector('div.progressier-install-banner'))
  259. const header = document.querySelector('#appbar');
  260. let offset = 0;
  261. if (barRef){
  262. const headerRoom=document.querySelector('#headerRoom');
  263. if (headerRoom){
  264. if (!barRef.showHeader){
  265. offset=headerRoom.clientHeight;
  266. const hstyle = window.getComputedStyle(headerRoom);
  267. if (hstyle!=null){
  268. let paddingBottom=hstyle.paddingBottom;
  269. paddingBottom=Number(paddingBottom.replace("px",""));
  270. offset=offset-paddingBottom;
  271. }
  272. }
  273. }
  274. }
  275. if (header!=null)return `${(header.offsetHeight-offset) || 0}px`
  276. else return '0px';
  277. }
  278. const handleScroll=()=>{
  279. if (!barRef.showHeader)return;
  280. let scrollTop = document.documentElement.scrollTop; //滚动条滚动高度
  281. let dt=scrollTop-lastscroll;
  282. lastscroll=scrollTop;
  283. let show=false;
  284. if (dt>20){
  285. show=false;
  286. setShowMHeader(show);
  287. }
  288. else if (dt<-20){
  289. show=true;
  290. setShowMHeader(show);
  291. }
  292. if (scrollTop<20){
  293. setShowMHeader(true);
  294. }
  295. }
  296. const changeHeaderHeight=()=>{
  297. document.documentElement.style.setProperty('--header-active', getHeaderHeight());
  298. initHeaderHeight(getHeaderHeight());
  299. }
  300. const resizeObserver = new ResizeObserver(entries => {
  301. for (let entry of entries) {
  302. // 获取目标元素的高度
  303. const { height } = entry.contentRect;
  304. // 可在这里处理高度变化的逻辑
  305. changeHeaderHeight();
  306. }
  307. });
  308. const getProgressierInstallBanner=(params,callback)=>{
  309. let banner=new window.NewProgressierInstallBanner(
  310. params,
  311. callback
  312. )
  313. banner.remove = async ()=>{
  314. if (banner.deny && !banner.data.preview) {
  315. banner.deny()
  316. }
  317. banner.element.classList.remove("in");
  318. // await progressier.utils.wait(1e3);
  319. banner.element.remove()
  320. }
  321. banner.make=()=>{
  322. document.querySelectorAll("." + banner.className).forEach(function(e) {
  323. e.remove()
  324. });
  325. let installBanner=document.querySelector("#installBanner");
  326. if (installBanner){
  327. banner.element = window.progressier.utils.node("div", banner.className, {
  328. parent: installBanner
  329. });
  330. banner.element.classList.add(styles.install_banner);
  331. }
  332. }
  333. window.install_banner=banner;
  334. return banner;
  335. }
  336. // const reProgressierInstallBanner=()=>{
  337. // if (window.NewProgressierInstallBanner!=null)return;
  338. // let ProgressierInstallBanner=window.ProgressierInstallBanner;
  339. // window.NewProgressierInstallBanner=ProgressierInstallBanner;
  340. // window.ProgressierInstallBanner=function (e, t) {
  341. // this.remove = async function() {
  342. //
  343. // };
  344. // this.render = async function() {
  345. //
  346. // };
  347. // this.make = function() {
  348. //
  349. // };
  350. // this.init = async function() {
  351. //
  352. // }
  353. // }
  354. // }
  355. const checkInstall=(l)=>{
  356. if (!installOpen){
  357. return false;
  358. }
  359. if (l.parent.detection.isStandalone()) {
  360. return false;
  361. }
  362. return true;
  363. }
  364. const startListening=async ()=>{
  365. if (window.progressier==null)return ;
  366. let l=window.progressier.banners;
  367. await l.parent.data.waitForData();
  368. l.data = l.parent.data.params;
  369. if (!l.reinstallTimer) {
  370. l.reinstallTimer = setInterval(function() {
  371. // window.alert(window.progressier["native"].justInstalled)
  372. // if (!l.showInstall()) {
  373. // return
  374. // }
  375. if (!checkInstall(l)){
  376. clearInterval(l.reinstallTimer)
  377. l.reinstallTimer=null;
  378. return;
  379. }
  380. // window.alert("222")
  381. // if (l.showed) {
  382. // clearInterval(l.reinstallTimer)
  383. // l.reinstallTimer=null;
  384. // return
  385. // }
  386. // window.alert("3333")
  387. l.showed = true;
  388. // new window.NewProgressierInstallBanner({
  389. // icon: l.icon(),
  390. // color: l.data.buttonColor,
  391. // btnColor: l.data.buttonTextColor,
  392. // text: l.data.installBannerPitch
  393. // },function() {
  394. // l.deny(l.installCookie)
  395. // }
  396. // ).init();
  397. let InstallBanner=getProgressierInstallBanner(
  398. {
  399. icon: l.icon(),
  400. color: l.data.buttonColor,
  401. btnColor: l.data.buttonTextColor,
  402. text: l.data.installBannerPitch
  403. },()=>{
  404. l.deny(l.installCookie)
  405. }
  406. ).init();
  407. clearInterval(l.reinstallTimer)
  408. }, l.interval)
  409. }
  410. else
  411. {
  412. clearInterval(l.reinstallTimer)
  413. l.reinstallTimer=null;
  414. }
  415. }
  416. const checkProgressier=async ()=>{
  417. // return ;
  418. if (window.progressier){
  419. // reProgressierInstallBanner();
  420. // let l=window.progressier.banners;
  421. // await l.parent.data.waitForData();
  422. // await checkWaitForData();
  423. // let data=l.data;
  424. // if (data){
  425. // if (window.NewProgressierInstallBanner){
  426. // let InstallBanner=getProgressierInstallBanner(
  427. // {
  428. // icon: l.icon(),
  429. // color: l.data.buttonColor,
  430. // btnColor: l.data.buttonTextColor,
  431. // text: l.data.installBannerPitch
  432. // }
  433. // ).init();
  434. // }
  435. // }
  436. startListening();
  437. }
  438. }
  439. const stopListener=()=>{
  440. if (window.progressier==null)return;
  441. let l=window.progressier.banners;
  442. if(l){
  443. if (l.reinstallTimer!=null){
  444. clearInterval(l.reinstallTimer);
  445. l.reinstallTimer=null;
  446. }
  447. }
  448. }
  449. const checkInstallOpen=()=>{
  450. if (installOpen){
  451. checkProgressier();
  452. }
  453. else
  454. {
  455. if (window.install_banner){
  456. window.install_banner.remove();
  457. }
  458. }
  459. }
  460. useEffect(() => {
  461. // 这里的代码会在组件挂载时执行
  462. // console.log('Component mounted');
  463. window.addEventListener('scroll', handleScroll);
  464. if (barRef.current) {
  465. resizeObserver.observe(barRef.current);
  466. }
  467. // testBefore();
  468. // checkProgressier();
  469. return () => {
  470. // 可选的清除函数,当组件卸载时执行
  471. // console.log('Component unmounted');
  472. window.removeEventListener('scroll', handleScroll);
  473. if (barRef.current) {
  474. resizeObserver.unobserve(barRef.current);
  475. }
  476. // stopListener();
  477. };
  478. }, []);
  479. // }, [pathname]);
  480. useEffect(()=>{
  481. setShowMHeader(showHeader);
  482. barRef.showHeader=showHeader;
  483. changeHeaderHeight();
  484. },[showHeader]);
  485. useEffect(()=>{
  486. let len=chain.length;
  487. let isShowH=len<=1;
  488. if (LINKS===null||LINKS.length<=1){
  489. isShowH=false;
  490. }
  491. setShowHeader(isShowH);
  492. },[pathname]);
  493. useEffect(()=>{
  494. checkInstallOpen();
  495. return ()=>{
  496. stopListener();
  497. }
  498. },[installOpen])
  499. const headerAni=useSpring({
  500. from: {transform: 'translateY(-100%) translateZ(0px)'},
  501. transform:showMHeader?'translateY(0%) translateZ(0px)':'translateY(-100%) translateZ(0px)',
  502. config: {
  503. easing: easings.easeOutBack,
  504. duration:300
  505. },
  506. });
  507. const changePath=(path)=>{
  508. navigate(path);
  509. }
  510. const renderHeaderRoom=()=>{
  511. const active=styles.active;
  512. return (
  513. <animated.div id={'headerRoom'} className={`${styles.header_room}`} style={headerAni}>
  514. <div className={`${styles['mobile-top-navigate-list']}`}>
  515. <button className={`${styles['mb-top-navigate-item']} ${pathname==='/'?active:''}`} onClick={()=>{
  516. changePath('/');
  517. }}>
  518. <svg className={"icon"}>
  519. <use xlinkHref={`${symbol_m}#icon_Home`} />
  520. </svg>
  521. <span>{i18n.t("links.home")}</span>
  522. </button>
  523. {
  524. LINKS.map((link, index) => (
  525. <button key={index} className={`${styles['mb-top-navigate-item']} ${checkNavActive(link.lv)?active:''}`} onClick={()=>{
  526. changePath(link.path);
  527. }}>
  528. <svg className={"icon"}>
  529. <use xlinkHref={`${symbol_m}#${link.icon}`} />
  530. </svg>
  531. <span>{i18n.t(link.title)}</span>
  532. </button>
  533. ))
  534. }
  535. </div>
  536. </animated.div>
  537. );
  538. }
  539. const renderMobileInstall=()=>{
  540. return (
  541. <div id={'installBanner'} className={styles.header_install}></div>
  542. )
  543. }
  544. const renderMobile=()=>{
  545. return (
  546. <>
  547. {renderMobileInstall()}
  548. <MobileNav />
  549. {renderHeaderRoom()}
  550. </>
  551. )
  552. }
  553. const renderDesktopInstall=()=>{
  554. return (
  555. <div id={'installBanner'} className={`${styles.header_install} ${styles.header_install_desk} ${navstyle}`}></div>
  556. )
  557. }
  558. const renderDesktop=()=>{
  559. return (
  560. <>
  561. {renderDesktopInstall()}
  562. <DesktopNav/>
  563. </>
  564. );
  565. }
  566. return (
  567. <div id={'appbar'} ref={barRef} className={`${styles.appbar} ${isMobile()?styles.mobile:''} ${isMobile()&&!showHeader?styles.hide_header:''}`}>
  568. {
  569. isMobile()?renderMobile():renderDesktop()
  570. }
  571. </div>
  572. )
  573. }
  574. export default PageHeader