Skip to content

Commit

Permalink
fix: feed column resize perf
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Aug 26, 2024
1 parent 11577dd commit a09f55a
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,52 @@ export const MarkAllReadWithOverlay = forwardRef<
useOnClickOutside({ current: popoverRef }, () => {
setShow(false)
})
const renderPopup = () => {
const $parent = containerRef.current!
const rect = $parent.getBoundingClientRect()
const paddingLeft = $parent.offsetLeft
return (
<RootPortal to={$parent}>
<m.div
ref={setPopoverRef}
initial={{ y: -70 }}
animate={{ y: 0 }}
exit={{ y: -70 }}
transition={{ type: "spring", damping: 20, stiffness: 300 }}
className="shadow-modal fixed z-50 bg-theme-modal-background-opaque shadow"
style={{
left: -paddingLeft,
top: rect.top,
width: rect.width,
}}
>
<div className="flex w-full translate-x-[-2px] items-center justify-between gap-3 !py-3 pl-6 pr-3 [&_button]:text-xs">
<span className="center gap-[calc(0.5rem+2px)]">
<i className="i-mgc-check-circle-cute-re" />
<span className="text-sm font-bold">
Mark
<span> </span>
{which}
<span> </span>
as read?
</span>
</span>
<div className="space-x-4">
<IconButton
icon={<i className="i-mgc-check-filled" />}
onClick={() => {
handleMarkAllAsRead()
setShow(false)
}}
>
Confirm
</IconButton>
</div>
</div>
</m.div>
</RootPortal>
)
}
return (
<Fragment>
<ActionButton
Expand All @@ -64,49 +110,7 @@ export const MarkAllReadWithOverlay = forwardRef<
<i className="i-mgc-check-circle-cute-re" />
</ActionButton>

<AnimatePresence>
{show && (
<RootPortal>
<m.div
ref={setPopoverRef}
initial={{ y: -70 }}
animate={{ y: 0 }}
exit={{ y: -70 }}
transition={{ type: "spring", damping: 20, stiffness: 300 }}
className="shadow-modal fixed z-50 bg-theme-modal-background-opaque shadow"
style={{
left: containerRef.current?.getBoundingClientRect().left,
top: containerRef.current?.getBoundingClientRect().top,
width: containerRef.current?.getBoundingClientRect().width,
}}
>
<div className="flex w-full translate-x-[-2px] items-center justify-between gap-3 !py-3 pl-6 pr-3 [&_button]:text-xs">
<span className="center gap-[calc(0.5rem+2px)]">
<i className="i-mgc-check-circle-cute-re" />
<span className="text-sm font-bold">
Mark
<span> </span>
{which}
<span> </span>
as read?
</span>
</span>
<div className="space-x-4">
<IconButton
icon={<i className="i-mgc-check-filled" />}
onClick={() => {
handleMarkAllAsRead()
setShow(false)
}}
>
Confirm
</IconButton>
</div>
</div>
</m.div>
</RootPortal>
)}
</AnimatePresence>
<AnimatePresence>{show && renderPopup()}</AnimatePresence>
</Fragment>
)
})
Expand Down
160 changes: 78 additions & 82 deletions src/renderer/src/modules/feed-column/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect */
import { getReadonlyRoute } from "@renderer/atoms/route"
import { useUISettingKey } from "@renderer/atoms/settings/ui"
import {
useUISettingKey,
} from "@renderer/atoms/settings/ui"
import { useSidebarActiveView } from "@renderer/atoms/sidebar"
import { ActionButton } from "@renderer/components/ui/button"
import { HotKeyScopeMap, views } from "@renderer/constants"
Expand All @@ -8,21 +11,23 @@ import { useNavigateEntry } from "@renderer/hooks/biz/useNavigateEntry"
import { useReduceMotion } from "@renderer/hooks/biz/useReduceMotion"
import { getRouteParams } from "@renderer/hooks/biz/useRouteParams"
import { useAuthQuery } from "@renderer/hooks/common"
import { nextFrame, stopPropagation } from "@renderer/lib/dom"
import { stopPropagation } from "@renderer/lib/dom"
import { Routes } from "@renderer/lib/enum"
import { jotaiStore } from "@renderer/lib/jotai"
import { clamp, cn } from "@renderer/lib/utils"
import { Queries } from "@renderer/queries"
import { useSubscriptionStore } from "@renderer/store/subscription"
import { useFeedUnreadStore } from "@renderer/store/unread"
import { useSubscribeElectronEvent } from "@shared/event"
import { useWheel } from "@use-gesture/react"
import type { MotionValue } from "framer-motion"
import { m, useSpring } from "framer-motion"
import { atom, useAtomValue } from "jotai"
import { AnimatePresence, m } from "framer-motion"
import { Lethargy } from "lethargy"
import type { PropsWithChildren } from "react"
import { useCallback, useLayoutEffect, useRef } from "react"
import type { FC, PropsWithChildren } from "react"
import {
useCallback,
useLayoutEffect,
useRef,
useState,
} from "react"
import { isHotkeyPressed, useHotkeys } from "react-hotkeys-hook"

import { WindowUnderBlur } from "../../components/ui/background"
Expand Down Expand Up @@ -64,15 +69,11 @@ const useUnreadByView = () => {
return totalUnread
}

