import { msg } from "@lingui/macro" import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core" import { Constants } from "app/constants" import { redirectToAdd, redirectToRootCategory } from "app/redirect/thunks" import { useAppDispatch, useAppSelector } from "app/store" import { setMobileMenuOpen } from "app/tree/slice" import { reloadTree } from "app/tree/thunks" import { setSidebarWidth } from "app/user/slice" import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks" import { ActionButton } from "components/ActionButton" import { AnnouncementDialog } from "components/AnnouncementDialog" import { Loader } from "components/Loader" import { Logo } from "components/Logo" import { OnDesktop } from "components/responsive/OnDesktop" import { OnMobile } from "components/responsive/OnMobile" import { useAppLoading } from "hooks/useAppLoading" import { useBrowserExtension } from "hooks/useBrowserExtension" import { useMobile } from "hooks/useMobile" import { useWebSocket } from "hooks/useWebSocket" import { LoadingPage } from "pages/LoadingPage" import { type ReactNode, Suspense, useEffect, useRef } from "react" import Draggable from "react-draggable" import { TbMenu2, TbPlus, TbX } from "react-icons/tb" import { Outlet } from "react-router-dom" import { useSwipeable } from "react-swipeable" import { tss } from "tss" interface LayoutProps { sidebar: ReactNode sidebarVisible: boolean header: ReactNode } function LogoAndTitle() { const dispatch = useAppDispatch() return (
await dispatch(redirectToRootCategory())} style={{ cursor: "pointer" }}> CommaFeed
) } const useStyles = tss .withParams<{ sidebarWidth: number sidebarPadding: string sidebarRightBorderWidth: string }>() .create(({ sidebarWidth, sidebarPadding, sidebarRightBorderWidth }) => { return { sidebarContent: { maxWidth: `calc(${sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, [`@media (max-width: ${Constants.layout.mobileBreakpoint}px)`]: { maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, }, }, } }) export default function Layout(props: LayoutProps) { const theme = useMantineTheme() const mobile = useMobile() const { isBrowserExtensionPopup } = useBrowserExtension() const draggableSeparator = useRef(null) const { loading } = useAppLoading() const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const mobileFooter = useAppSelector(state => state.user.settings?.mobileFooter) const sidebarWidth = useAppSelector(state => state.user.localSettings.sidebarWidth) const headerInFooter = mobile && !isBrowserExtensionPopup && mobileFooter const dispatch = useAppDispatch() useWebSocket() const sidebarPadding = theme.spacing.xs const { classes } = useStyles({ sidebarWidth, sidebarPadding, sidebarRightBorderWidth: "1px", }) useEffect(() => { // load initial data dispatch(reloadSettings()) dispatch(reloadProfile()) dispatch(reloadTree()) dispatch(reloadTags()) }, [dispatch]) useEffect(() => { let timer: number | undefined if (!webSocketConnected && treeReloadInterval) { // reload tree periodically if not receiving websocket events timer = window.setInterval(async () => await dispatch(reloadTree()), treeReloadInterval) } return () => clearInterval(timer) }, [dispatch, webSocketConnected, treeReloadInterval]) const burger = ( : } onClick={() => dispatch(setMobileMenuOpen(!mobileMenuOpen))} /> ) const addButton = ( await dispatch(redirectToAdd())} aria-label="Subscribe" > ) const header = ( <> {mobileMenuOpen && ( {burger} {addButton} )} {!mobileMenuOpen && ( {burger} {props.header} )} {addButton} {props.header} ) const swipeHandlers = useSwipeable({ onSwiping: e => { const threshold = document.documentElement.clientWidth / 6 if (e.absX > threshold) { dispatch(setMobileMenuOpen(e.dir === "Right")) } }, }) if (loading) return return ( {!headerInFooter && header} {headerInFooter && header} {props.sidebar} { dispatch(setSidebarWidth(data.x)) return }} > }> ) }