Skip to content

Commit

Permalink
feat: support zen mode sidebar entry timeline selector
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Oct 28, 2024
1 parent f9b1a61 commit 4ab132c
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 37 deletions.
10 changes: 10 additions & 0 deletions apps/renderer/src/atoms/settings/ui.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { createSettingAtom } from "@follow/atoms/helper/setting.js"
import type { UISettings } from "@follow/shared/interface/settings"
import { atom, useAtomValue } from "jotai"

import { internal_feedColumnShowAtom } from "../sidebar"

export const createDefaultSettings = (): UISettings => ({
// Sidebar
Expand Down Expand Up @@ -56,3 +59,10 @@ export const uiServerSyncWhiteListKeys: (keyof UISettings)[] = [
"opaqueSidebar",
"voice",
]

const isZenModeAtom = atom((get) => {
const ui = get(__uiSettingAtom)
return ui.wideMode && !get(internal_feedColumnShowAtom)
})

export const useIsZenMode = () => useAtomValue(isZenModeAtom)
11 changes: 8 additions & 3 deletions apps/renderer/src/atoms/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ viewAtom.onMount = () => {
setSidebarActiveView(view)
}
}
export const [, , useFeedColumnShow, , getFeedColumnShow, setFeedColumnShow] = createAtomHooks(
atom(true),
)
export const [
internal_feedColumnShowAtom,
,
useFeedColumnShow,
,
getFeedColumnShow,
setFeedColumnShow,
] = createAtomHooks(atom(true))

export const [, , useFeedColumnTempShow, , getFeedColumnTempShow, setFeedColumnTempShow] =
createAtomHooks(atom(false))
23 changes: 13 additions & 10 deletions apps/renderer/src/components/ui/modal/components/close.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@follow/components/ui/tooltip/index.js"
import { cn } from "@follow/utils/utils"
import { useTranslation } from "react-i18next"

export const FixedModalCloseButton: Component<{
onClick: () => void
}> = ({ onClick }) => {
className?: string
}> = ({ onClick, className }) => {
const { t } = useTranslation("common")
return (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="center flex size-8 rounded-full bg-background p-1 shadow-sm ring-1 ring-zinc-200 dark:ring-neutral-800"
onClick={onClick}
>
<i className="i-mgc-close-cute-re text-lg" />
<span className="sr-only">{t("close")}</span>
</button>
<TooltipTrigger
type="button"
className={cn(
"center flex size-8 rounded-full bg-background p-1 shadow-sm ring-1 ring-zinc-200 dark:ring-neutral-800",
className,
)}
onClick={onClick}
>
<i className="i-mgc-close-cute-re text-lg" />
<span className="sr-only">{t("close")}</span>
</TooltipTrigger>
<TooltipContent>{t("close")}</TooltipContent>
</Tooltip>
Expand Down
27 changes: 27 additions & 0 deletions apps/renderer/src/modules/entry-column/hooks/useEntryIdListSnap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { atom, useAtomValue, useSetAtom } from "jotai"
import { useEffect, useMemo, useState } from "react"

/**
* This is a global atom to store the current entry column's entry id list snapshot.
* It used to get current entry id list(keep sorted) in other components.
*/
const globalEntryIdListSnapAtom = atom<string[]>([])
export const useSnapEntryIdList = (ids: string[]) => {
const set = useSetAtom(globalEntryIdListSnapAtom)
useEffect(() => {
set(ids)
}, [ids, set])
}

export const useGetEntryIdInRange = (id: string, range: [number, number]) => {
const snap = useAtomValue(globalEntryIdListSnapAtom)
const [stableRange] = useState(range)
return useMemo(() => {
const index = snap.indexOf(id)

return snap.slice(
Math.max(0, index - stableRange[0]),
Math.min(snap.length, index + stableRange[1]),
)
}, [id, snap, stableRange])
}
3 changes: 3 additions & 0 deletions apps/renderer/src/modules/entry-column/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { useFeedById, useFeedHeaderTitle } from "~/store/feed"
import { useSubscriptionByFeedId } from "~/store/subscription"

import { useEntriesByView, useEntryMarkReadHandler } from "./hooks"
import { useSnapEntryIdList } from "./hooks/useEntryIdListSnap"
import { EntryItem, EntryItemSkeleton } from "./item"
import { PictureMasonry } from "./Items/picture-masonry"
import { EntryListHeader } from "./layouts/EntryListHeader"
Expand All @@ -55,7 +56,9 @@ function EntryColumnImpl() {
}, []),
isArchived,
})

const { entriesIds, isFetchingNextPage, groupedCounts } = entries
useSnapEntryIdList(entriesIds)

const {
entryId: activeEntryId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typography/EllipsisWithTooltip.js"
import { cn } from "@follow/utils/utils"
import type { Target, TargetAndTransition } from "framer-motion"
import { m } from "framer-motion"

import { useIsZenMode } from "~/atoms/settings/ui"
import { useAsRead } from "~/hooks/biz/useAsRead"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams"
import { useGetEntryIdInRange } from "~/modules/entry-column/hooks/useEntryIdListSnap"
import { useEntry } from "~/store/entry"

export const EntryTimelineSidebar = ({ entryId }: { entryId: string }) => {
const isZenMode = useIsZenMode()

if (!isZenMode) {
return null
}

return <Timeline entryId={entryId} />
}

const Timeline = ({ entryId }: { entryId: string }) => {
const entryIds = useGetEntryIdInRange(entryId, [5, 5])

return (
<m.div
className="absolute left-8 top-28 z-10 @lg:max-w-0 @6xl:max-w-[200px] @7xl:max-w-[200px] @[90rem]:max-w-[250px]"
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { delay: 0.5 } }}
>
{entryIds.map((id) => (
<TimelineItem key={id} id={id} />
))}
</m.div>
)
}

