mirror of
https://github.com/Athou/commafeed.git
synced 2026-10-07 04:44:50 +00:00
move handlers in Layout because they actually only apply to the reader pages
This commit is contained in:
@@ -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 />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user