From 29f2c0ca41badb34b001ac9dff91c1e2b600a71f Mon Sep 17 00:00:00 2001 From: DIYgod Date: Fri, 27 Sep 2024 04:41:04 +0800 Subject: [PATCH] feat: picture entry preview modal --- .../src/components/ui/media/VideoPlayer.tsx | 28 ++--- .../src/components/ui/media/hooks.tsx | 4 +- .../src/components/ui/media/preview-media.tsx | 112 ++++++++++-------- .../entry-column/Items/picture-item.tsx | 2 +- .../entry-content/components/EntryTitle.tsx | 6 +- .../src/modules/entry-content/header.tsx | 4 +- .../src/modules/entry-content/index.tsx | 22 +++- 7 files changed, 98 insertions(+), 80 deletions(-) diff --git a/apps/renderer/src/components/ui/media/VideoPlayer.tsx b/apps/renderer/src/components/ui/media/VideoPlayer.tsx index 9153fe5b3..ca9043bd8 100644 --- a/apps/renderer/src/components/ui/media/VideoPlayer.tsx +++ b/apps/renderer/src/components/ui/media/VideoPlayer.tsx @@ -27,8 +27,6 @@ import { cn } from "~/lib/utils" import { MotionButtonBase } from "../button" import { softSpringPreset } from "../constants/spring" -import { KbdCombined } from "../kbd/Kbd" -import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip" import { VolumeSlider } from "./VolumeSlider" type VideoPlayerProps = { @@ -225,7 +223,7 @@ const ControlBar = memo(() => { dragMomentum={false} dragConstraints={{ current: document.documentElement }} className={cn( - "absolute inset-x-2 -bottom-10 h-8 rounded-2xl border bg-zinc-100/90 backdrop-blur-xl dark:border-transparent dark:bg-neutral-700/90", + "absolute inset-x-2 bottom-2 h-8 rounded-2xl border bg-zinc-100/90 backdrop-blur-xl dark:border-transparent dark:bg-neutral-700/90", "flex items-center gap-3 px-3", "mx-auto max-w-[80vw]", )} @@ -423,8 +421,6 @@ const PlayProgressBar = () => { const ActionIcon = ({ className, onClick, - label, - labelDelayDuration = 700, children, shortcut, }: { @@ -446,20 +442,12 @@ const ActionIcon = ({ }, ) return ( - - - - - - {label} - {shortcut && {shortcut}} - - + ) } diff --git a/apps/renderer/src/components/ui/media/hooks.tsx b/apps/renderer/src/components/ui/media/hooks.tsx index 5c51e39fe..ab9b82215 100644 --- a/apps/renderer/src/components/ui/media/hooks.tsx +++ b/apps/renderer/src/components/ui/media/hooks.tsx @@ -5,14 +5,14 @@ import { useModalStack } from "../modal/stacked/hooks" import type { PreviewMediaProps } from "./preview-media" import { PreviewMediaContent } from "./preview-media" -export const usePreviewMedia = () => { +export const usePreviewMedia = (entryId?: string) => { const { present } = useModalStack() return useCallback( (media: PreviewMediaProps[], initialIndex = 0) => { present({ content: () => (
- +
), title: "Media Preview", diff --git a/apps/renderer/src/components/ui/media/preview-media.tsx b/apps/renderer/src/components/ui/media/preview-media.tsx index 630a2cc34..b6d0436fe 100644 --- a/apps/renderer/src/components/ui/media/preview-media.tsx +++ b/apps/renderer/src/components/ui/media/preview-media.tsx @@ -11,6 +11,7 @@ import { tipcClient } from "~/lib/client" import { stopPropagation } from "~/lib/dom" import { replaceImgUrlIfNeed } from "~/lib/img-proxy" import { cn } from "~/lib/utils" +import { EntryContent } from "~/modules/entry-content" import { ActionButton, MotionButtonBase } from "../button" import { microReboundPreset } from "../constants/spring" @@ -20,55 +21,61 @@ import { VideoPlayer } from "./VideoPlayer" const Wrapper: Component<{ src: string showActions?: boolean -}> = ({ children, src, showActions }) => { + entryId?: string +}> = ({ children, src, showActions, entryId }) => { const { dismiss } = useCurrentModal() return ( -
+
- {children} - - - - {showActions && ( - - {!!window.electron && ( - { - tipcClient?.download(src) - }} - > - - + {children} +
+ {showActions && ( + + {!!window.electron && ( + { + tipcClient?.download(src) + }} + > + + + )} + { + window.open(src) + }} + > + + + )} - { - window.open(src) - }} - > - - - +
+
+ {entryId && ( +
+ +
)}
@@ -81,7 +88,8 @@ export interface PreviewMediaProps extends MediaModel { export const PreviewMediaContent: FC<{ media: PreviewMediaProps[] initialIndex?: number -}> = ({ media, initialIndex = 0 }) => { + entryId?: string +}> = ({ media, initialIndex = 0, entryId }) => { const [currentMedia, setCurrentMedia] = useState(media[initialIndex]) const [currentSlideIndex, setCurrentSlideIndex] = useState(initialIndex) const swiperRef = useRef(null) @@ -100,20 +108,21 @@ export const PreviewMediaContent: FC<{ const src = media[0].url const { type } = media[0] return ( - + {type === "video" ? ( ) : ( @@ -122,7 +131,7 @@ export const PreviewMediaContent: FC<{ ) } return ( - + {showActions && (
@@ -148,7 +157,7 @@ export const PreviewMediaContent: FC<{ transition={{ ease: "easeInOut", duration: 0.2 }} onClick={() => swiperRef.current?.swiper.slidePrev()} type="button" - className="center fixed left-2 top-1/2 z-[99] size-8 -translate-y-1/2 rounded-full border border-white/20 bg-neutral-900/80 text-white backdrop-blur duration-200 hover:bg-neutral-900" + className="center absolute left-2 top-1/2 z-[99] size-8 -translate-y-1/2 rounded-full border border-white/20 bg-neutral-900/80 text-white backdrop-blur duration-200 hover:bg-neutral-900" > @@ -159,7 +168,7 @@ export const PreviewMediaContent: FC<{ transition={{ ease: "easeInOut", duration: 0.2 }} onClick={() => swiperRef.current?.swiper.slideNext()} type="button" - className="center fixed right-2 top-1/2 z-[99] size-8 -translate-y-1/2 rounded-full border border-white/20 bg-neutral-900/80 text-white backdrop-blur duration-200 hover:bg-neutral-900" + className="center absolute right-2 top-1/2 z-[99] size-8 -translate-y-1/2 rounded-full border border-white/20 bg-neutral-900/80 text-white backdrop-blur duration-200 hover:bg-neutral-900" > @@ -168,13 +177,13 @@ export const PreviewMediaContent: FC<{ {showActions && (
-
+
{currentSlideIndex + 1} / {media.length}
{Array.from({ length: media.length }) .fill(0) @@ -200,6 +209,8 @@ export const PreviewMediaContent: FC<{ {med.type === "video" ? ( e.stopPropagation()} @@ -223,9 +234,10 @@ export const PreviewMediaContent: FC<{ const FallbackableImage: FC< Omit, "src"> & { src: string + containerClassName?: string fallbackUrl?: string } -> = ({ src, onError, fallbackUrl, ...props }) => { +> = ({ src, onError, fallbackUrl, containerClassName, ...props }) => { const [currentSrc, setCurrentSrc] = useState(() => replaceImgUrlIfNeed(src)) const [isAllError, setIsAllError] = useState(false) @@ -266,7 +278,7 @@ const FallbackableImage: FC< }, [currentSrc, currentState, fallbackUrl, src]) return ( -
+
{isLoading && !isAllError && (
diff --git a/apps/renderer/src/modules/entry-column/Items/picture-item.tsx b/apps/renderer/src/modules/entry-column/Items/picture-item.tsx index 9e7e2dfec..4f1ce3f9c 100644 --- a/apps/renderer/src/modules/entry-column/Items/picture-item.tsx +++ b/apps/renderer/src/modules/entry-column/Items/picture-item.tsx @@ -73,7 +73,7 @@ export const PictureWaterFallItem = memo(function PictureWaterFallItem({ const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.entries.id) - const previewMedia = usePreviewMedia() + const previewMedia = usePreviewMedia(entryId) const itemWidth = useMasonryItemWidth() const [ref, setRef] = useState(null) diff --git a/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx b/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx index b8ee7d0a8..7aec1d2ed 100644 --- a/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx +++ b/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react" import { useWhoami } from "~/atoms/user" import { useAuthQuery } from "~/hooks/common" +import { cn } from "~/lib/utils" import type { FeedModel } from "~/models" import { Queries } from "~/queries" import { useEntry, useEntryReadHistory } from "~/store/entry" @@ -11,6 +12,7 @@ import { EntryTranslation } from "../../entry-column/translation" interface EntryLinkProps { entryId: string + compact?: boolean } const safeUrl = (url: string, baseUrl: string) => { @@ -21,7 +23,7 @@ const safeUrl = (url: string, baseUrl: string) => { } } -export const EntryTitle = ({ entryId }: EntryLinkProps) => { +export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => { const user = useWhoami() const entry = useEntry(entryId) const feed = useFeedById(entry?.feedId) as FeedModel @@ -62,7 +64,7 @@ export const EntryTitle = ({ entryId }: EntryLinkProps) => { className="-mx-6 block cursor-button rounded-lg p-6 transition-colors hover:bg-theme-item-hover focus-visible:bg-theme-item-hover focus-visible:!outline-none @sm:-mx-3 @sm:p-3" rel="noreferrer" > -
+
{getPreferredTitle(feed)}
diff --git a/apps/renderer/src/modules/entry-content/header.tsx b/apps/renderer/src/modules/entry-content/header.tsx index 88fb21a66..97171e12c 100644 --- a/apps/renderer/src/modules/entry-content/header.tsx +++ b/apps/renderer/src/modules/entry-content/header.tsx @@ -31,10 +31,12 @@ function EntryHeaderImpl({ view, entryId, className, + compact, }: { view: number entryId: string className?: string + compact?: boolean }) { const entry = useEntry(entryId) @@ -90,7 +92,7 @@ function EntryHeaderImpl({
- + {!compact && } {items .filter((item) => !item.hide) diff --git a/apps/renderer/src/modules/entry-content/index.tsx b/apps/renderer/src/modules/entry-content/index.tsx index 783f4e777..c7376142c 100644 --- a/apps/renderer/src/modules/entry-content/index.tsx +++ b/apps/renderer/src/modules/entry-content/index.tsx @@ -50,7 +50,15 @@ import { EntryHeader } from "./header" import { EntryContentLoading } from "./loading" import { EntryContentProvider } from "./provider" -export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => { +export const EntryContent = ({ + entryId, + noMedia, + compact, +}: { + entryId: ActiveEntryId + noMedia?: boolean + compact?: boolean +}) => { const title = useFeedHeaderTitle() const { feedId, view } = useRouteParams() @@ -70,10 +78,14 @@ export const EntryContent = ({ entryId }: { entryId: ActiveEntryId }) => { ) } - return + return } -export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, className }) => { +export const EntryContentRender: Component<{ + entryId: string + noMedia?: boolean + compact?: boolean +}> = ({ entryId, noMedia, className, compact }) => { const { t } = useTranslation() const { error, data, isPending } = useAuthQuery(Queries.entries.byId(entryId), { @@ -148,6 +160,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl entryId={entry.entries.id} view={0} className="h-[55px] shrink-0 px-3 @container" + compact={compact} /> = ({ entryId, cl onContextMenu={stopPropagation} className="relative m-auto min-w-0 max-w-[550px] @3xl:max-w-[70ch]" > - +
@@ -187,6 +200,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl {!isInReadabilityMode ? (