const initialButton: Target = {
opacity: 0.0001,
}
const animateButton: TargetAndTransition = {
opacity: 1,
}

const TimelineItem = ({ id }: { id: string }) => {
const entry = useEntry(id, (e) => ({
title: e.entries.title,
read: e.read,
}))
const asRead = useAsRead(entry!)
const navigate = useNavigateEntry()

const isActive = useRouteParamsSelector((r) => r.entryId === id)

return (
<m.button
layoutId={`timeline-${id}`}
initial={initialButton}
animate={animateButton}
className={"relative block min-w-0 max-w-full cursor-pointer text-xs leading-loose"}
type="button"
onClick={() => navigate({ entryId: id })}
>
{!asRead && (
<span className="absolute -left-4 top-1/2 size-1.5 -translate-y-1/2 rounded-full bg-accent opacity-50" />
)}
<EllipsisHorizontalTextWithTooltip
className={cn(
"truncate transition-[opacity,font-weight] duration-200",
isActive ? "font-medium opacity-100" : "opacity-60 hover:opacity-80",
)}
>
{entry?.title}
</EllipsisHorizontalTextWithTooltip>
</m.button>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ export const SupportCreator = ({ entryId }: { entryId: string }) => {
enableModal
/>
) : (
<FeedIcon className="w-40 flex-col gap-3 p-0" size={46} feed={feed} fallback />
<FeedIcon className="mr-0 w-40 flex-col gap-3 p-0" size={46} feed={feed} fallback />
)}
<span className="-mt-6 text-lg font-medium">{feed.title}</span>

<div className="flex items-center gap-4">
{!isMyOwnedFeed && (
Expand Down
2 changes: 1 addition & 1 deletion apps/renderer/src/modules/entry-content/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function EntryHeaderImpl({
return (
<div
className={cn(
"relative flex min-w-0 items-center justify-between gap-3 overflow-hidden text-lg text-zinc-500",
"relative flex min-w-0 items-center justify-between gap-3 overflow-hidden text-lg text-zinc-500 duration-200",
shouldShowMeta && "border-b border-border",
className,
)}
Expand Down
6 changes: 4 additions & 2 deletions apps/renderer/src/modules/entry-content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
setEntryTitleMeta,
setTranslationCache,
} from "./atoms"
import { EntryTimelineSidebar } from "./components/EntryTimelineSidebar"
import { EntryTitle } from "./components/EntryTitle"
import { SourceContentPanel } from "./components/SourceContentView"
import { SupportCreator } from "./components/SupportCreator"
Expand Down Expand Up @@ -196,10 +197,11 @@ export const EntryContentRender: Component<{
compact={compact}
/>

<div className="relative flex size-full flex-col overflow-hidden">
<div className="relative flex size-full flex-col overflow-hidden @container">
<EntryTimelineSidebar entryId={entry.entries.id} />
<ScrollArea.ScrollArea
mask={false}
rootClassName={cn("h-0 min-w-0 grow overflow-y-auto @container", className)}
rootClassName={cn("h-0 min-w-0 grow overflow-y-auto", className)}
scrollbarClassName="mr-[1.5px]"
viewportClassName="p-5"
ref={scrollerRef}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ActionButton } from "@follow/components/ui/button/index.js"
import { FeedViewType, views } from "@follow/constants"
import { cn } from "@follow/utils/utils"
import { useWheel } from "@use-gesture/react"
Expand All @@ -11,6 +10,7 @@ import { useParams } from "react-router-dom"
import { useUISettingKey } from "~/atoms/settings/ui"
import { useFeedColumnShow, useFeedColumnTempShow } from "~/atoms/sidebar"
import { m } from "~/components/common/Motion"
import { FixedModalCloseButton } from "~/components/ui/modal/components/close"
import { HotKeyScopeMap, ROUTE_ENTRY_PENDING, ROUTE_FEED_PENDING } from "~/constants"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
import { useRouteParams, useRouteView } from "~/hooks/biz/useRouteParams"
Expand All @@ -35,12 +35,10 @@ export const Component = () => {
useHotkeys(
"Escape",
() => {
if (settingWideMode) {
navigate({ entryId: null })
}
navigate({ entryId: null })
},
{
enabled: showEntryContent,
enabled: showEntryContent && settingWideMode,
scopes: HotKeyScopeMap.Home,
preventDefault: true,
},
Expand All @@ -57,21 +55,10 @@ export const Component = () => {
<AppLayoutGridContainerProvider>
<EntryGridContainer showEntryContent={showEntryContent} wideMode={wideMode}>
{wideMode && (
// Close button
<ActionButton
className={cn(
"absolute left-3 top-3 z-10 duration-200",
shouldHeaderPaddingLeft
? "left-[calc(theme(width.3)+theme(width.feed-col))]"
: "left-3",
)}
tooltip="Close"
shortcut="Escape"
disableTriggerShortcut
<FixedModalCloseButton
className="absolute left-3 top-3 z-10"
onClick={() => navigate({ entryId: null })}
>
<i className="i-mgc-close-cute-re size-5" />
</ActionButton>
/>
)}
{realEntryId ? (
<EntryContent
Expand All @@ -80,7 +67,7 @@ export const Component = () => {
header: shouldHeaderPaddingLeft
? "ml-[calc(theme(width.feed-col)+theme(width.8))]"
: wideMode
? "ml-8"
? "ml-12"
: "",
}}
/>
Expand Down

0 comments on commit 4ab132c

Please sign in to comment.