fix: preview image

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-07-04 11:44:27 +08:00
parent 41c7e80873
commit d007d90392
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
4 changed files with 242 additions and 213 deletions

View File

@ -79,8 +79,9 @@ export function SwipeImages({
height: 600,
}}
disableContextMenu
onClick={() => {
onClick={(e) => {
onPreview?.(images, i)
e.stopPropagation()
}}
/>
</SwiperSlide>
@ -104,7 +105,10 @@ export function SwipeImages({
) : uniqImages?.length >= 1 ?
(
<Image
onClick={() => onPreview?.(uniqImages)}
onClick={(e) => {
onPreview?.(uniqImages)
e.stopPropagation()
}}
className="size-full rounded-none object-cover sm:transition-transform sm:duration-300 sm:ease-in-out sm:group-hover:scale-105"
alt="cover"
src={uniqImages[0]}

View File

@ -8,6 +8,7 @@ import { useSetAtom } from "jotai"
import type { PointerEventHandler, SyntheticEvent } from "react"
import {
createElement,
forwardRef,
Fragment,
memo,
useCallback,
@ -33,220 +34,229 @@ export const ModalInternal: Component<{
isTop: boolean
onClose?: (open: boolean) => void
}> = memo(function Modal({
item,
index,
onClose: onPropsClose,
children,
isTop,
}) {
const setStack = useSetAtom(modalStackAtom)
const close = useEventCallback(() => {
setStack((p) => p.filter((modal) => modal.id !== item.id))
onPropsClose?.(false)
})
}> = memo(
forwardRef(function Modal(
{ item, index, onClose: onPropsClose, children, isTop },
ref: any,
) {
const setStack = useSetAtom(modalStackAtom)
const close = useEventCallback(() => {
setStack((p) => p.filter((modal) => modal.id !== item.id))
onPropsClose?.(false)
})
const onClose = useCallback(
(open: boolean): void => {
if (!open) {
const onClose = useCallback(
(open: boolean): void => {
if (!open) {
close()
}
},
[close],
)
const opaque = useUISettingKey("modalOpaque")
const {
CustomModalComponent,
modalClassName,
content,
title,
clickOutsideToDismiss,
modalContainerClassName,
wrapper: Wrapper = Fragment,
max,
icon,
draggable = false,
} = item
const zIndexStyle = useMemo(
() => ({ zIndex: MODAL_STACK_Z_INDEX + index + 1 }),
[index],
)
const dismiss = useCallback(
(e: SyntheticEvent) => {
e.stopPropagation()
close()
}
},
[close],
)
},
[close],
)
const opaque = useUISettingKey("modalOpaque")
const {
CustomModalComponent,
modalClassName,
content,
title,
clickOutsideToDismiss,
modalContainerClassName,
wrapper: Wrapper = Fragment,
max,
icon,
draggable = false,
} = item
const zIndexStyle = useMemo(() => ({ zIndex: MODAL_STACK_Z_INDEX + index + 1 }), [index])
const dismiss = useCallback(
(e: SyntheticEvent) => {
e.stopPropagation()
close()
},
[close],
)
const animateController = useAnimationControls()
useEffect(() => {
requestAnimationFrame(() => {
animateController.start(modalMontionConfig.animate)
})
}, [animateController])
const noticeModal = useCallback(() => {
animateController
.start({
scale: 1.05,
transition: {
duration: 0.06,
},
const animateController = useAnimationControls()
useEffect(() => {
requestAnimationFrame(() => {
animateController.start(modalMontionConfig.animate)
})
.then(() => {
animateController.start({
scale: 1,
}, [animateController])
const noticeModal = useCallback(() => {
animateController
.start({
scale: 1.05,
transition: {
duration: 0.06,
},
})
.then(() => {
animateController.start({
scale: 1,
})
})
}, [animateController])
const dragController = useDragControls()
const handleDrag: PointerEventHandler<HTMLDivElement> = useCallback(
(e) => {
if (draggable) {
dragController.start(e)
}
},
[dragController, draggable],
)
useEffect(() => {
if (isTop) return
animateController.start({
scale: 0.96,
y: 10,
})
}, [animateController])
const dragController = useDragControls()
const handleDrag: PointerEventHandler<HTMLDivElement> = useCallback(
(e) => {
if (draggable) {
dragController.start(e)
return () => {
try {
animateController.stop()
animateController.start({
scale: 1,
y: 0,
})
} catch {
/* empty */
}
}
},
[dragController, draggable],
)
}, [isTop])
useEffect(() => {
if (isTop) return
animateController.start({
scale: 0.96,
y: 10,
})
return () => {
try {
animateController.stop()
animateController.start({
scale: 1,
y: 0,
})
} catch {
/* empty */
}
const modalContentRef = useRef<HTMLDivElement>(null)
const ModalProps: ModalContentPropsInternal = useMemo(
() => ({
dismiss: close,
}),
[close],
)
const ModalContextProps = useMemo<CurrentModalContentProps>(
() => ({
...ModalProps,
ref: modalContentRef,
}),
[ModalProps],
)
const finalChildren = (
<CurrentModalContext.Provider value={ModalContextProps}>
{children ?? createElement(content, ModalProps)}
</CurrentModalContext.Provider>
)
const edgeElementRef = useRef<HTMLDivElement>(null)
if (CustomModalComponent) {
return (
<Wrapper>
<Dialog.Root open onOpenChange={onClose}>
<Dialog.Portal>
<Dialog.DialogTitle className="sr-only">
{title}
</Dialog.DialogTitle>
<Dialog.Content asChild>
<div
className={cn(
"fixed inset-0 z-20 overflow-auto",
modalContainerClassName,
)}
onClick={clickOutsideToDismiss ? dismiss : undefined}
style={zIndexStyle}
>
<div
className={cn("contents", modalClassName)}
onClick={stopPropagation}
ref={ref}
>
<CustomModalComponent>{finalChildren}</CustomModalComponent>
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Wrapper>
)
}
}, [isTop])
const modalContentRef = useRef<HTMLDivElement>(null)
const ModalProps: ModalContentPropsInternal = useMemo(
() => ({
dismiss: close,
}),
[close],
)
const ModalContextProps = useMemo<CurrentModalContentProps>(
() => ({
...ModalProps,
ref: modalContentRef,
}),
[ModalProps],
)
const finalChildren = (
<CurrentModalContext.Provider value={ModalContextProps}>
{children ?? createElement(content, ModalProps)}
</CurrentModalContext.Provider>
)
const edgeElementRef = useRef<HTMLDivElement>(null)
if (CustomModalComponent) {
return (
<Wrapper>
<Dialog.Root open onOpenChange={onClose}>
<Dialog.Portal>
<Dialog.DialogTitle className="sr-only">{title}</Dialog.DialogTitle>
<Dialog.Content asChild>
<div
ref={edgeElementRef}
style={zIndexStyle}
className={cn(
"fixed inset-0 z-20 overflow-auto",
"center fixed inset-0 z-20 flex",
modalContainerClassName,
)}
onClick={clickOutsideToDismiss ? dismiss : undefined}
style={zIndexStyle}
onClick={clickOutsideToDismiss ? dismiss : noticeModal}
>
<div
className={cn("contents", modalClassName)}
<m.div
ref={ref}
style={zIndexStyle}
{...modalMontionConfig}
animate={animateController}
className={cn(
"relative flex flex-col overflow-hidden rounded-lg",
opaque ?
"bg-theme-modal-background-opaque" :
"bg-theme-modal-background backdrop-blur-sm",
"shadow-modal p-2",
max ?
"h-[90vh] w-[90vw]" :
"max-h-[70vh] min-w-[300px] max-w-[90vw] lg:max-h-[calc(100vh-20rem)] lg:max-w-[70vw]",
"border border-slate-200 dark:border-neutral-800",
modalClassName,
)}
onClick={stopPropagation}
drag
dragControls={dragController}
dragElastic={0}
dragListener={false}
dragMomentum={false}
dragConstraints={edgeElementRef}
whileDrag={{
cursor: "grabbing",
}}
>
<CustomModalComponent>{finalChildren}</CustomModalComponent>
</div>
<div
className="relative flex items-center"
onPointerDownCapture={handleDrag}
>
<Dialog.Title className="flex shrink-0 grow items-center gap-2 px-4 py-1 text-lg font-semibold">
{icon && <span className="size-4">{icon}</span>}
<span>{title}</span>
</Dialog.Title>
<Dialog.DialogClose
className="center p-2"
tabIndex={1}
onClick={close}
>
<i className="i-mgc-close-cute-re" />
</Dialog.DialogClose>
</div>
<Divider className="my-2 shrink-0 border-slate-200 opacity-80 dark:border-neutral-800" />
<div className="min-h-0 shrink grow overflow-auto px-4 py-2">
{finalChildren}
</div>
</m.div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Wrapper>
)
}
return (
<Wrapper>
<Dialog.Root open onOpenChange={onClose}>
<Dialog.Portal>
<Dialog.Content asChild>
<div
ref={edgeElementRef}
style={zIndexStyle}
className={cn(
"center fixed inset-0 z-20 flex",
modalContainerClassName,
)}
onClick={clickOutsideToDismiss ? dismiss : noticeModal}
>
<m.div
style={zIndexStyle}
{...modalMontionConfig}
animate={animateController}
className={cn(
"relative flex flex-col overflow-hidden rounded-lg",
opaque ?
"bg-theme-modal-background-opaque" :
"bg-theme-modal-background backdrop-blur-sm",
"shadow-modal p-2",
max ?
"h-[90vh] w-[90vw]" :
"max-h-[70vh] min-w-[300px] max-w-[90vw] lg:max-h-[calc(100vh-20rem)] lg:max-w-[70vw]",
"border border-slate-200 dark:border-neutral-800",
modalClassName,
)}
onClick={stopPropagation}
drag
dragControls={dragController}
dragElastic={0}
dragListener={false}
dragMomentum={false}
dragConstraints={edgeElementRef}
whileDrag={{
cursor: "grabbing",
}}
>
<div
className="relative flex items-center"
onPointerDownCapture={handleDrag}
>
<Dialog.Title className="flex shrink-0 grow items-center gap-2 px-4 py-1 text-lg font-semibold">
{icon && <span className="size-4">{icon}</span>}
<span>{title}</span>
</Dialog.Title>
<Dialog.DialogClose className="center p-2" tabIndex={1} onClick={close}>
<i className="i-mgc-close-cute-re" />
</Dialog.DialogClose>
</div>
<Divider className="my-2 shrink-0 border-slate-200 opacity-80 dark:border-neutral-800" />
<div className="min-h-0 shrink grow overflow-auto px-4 py-2">
{finalChildren}
</div>
</m.div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</Wrapper>
)
})
}),
)

View File

@ -1,23 +1,25 @@
import { m } from "@renderer/components/common/Motion"
import type { ForwardedRef } from "react"
import { forwardRef } from "react"
import { RootPortal } from "../../portal"
export const ModalOverlay = ({
onClick,
zIndex,
}: {
onClick?: () => void
zIndex?: number
}) => (
<RootPortal>
<m.div
id="modal-overlay"
onClick={onClick}
className="fixed inset-0 z-[11] bg-zinc-50/80 dark:bg-neutral-900/80"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{ zIndex }}
/>
</RootPortal>
export const ModalOverlay = forwardRef(
(
{ onClick, zIndex }: { onClick?: () => void, zIndex?: number },
ref: ForwardedRef<HTMLDivElement>,
) => (
<RootPortal>
<m.div
ref={ref}
id="modal-overlay"
onClick={onClick}
className="fixed inset-0 z-[11] bg-zinc-50/80 dark:bg-neutral-900/80"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{ zIndex }}
/>
</RootPortal>
),
)

View File

@ -8,19 +8,32 @@ import { useEntry } from "@renderer/store/entry/hooks"
import { usePreviewImages } from "../../components/ui/image/hooks"
import type { UniversalItemProps } from "./types"
export function PictureItem({ entryId, entryPreview, translation }: UniversalItemProps) {
export function PictureItem({
entryId,
entryPreview,
translation,
}: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.entries.id)
const isActive = useRouteParamsSelector(
({ entryId }) => entryId === entry?.entries.id,
)
const previewImage = usePreviewImages()
if (!entry) return <ReactVirtuosoItemPlaceholder />
return (
<GridItem entryId={entryId} entryPreview={entryPreview} translation={translation}>
<GridItem
entryId={entryId}
entryPreview={entryPreview}
translation={translation}
>
<div className="relative flex gap-2 overflow-x-auto">
<SwipeImages
images={entry.entries.images}
className={cn("aspect-square w-full shrink-0 rounded-md", isActive && "rounded-b-none")}
className={cn(
"aspect-square w-full shrink-0 rounded-md",
isActive && "rounded-b-none",
)}
imgClassName="object-cover"
uniqueKey={entryId}
onPreview={(images, i) => {