const carouselWidthAtom = atom(256)
export function FeedColumn({ children }: PropsWithChildren) {
const carouselRef = useRef<HTMLDivElement>(null)

const [active, setActive_] = useSidebarActiveView()
const spring = useSpring(0, {
stiffness: 700,
damping: 40,
})

const navigateBackHome = useBackHome(active)
const setActive: typeof setActive_ = useCallback(
(args) => {
Expand All @@ -83,7 +84,7 @@ export function FeedColumn({ children }: PropsWithChildren) {
navigateBackHome(nextActive)
}
},
[active, navigateBackHome, spring],
[active, navigateBackHome],
)

useLayoutEffect(() => {
Expand All @@ -93,18 +94,6 @@ export function FeedColumn({ children }: PropsWithChildren) {
}
}, [setActive_])

useLayoutEffect(() => {
const handler = () => {
spring.jump(-active * jotaiStore.get(carouselWidthAtom))
}
const dispose = jotaiStore.sub(carouselWidthAtom, handler)

spring.set(-active * jotaiStore.get(carouselWidthAtom))
return () => {
dispose()
}
}, [active, spring])

useHotkeys(
shortcuts.feeds.switchBetweenViews.key,
(e) => {
Expand Down Expand Up @@ -147,22 +136,6 @@ export function FeedColumn({ children }: PropsWithChildren) {
},
)

useLayoutEffect(() => {
const $carousel = carouselRef.current
if (!$carousel) return

const handler = () => {
const width = $carousel.clientWidth

jotaiStore.set(carouselWidthAtom, width)
}
handler()
new ResizeObserver(handler).observe($carousel)
return () => {
new ResizeObserver(handler).disconnect()
}
}, [])

const unreadByView = useUnreadByView()

const showSidebarUnreadCount = useUISettingKey("sidebarShowUnreadCount")
Expand Down Expand Up @@ -212,14 +185,11 @@ export function FeedColumn({ children }: PropsWithChildren) {
))}
</div>
<div className="relative size-full overflow-hidden" ref={carouselRef}>
<SwipeWrapper active={active} spring={spring}>
<SwipeWrapper active={active}>
{views.map((item, index) => (
<section
key={item.name}
className="absolute h-full w-[var(--fo-feed-col-w)] shrink-0 snap-center"
style={{
left: `${index * 100}%`,
}}
className="h-full w-[var(--fo-feed-col-w)] shrink-0 snap-center"
>
{active === index && (
<FeedList
Expand All @@ -237,52 +207,78 @@ export function FeedColumn({ children }: PropsWithChildren) {
)
}

const SwipeWrapper: Component<{
const SwipeWrapper: FC<{
active: number
spring: MotionValue<number>
}> = ({ children, active, spring }) => {
children: React.JSX.Element[]
}> = ({ children, active }) => {
const reduceMotion = useReduceMotion()

const carouselWidth = useAtomValue(carouselWidthAtom)
const feedColumnWidth = useUISettingKey("feedColWidth")
const containerRef = useRef<HTMLDivElement>(null)

useLayoutEffect(() => {
const $container = containerRef.current
if (!$container) return
// useLayoutEffect(() => {
// const $container = containerRef.current;
// if (!$container) return;

const x = -active * carouselWidth
// NOTE: To fix the misalignment of the browser's layout, use display to re-render it.
if (x !== $container.getBoundingClientRect().x) {
$container.style.display = "none"
// const x = -active * feedColumnWidth;
// // NOTE: To fix the misalignment of the browser's layout, use display to re-render it.
// if (x !== $container.getBoundingClientRect().x) {
// $container.style.display = "none";

nextFrame(() => {
$container.style.display = ""
})
// nextFrame(() => {
// $container.style.display = "";
// });
// }
// }, []);

const prevActiveIndexRef = useRef(-1)
const [isReady, setIsReady] = useState(false)

const [direction, setDirection] = useState<"left" | "right">("right")
const [currentAnimtedActive, setCurrentAnimatedActive] = useState(active)

useLayoutEffect(() => {
const prevActiveIndex = prevActiveIndexRef.current
if (prevActiveIndex !== active) {
if (prevActiveIndex < active) {
setDirection("right")
} else {
setDirection("left")
}
}
}, [])
setCurrentAnimatedActive(active)
if (prevActiveIndexRef.current !== -1) {
setIsReady(true)
}
prevActiveIndexRef.current = active
}, [active])

if (reduceMotion) {
return (
<div
ref={containerRef}
className="absolute inset-0"
style={{
transform: `translateX(${-active * carouselWidth}px)`,
}}
>
{children}
</div>
)
return <div ref={containerRef}>{children}</div>
}

return (
<m.div
ref={containerRef}
className="absolute inset-0"
style={{
x: spring,
}}
>
{children}
</m.div>
<AnimatePresence mode="popLayout">
<m.div
key={currentAnimtedActive}
initial={
isReady ?
{
x: direction === "right" ? -feedColumnWidth : feedColumnWidth,
} :
true
}
animate={{ x: 0 }}
exit={{
x: direction === "right" ? feedColumnWidth : -feedColumnWidth,
}}
transition={{
x: { type: "spring", stiffness: 700, damping: 40 },
}}
ref={containerRef}
>
{children[currentAnimtedActive]}
</m.div>
</AnimatePresence>
)
}
3 changes: 2 additions & 1 deletion src/renderer/src/pages/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,14 @@ const FeedResponsiveResizerContainer = ({
<div
className={cn(
"shrink-0 overflow-hidden",
"absolute inset-y-0 z-10 duration-200",
"absolute inset-y-0 z-10",
feedColumnTempShow &&
!feedColumnShow &&
"shadow-drawer-right z-[12] border-r",
!feedColumnShow && !feedColumnTempShow ?
"-translate-x-full delay-200" :
"",
!isDragging ? "duration-200" : "",
)}
style={{
"width": `${position}px`,
Expand Down

0 comments on commit a09f55a

Please sign in to comment.