import { ActionIcon, AppShell, Box, Burger, Center, createStyles, DEFAULT_THEME, Group, Header, Navbar, 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, setSidebarWidth } from "app/tree/slice" import { reloadTree } from "app/tree/thunks" import { reloadProfile, reloadSettings, reloadTags } from "app/user/thunks" 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 { useMobile } from "hooks/useMobile" import { useWebSocket } from "hooks/useWebSocket" import { LoadingPage } from "pages/LoadingPage" import { Resizable } from "re-resizable" import { type ReactNode, Suspense, useEffect } from "react" import { TbPlus } from "react-icons/tb" import { Outlet } from "react-router-dom" interface LayoutProps { sidebar: ReactNode sidebarWidth: number header: ReactNode } const sidebarPadding = DEFAULT_THEME.spacing.xs const sidebarRightBorderWidth = "1px" const useStyles = createStyles((theme, props: LayoutProps) => ({ sidebar: { "& .mantine-ScrollArea-scrollbar[data-orientation='horizontal']": { display: "none", }, }, sidebarContentResizeWrapper: { padding: sidebarPadding, minHeight: `calc(100vh - ${Constants.layout.headerHeight}px)`, }, sidebarContent: { maxWidth: `calc(${props.sidebarWidth}px - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { maxWidth: `calc(100vw - ${sidebarPadding} * 2 - ${sidebarRightBorderWidth})`, }, }, mainContentWrapper: { paddingTop: Constants.layout.headerHeight, paddingLeft: props.sidebarWidth, paddingRight: 0, paddingBottom: 0, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { paddingLeft: 0, }, }, mainContent: { maxWidth: `calc(100vw - ${props.sidebarWidth}px)`, padding: theme.spacing.md, [theme.fn.smallerThan(Constants.layout.mobileBreakpoint)]: { maxWidth: "100vw", padding: "6px", }, }, })) function LogoAndTitle() { const dispatch = useAppDispatch() return (
await dispatch(redirectToRootCategory())} style={{ cursor: "pointer" }}> CommaFeed
) } export default function Layout(props: LayoutProps) { const { classes } = useStyles(props) const theme = useMantineTheme() const { loading } = useAppLoading() const mobile = useMobile() const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const sidebarHidden = props.sidebarWidth === 0 const dispatch = useAppDispatch() useWebSocket() const handleResize = (element: HTMLElement) => dispatch(setSidebarWidth(element.offsetWidth)) 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 = (
dispatch(setMobileMenuOpen(!mobileMenuOpen))} size="sm" />
) const addButton = ( await dispatch(redirectToAdd())} aria-label="Subscribe"> ) if (loading) return return ( ) }