Merge pull request #5 from RSSNext/refactor/outlet-context

refactor: extract outlet context as a outside context
This commit is contained in:
DIYgod 2024-05-24 14:27:05 +01:00 committed by GitHub
commit c0e3a10568
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 141 additions and 137 deletions

View File

@ -1,5 +1,5 @@
import { useEntries } from "@renderer/lib/queries/entries"
import { ActivedList, ActivedEntry } from "@renderer/lib/types"
import { ActiveList, ActivedEntry } from "@renderer/lib/types"
import { m } from "framer-motion"
import { cn } from "@renderer/lib/utils"
import { ArticleItem } from "./article-item"
@ -8,23 +8,21 @@ import { PictureItem } from "./picture-item"
import { VideoItem } from "./video-item"
import { NotificationItem } from "./notification-item"
import { EntryItemWrapper } from "./item-wrapper"
import { useOutletContext } from "react-router-dom"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
const gridMode = [2, 3]
export function EntryColumn() {
const { activedList, setActivedEntry } = useOutletContext<{
activedList: ActivedList
setActivedEntry: (value: ActivedEntry) => void
}>()
const { activeList, setActiveEntry: setActivedEntry } = useMainLayoutContext()
const entries = useEntries({
level: activedList?.level,
id: activedList?.id,
view: activedList?.view,
level: activeList?.level,
id: activeList?.id,
view: activeList?.view,
})
let Item
switch (activedList?.view) {
switch (activeList?.view) {
case 0:
Item = ArticleItem
break
@ -47,7 +45,7 @@ export function EntryColumn() {
return (
<div className="px-2" onClick={() => setActivedEntry(null)}>
<div className="ml-9 mb-5">
<div className="font-bold text-lg">{activedList?.name}</div>
<div className="font-bold text-lg">{activeList?.name}</div>
<div className="text-xs font-medium text-zinc-400">
{entries.data?.pages?.[0].total} Items
</div>
@ -55,13 +53,13 @@ export function EntryColumn() {
<div>
{entries.data?.pages?.map((page, index) => (
<m.div
key={`${activedList?.level}-${activedList?.id}-${index}`}
key={`${activeList?.level}-${activeList?.id}-${index}`}
initial={{ opacity: 0.01, y: 100 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0.01, y: -100 }}
className={cn(
activedList?.view &&
gridMode.includes(activedList.view) &&
activeList?.view &&
gridMode.includes(activeList.view) &&
"grid gap-2 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 px-2",
)}
>
@ -69,7 +67,7 @@ export function EntryColumn() {
<EntryItemWrapper
key={entry.id}
entry={entry}
view={activedList?.view}
view={activeList?.view}
>
<Item entry={entry} />
</EntryItemWrapper>

View File

@ -1,8 +1,8 @@
import { ActivedEntry, EntriesResponse } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { useEntryActions } from "@renderer/hooks/useEntryActions"
import { useOutletContext } from "react-router-dom"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { EntriesResponse } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
export function EntryItemWrapper({
entry,
@ -19,17 +19,16 @@ export function EntryItemWrapper({
view,
entry,
})
const { activedEntry, setActivedEntry } = useOutletContext<{
activedEntry: ActivedEntry
setActivedEntry: (value: ActivedEntry) => void
}>()
const { activeEntry, setActiveEntry: setActivedEntry } =
useMainLayoutContext()
return (
<div
key={entry.id}
className={cn(
"rounded-md transition-colors",
activedEntry === entry.id && "bg-[#DEDDDC]",
activeEntry === entry.id && "bg-[#DEDDDC]",
)}
onClick={(e) => {
e.stopPropagation()

View File

@ -2,20 +2,19 @@ import {
Collapsible,
CollapsibleTrigger,
} from "@renderer/components/ui/collapsible"
import { m, AnimatePresence } from "framer-motion"
import { useEffect, useState } from "react"
import { levels } from "@renderer/lib/constants"
import { ActivedList } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
import { Response as SubscriptionsResponse } from "@renderer/lib/queries/subscriptions"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { client } from "@renderer/lib/client"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { apiFetch } from "@renderer/lib/queries/api-fetch"
import { CategoryRenameDialog } from "./category-rename-dialog"
import { Dialog } from "@renderer/components/ui/dialog"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { client } from "@renderer/lib/client"
import { levels } from "@renderer/lib/constants"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { apiFetch } from "@renderer/lib/queries/api-fetch"
import { Response as SubscriptionsResponse } from "@renderer/lib/queries/subscriptions"
import { cn } from "@renderer/lib/utils"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { AnimatePresence, m } from "framer-motion"
import { useEffect, useState } from "react"
import { CategoryRenameDialog } from "./category-rename-dialog"
import { FeedItem } from "./item"
import { useOutletContext } from "react-router-dom"
export function FeedCategory({
data,
@ -26,10 +25,13 @@ export function FeedCategory({
view?: number
expansion: boolean
}) {
const { activedList, setActivedList } = useOutletContext<{
activedList: ActivedList
setActivedList: ((value: ActivedList) => void) | null
}>()
const {
activeList,
setActiveEntry: setActivedEntry,
setActiveList,
activeEntry,
} = useMainLayoutContext()
const [open, setOpen] = useState(!data.name)
const [dialogOpen, setDialogOpen] = useState(false)
@ -59,7 +61,7 @@ export function FeedCategory({
const setCategoryActive = () => {
view !== undefined &&
setActivedList?.({
setActiveList?.({
level: levels.folder,
id: data.list.map((feed) => feed.feedId).join(","),
name: data.name,
@ -78,8 +80,8 @@ export function FeedCategory({
<div
className={cn(
"flex items-center justify-between font-medium text-sm leading-loose px-2.5 py-[2px] rounded-md w-full transition-colors",
activedList?.level === levels.folder &&
activedList.name === data.name &&
activeList?.level === levels.folder &&
activeList.name === data.name &&
"bg-native-active",
)}
onClick={(e) => {
@ -120,15 +122,15 @@ export function FeedCategory({
<CollapsibleTrigger
className={cn(
"flex items-center h-7 [&_.i-mingcute-right-fill]:data-[state=open]:rotate-90",
!setActivedList && "flex-1",
!setActiveList && "flex-1",
)}
>
<i className="i-mingcute-right-fill mr-2 transition-transform" />
{!setActivedList && (
{!setActiveList && (
<span className="truncate">{data.name}</span>
)}
</CollapsibleTrigger>
{setActivedList && <span className="truncate">{data.name}</span>}
{setActiveList && <span className="truncate">{data.name}</span>}
</div>
{!!data.unread && (
<div className="text-xs text-zinc-500 ml-2">{data.unread}</div>

View File

@ -1,27 +1,25 @@
import { Button } from "@renderer/components/ui/button"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@renderer/components/ui/tooltip"
import { useEffect, useRef, useState } from "react"
import { useWheel } from "@use-gesture/react"
import { Lethargy } from "lethargy"
import { cn, clamp } from "@renderer/lib/utils"
import { m, useSpring } from "framer-motion"
import { FeedList } from "./list"
import { ActivedList } from "@renderer/lib/types"
import { UserButton } from "@renderer/components/user-button"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { levels, views } from "@renderer/lib/constants"
import { Link, useOutletContext } from "react-router-dom"
import { Button } from "@renderer/components/ui/button"
import { clamp, cn } from "@renderer/lib/utils"
import { useWheel } from "@use-gesture/react"
import { m, useSpring } from "framer-motion"
import { Lethargy } from "lethargy"
import { useEffect, useRef, useState } from "react"
import { Link } from "react-router-dom"
import { FeedList } from "./list"
const lethargy = new Lethargy()
export function FeedColumn() {
const { setActivedList } = useOutletContext<{
setActivedList: (value: ActivedList) => void
}>()
const { setActiveList } = useMainLayoutContext()
const carouselRef = useRef<HTMLDivElement>(null)
const [active, setActive] = useState(0)
@ -61,7 +59,7 @@ export function FeedColumn() {
<div
className="h-full flex flex-col gap-3"
onClick={() =>
setActivedList?.({
setActiveList?.({
level: levels.view,
id: active,
name: views[active].name,
@ -75,7 +73,7 @@ export function FeedColumn() {
className="font-bold text-xl flex items-center gap-1"
onClick={(e) => {
e.stopPropagation()
setActivedList?.({
setActiveList?.({
level: levels.view,
id: active,
name: views[active].name,
@ -113,7 +111,7 @@ export function FeedColumn() {
size="sm"
onClick={(e) => {
setActive(index)
setActivedList?.({
setActiveList?.({
level: "view",
id: index,
name: views[index].name,

View File

@ -1,25 +1,24 @@
import { useState } from "react"
import { levels } from "@renderer/lib/constants"
import { ActivedList, SubscriptionResponse } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
import { FeedIcon } from "@renderer/components/feed-icon"
import { FollowDialog } from "@renderer/components/follow/dialog"
import { Dialog } from "@renderer/components/ui/dialog"
import { ToastAction } from "@renderer/components/ui/toast"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipProvider,
TooltipTrigger,
TooltipPortal,
} from "@renderer/components/ui/tooltip"
import { FeedIcon } from "@renderer/components/feed-icon"
import { useToast } from "@renderer/components/ui/use-toast"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { levels } from "@renderer/lib/constants"
import dayjs from "@renderer/lib/dayjs"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { apiFetch } from "@renderer/lib/queries/api-fetch"
import { Dialog } from "@renderer/components/ui/dialog"
import { FollowDialog } from "@renderer/components/follow/dialog"
import { useToast } from "@renderer/components/ui/use-toast"
import { ToastAction } from "@renderer/components/ui/toast"
import { useOutletContext } from "react-router-dom"
import { SubscriptionResponse } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react"
export function FeedItem({
feed,
view,
@ -30,14 +29,11 @@ export function FeedItem({
className?: string
}) {
const [dialogOpen, setDialogOpen] = useState(false)
const { activedList, setActivedList } = useOutletContext<{
activedList: ActivedList
setActivedList: (value: ActivedList) => void
}>()
const { activeList, setActiveList } = useMainLayoutContext()
const setFeedActive = (feed: SubscriptionResponse[number]) => {
view !== undefined &&
setActivedList?.({
setActiveList?.({
level: levels.feed,
id: feed.feedId,
name: feed.feeds.title || "",
@ -98,8 +94,8 @@ export function FeedItem({
<div
className={cn(
"flex items-center justify-between text-sm font-medium leading-loose w-full pr-2.5 py-[2px] rounded-md",
activedList?.level === levels.feed &&
activedList.id === feed.feedId &&
activeList?.level === levels.feed &&
activeList.id === feed.feedId &&
"bg-native-active",
className,
)}

View File

@ -1,10 +1,9 @@
import { useSubscriptions } from "@renderer/lib/queries/subscriptions"
import { useState } from "react"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { levels, views } from "@renderer/lib/constants"
import { ActivedList } from "@renderer/lib/types"
import { useSubscriptions } from "@renderer/lib/queries/subscriptions"
import { cn } from "@renderer/lib/utils"
import { useState } from "react"
import { FeedCategory } from "./category"
import { useOutletContext } from "react-router-dom"
export function FeedList({
className,
@ -17,10 +16,7 @@ export function FeedList({
}) {
const subscriptions = useSubscriptions(view)
const [expansion, setExpansion] = useState(false)
const { setActivedList } = useOutletContext<{
activedList: ActivedList
setActivedList: (value: ActivedList) => void
}>()
const { setActiveList } = useMainLayoutContext()
return (
<div className={className}>
@ -33,7 +29,7 @@ export function FeedList({
onClick={(e) => {
e.stopPropagation()
view !== undefined &&
setActivedList({
setActiveList({
level: levels.view,
id: view,
name: views[view].name,

View File

@ -0,0 +1,17 @@
import type { ActiveList, ActivedEntry } from "@renderer/lib/types"
import { Outlet, useOutletContext } from "react-router-dom"
export interface MainLayoutContextType {
activeList: ActiveList
setActiveEntry: (value: ActivedEntry) => void
activeEntry: ActivedEntry
setActiveList: (value: ActiveList) => void
}
export const useMainLayoutContext = () =>
useOutletContext<MainLayoutContextType>()
export const MainLayoutOutlet = (ctxValue: MainLayoutContextType) => (
<Outlet context={ctxValue} />
)

View File

@ -1,4 +1,4 @@
export type ActivedList = {
export type ActiveList = {
level: string
id: string | number
name: string

View File

@ -1,30 +1,28 @@
import { EntryColumn } from "@renderer/components/entry-column"
import { useEffect } from "react"
import { ActivedList, ActivedEntry } from "@renderer/lib/types"
import { cn } from "@renderer/lib/utils"
import { EntryContent } from "@renderer/components/entry-content"
import { useMainLayoutContext } from "@renderer/contexts/outlet/main-layout"
import { cn } from "@renderer/lib/utils"
import { AnimatePresence } from "framer-motion"
import { useOutletContext } from "react-router-dom"
import { useEffect } from "react"
const wideMode = [1, 2, 3, 4]
export function Component() {
const { activedList, activedEntry, setActivedEntry } = useOutletContext<{
activedList: ActivedList
activedEntry: ActivedEntry
setActivedEntry: (value: ActivedEntry) => void
}>()
const {
activeList,
activeEntry,
setActiveEntry: setActivedEntry,
} = useMainLayoutContext()
useEffect(() => {
setActivedEntry(null)
}, [activedList])
}, [activeList])
return (
<>
<div
className={cn(
"pt-10 border-r shrink-0 h-full overflow-y-auto",
activedList && wideMode.includes(activedList.view)
activeList && wideMode.includes(activeList.view)
? "flex-1"
: "w-[340px]",
)}
@ -32,12 +30,11 @@ export function Component() {
<EntryColumn />
</div>
<AnimatePresence>
{!(activedList && wideMode.includes(activedList.view)) &&
activedEntry && (
<div className="flex-1">
<EntryContent entryId={activedEntry} />
</div>
)}
{!(activeList && wideMode.includes(activeList.view)) && activeEntry && (
<div className="flex-1">
<EntryContent entryId={activeEntry} />
</div>
)}
</AnimatePresence>
</>
)

View File

@ -1,27 +1,27 @@
import { FeedColumn } from "@renderer/components/feed-column"
import { ActivedEntry, ActivedList } from "@renderer/lib/types"
import { Outlet, useOutletContext } from "react-router-dom"
import {
MainLayoutOutlet,
useMainLayoutContext,
} from "@renderer/contexts/outlet/main-layout"
export function Component() {
const { activedList, setActivedList, activedEntry, setActivedEntry } =
useOutletContext<{
activedList: ActivedList
setActivedList: (value: ActivedList) => void
activedEntry: ActivedEntry
setActivedEntry: (value: ActivedEntry) => void
}>()
const {
activeEntry,
activeList,
setActiveList,
setActiveEntry: setActivedEntry,
} = useMainLayoutContext()
return (
<div className="flex h-full">
<div className="w-64 pt-10 border-r shrink-0 bg-native">
<FeedColumn />
</div>
<Outlet
context={{
activedList,
activedEntry,
setActivedEntry,
setActivedList,
<MainLayoutOutlet
{...{
activeList,
activeEntry,
setActiveList,
setActiveEntry: setActivedEntry,
}}
/>
</div>

View File

@ -1,38 +1,39 @@
import { useEffect, useState } from "react"
import { ActivedList, ActivedEntry } from "@renderer/lib/types"
import { ActiveList, ActivedEntry } from "@renderer/lib/types"
import { Outlet } from "react-router-dom"
import { useNavigate } from "react-router-dom"
import { useSession } from "@hono/auth-js/react"
import { MainLayoutOutlet } from "@renderer/contexts/outlet/main-layout"
export function Component() {
const { status } = useSession()
const navigate = useNavigate()
const [activedList, setActivedList] = useState<ActivedList>({
const [activeList, setActiveList] = useState<ActiveList>({
level: "view",
id: 0,
name: "Articles",
view: 0,
})
const [activedEntry, setActivedEntry] = useState<ActivedEntry>(null)
const [activeEntry, setActiveEntry] = useState<ActivedEntry>(null)
useEffect(() => {
setActivedEntry(null)
if (!activedList?.preventNavigate) {
setActiveEntry(null)
if (!activeList?.preventNavigate) {
navigate("/")
}
}, [activedList])
}, [activeList])
if (status !== "authenticated") {
return navigate("/login")
}
return (
<Outlet
context={{
activedList,
activedEntry,
setActivedEntry,
setActivedList,
<MainLayoutOutlet
{...{
activeList,
activeEntry,
setActiveEntry: setActiveEntry,
setActiveList,
}}
/>
)