move handlers in Layout because they actually only apply to the reader pages

This commit is contained in:
Athou
2026-05-02 08:14:41 +02:00
parent 4b03fce85d
commit 9f7f87b4ae
2 changed files with 142 additions and 143 deletions

View File

@@ -4,21 +4,16 @@ import { MantineProvider, v8CssVariablesResolver } from "@mantine/core"
import { ModalsProvider } from "@mantine/modals" import { ModalsProvider } from "@mantine/modals"
import { Notifications } from "@mantine/notifications" import { Notifications } from "@mantine/notifications"
import type React from "react" import type React from "react"
import { useEffect, useState } from "react" import { useEffect } from "react"
import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom" import { HashRouter, Navigate, Route, Routes, useNavigate } from "react-router-dom"
import Tinycon from "tinycon"
import { Constants } from "@/app/constants" import { Constants } from "@/app/constants"
import { redirectTo } from "@/app/redirect/slice" import { redirectTo } from "@/app/redirect/slice"
import { redirectToInitialSetup } from "@/app/redirect/thunks" import { redirectToInitialSetup } from "@/app/redirect/thunks"
import { reloadServerInfos } from "@/app/server/thunks" import { reloadServerInfos } from "@/app/server/thunks"
import { useAppDispatch, useAppSelector } from "@/app/store" import { useAppDispatch, useAppSelector } from "@/app/store"
import { categoryUnreadCount } from "@/app/utils"
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
import { ErrorBoundary } from "@/components/ErrorBoundary" import { ErrorBoundary } from "@/components/ErrorBoundary"
import { Header } from "@/components/header/Header" import { Header } from "@/components/header/Header"
import { Tree } from "@/components/sidebar/Tree" import { Tree } from "@/components/sidebar/Tree"
import { useAppLoading } from "@/hooks/useAppLoading"
import { useBrowserExtension } from "@/hooks/useBrowserExtension"
import { useI18n } from "@/i18n" import { useI18n } from "@/i18n"
import { AdminUsersPage } from "@/pages/admin/AdminUsersPage" import { AdminUsersPage } from "@/pages/admin/AdminUsersPage"
import { MetricsPage } from "@/pages/admin/MetricsPage" import { MetricsPage } from "@/pages/admin/MetricsPage"
@@ -146,83 +141,8 @@ function RedirectHandler() {
return null return null
} }
function UnreadCountTitleHandler({
enabled,
}: Readonly<{
enabled?: boolean
}>) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
}
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
useEffect(() => {
if (enabled && unreadCount > 0) {
Tinycon.setBubble(unreadCount)
} else {
Tinycon.reset()
}
}, [unreadCount, enabled])
return null
}
function BrowserExtensionBadgeUnreadCountHandler() {
const root = useAppSelector(state => state.tree.rootCategory)
const { setBadgeUnreadCount } = useBrowserExtension()
useEffect(() => {
if (!root) return
const unreadCount = categoryUnreadCount(root)
setBadgeUnreadCount(unreadCount)
}, [root, setBadgeUnreadCount])
return null
}
function CustomJsHandler() {
const [scriptLoaded, setScriptLoaded] = useState(false)
const { loading } = useAppLoading()
useEffect(() => {
if (scriptLoaded || loading) {
return
}
const script = document.createElement("script")
script.src = "custom_js.js"
script.async = true
document.body.appendChild(script)
setScriptLoaded(true)
return () => script.remove()
}, [scriptLoaded, loading])
return null
}
function CustomCssHandler() {
useEffect(() => {
const link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = "custom_css.css"
document.head.appendChild(link)
return () => link.remove()
}, [])
return null
}
export function App() { export function App() {
useI18n() useI18n()
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useEffect(() => { useEffect(() => {
@@ -231,13 +151,6 @@ export function App() {
return ( return (
<Providers> <Providers>
<UnreadCountTitleHandler enabled={unreadCountTitle} />
<UnreadCountFaviconHandler enabled={unreadCountFavicon} />
<BrowserExtensionBadgeUnreadCountHandler />
<CustomJsHandler />
<CustomCssHandler />
<DisablePullToRefresh enabled={disablePullToRefresh} />
<HashRouter> <HashRouter>
<InitialSetupHandler /> <InitialSetupHandler />
<RedirectHandler /> <RedirectHandler />

View File

@@ -1,10 +1,11 @@
import { msg } from "@lingui/core/macro" import { msg } from "@lingui/core/macro"
import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core" import { ActionIcon, AppShell, Box, Center, Group, ScrollArea, Title, useMantineTheme } from "@mantine/core"
import { type ReactNode, type RefObject, Suspense, useEffect, useRef } from "react" import { type ReactNode, type RefObject, Suspense, useEffect, useRef, useState } from "react"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import { TbMenu2, TbPlus, TbX } from "react-icons/tb" import { TbMenu2, TbPlus, TbX } from "react-icons/tb"
import { Outlet } from "react-router-dom" import { Outlet } from "react-router-dom"
import { useSwipeable } from "react-swipeable" import { useSwipeable } from "react-swipeable"
import Tinycon from "tinycon"
import { Constants } from "@/app/constants" import { Constants } from "@/app/constants"
import { redirectToAdd, redirectToRootCategory } from "@/app/redirect/thunks" import { redirectToAdd, redirectToRootCategory } from "@/app/redirect/thunks"
import { useAppDispatch, useAppSelector } from "@/app/store" import { useAppDispatch, useAppSelector } from "@/app/store"
@@ -12,8 +13,10 @@ import { setMobileMenuOpen } from "@/app/tree/slice"
import { reloadTree } from "@/app/tree/thunks" import { reloadTree } from "@/app/tree/thunks"
import { setSidebarWidth } from "@/app/user/slice" import { setSidebarWidth } from "@/app/user/slice"
import { reloadProfile, reloadSettings, reloadTags } from "@/app/user/thunks" import { reloadProfile, reloadSettings, reloadTags } from "@/app/user/thunks"
import { categoryUnreadCount } from "@/app/utils"
import { ActionButton } from "@/components/ActionButton" import { ActionButton } from "@/components/ActionButton"
import { AnnouncementDialog } from "@/components/AnnouncementDialog" import { AnnouncementDialog } from "@/components/AnnouncementDialog"
import { DisablePullToRefresh } from "@/components/DisablePullToRefresh"
import { Loader } from "@/components/Loader" import { Loader } from "@/components/Loader"
import { Logo } from "@/components/Logo" import { Logo } from "@/components/Logo"
import { MarkAllAsReadConfirmationDialog } from "@/components/MarkAllAsReadConfirmationDialog" import { MarkAllAsReadConfirmationDialog } from "@/components/MarkAllAsReadConfirmationDialog"
@@ -51,6 +54,78 @@ function LogoAndTitle() {
) )
} }
function UnreadCountTitleHandler({
enabled,
}: Readonly<{
enabled?: boolean
}>) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
return <title>{enabled && unreadCount > 0 ? `(${unreadCount}) CommaFeed` : "CommaFeed"}</title>
}
function UnreadCountFaviconHandler({ enabled }: { enabled?: boolean }) {
const root = useAppSelector(state => state.tree.rootCategory)
const unreadCount = categoryUnreadCount(root)
useEffect(() => {
if (enabled && unreadCount > 0) {
Tinycon.setBubble(unreadCount)
} else {
Tinycon.reset()
}
}, [unreadCount, enabled])
return null
}
function BrowserExtensionBadgeUnreadCountHandler() {
const root = useAppSelector(state => state.tree.rootCategory)
const { setBadgeUnreadCount } = useBrowserExtension()
useEffect(() => {
if (!root) return
const unreadCount = categoryUnreadCount(root)
setBadgeUnreadCount(unreadCount)
}, [root, setBadgeUnreadCount])
return null
}
function CustomJsHandler() {
const [scriptLoaded, setScriptLoaded] = useState(false)
const { loading } = useAppLoading()
useEffect(() => {
if (scriptLoaded || loading) {
return
}
const script = document.createElement("script")
script.src = "custom_js.js"
script.async = true
document.body.appendChild(script)
setScriptLoaded(true)
return () => script.remove()
}, [scriptLoaded, loading])
return null
}
function CustomCssHandler() {
useEffect(() => {
const link = document.createElement("link")
link.rel = "stylesheet"
link.type = "text/css"
link.href = "custom_css.css"
document.head.appendChild(link)
return () => link.remove()
}, [])
return null
}
const useStyles = tss const useStyles = tss
.withParams<{ .withParams<{
sidebarWidth: number sidebarWidth: number
@@ -75,6 +150,9 @@ export default function Layout(props: Readonly<LayoutProps>) {
const draggableSeparator = useRef<HTMLDivElement>(null) const draggableSeparator = useRef<HTMLDivElement>(null)
const { loading } = useAppLoading() const { loading } = useAppLoading()
const unreadCountTitle = useAppSelector(state => state.user.settings?.unreadCountTitle)
const unreadCountFavicon = useAppSelector(state => state.user.settings?.unreadCountFavicon)
const disablePullToRefresh = useAppSelector(state => state.user.settings?.disablePullToRefresh)
const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen) const mobileMenuOpen = useAppSelector(state => state.tree.mobileMenuOpen)
const webSocketConnected = useAppSelector(state => state.server.webSocketConnected) const webSocketConnected = useAppSelector(state => state.server.webSocketConnected)
const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval) const treeReloadInterval = useAppSelector(state => state.server.serverInfos?.treeReloadInterval)
@@ -173,61 +251,69 @@ export default function Layout(props: Readonly<LayoutProps>) {
if (loading) return <LoadingPage /> if (loading) return <LoadingPage />
return ( return (
<Box {...swipeHandlers}> <>
<AppShell <UnreadCountTitleHandler enabled={unreadCountTitle} />
header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }} <UnreadCountFaviconHandler enabled={unreadCountFavicon} />
footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }} <BrowserExtensionBadgeUnreadCountHandler />
navbar={{ <CustomJsHandler />
width: sidebarWidth, <CustomCssHandler />
breakpoint: Constants.layout.mobileBreakpoint, <DisablePullToRefresh enabled={disablePullToRefresh} />
collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible }, <Box {...swipeHandlers}>
}} <AppShell
padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }} header={{ height: Constants.layout.headerHeight, collapsed: headerInFooter }}
> footer={{ height: Constants.layout.headerHeight, collapsed: !headerInFooter }}
<AppShell.Header>{!headerInFooter && header}</AppShell.Header> navbar={{
<AppShell.Footer>{headerInFooter && header}</AppShell.Footer> width: sidebarWidth,
<AppShell.Navbar p={sidebarPadding}> breakpoint: Constants.layout.mobileBreakpoint,
<AppShell.Section grow component={ScrollArea} mx="-sm" px="sm"> collapsed: { mobile: !mobileMenuOpen, desktop: !props.sidebarVisible },
<Box className={classes.sidebarContent}>{props.sidebar}</Box> }}
</AppShell.Section> padding={{ base: 6, [Constants.layout.mobileBreakpointName]: "md" }}
</AppShell.Navbar> >
<OnDesktop> <AppShell.Header>{!headerInFooter && header}</AppShell.Header>
<Draggable <AppShell.Footer>{headerInFooter && header}</AppShell.Footer>
nodeRef={draggableSeparator as RefObject<HTMLElement>} <AppShell.Navbar p={sidebarPadding}>
axis="x" <AppShell.Section grow component={ScrollArea} mx="-sm" px="sm">
defaultPosition={{ <Box className={classes.sidebarContent}>{props.sidebar}</Box>
x: sidebarWidth, </AppShell.Section>
y: 0, </AppShell.Navbar>
}} <OnDesktop>
bounds={{ <Draggable
left: 120, nodeRef={draggableSeparator as RefObject<HTMLElement>}
right: 1000, axis="x"
}} defaultPosition={{
grid={[30, 30]} x: sidebarWidth,
onDrag={(_e, data) => { y: 0,
dispatch(setSidebarWidth(data.x))
}}
>
<Box
ref={draggableSeparator}
style={{
position: "fixed",
height: "100%",
width: "10px",
cursor: "ew-resize",
}} }}
/> bounds={{
</Draggable> left: 120,
</OnDesktop> right: 1000,
}}
grid={[30, 30]}
onDrag={(_e, data) => {
dispatch(setSidebarWidth(data.x))
}}
>
<Box
ref={draggableSeparator}
style={{
position: "fixed",
height: "100%",
width: "10px",
cursor: "ew-resize",
}}
/>
</Draggable>
</OnDesktop>
<AppShell.Main> <AppShell.Main>
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<AnnouncementDialog /> <AnnouncementDialog />
<MarkAllAsReadConfirmationDialog /> <MarkAllAsReadConfirmationDialog />
<Outlet /> <Outlet />
</Suspense> </Suspense>
</AppShell.Main> </AppShell.Main>
</AppShell> </AppShell>
</Box> </Box>
</>
) )
} }