From d3fd0d9115f1d7fa561aeb2b7fa3e51ca4bda583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 16 Jun 2023 23:53:23 +0800 Subject: [PATCH] refactor: tmp of selection --- src/Image.tsx | 186 +++++++++++++++++++--------------- src/Preview.tsx | 4 +- src/PreviewGroup.tsx | 116 +++++++++++---------- src/hooks/context.ts | 9 ++ src/hooks/usePreviewItems.ts | 53 ++++++++++ src/hooks/useRegisterImage.ts | 35 +++++++ src/interface.ts | 26 +++++ 7 files changed, 298 insertions(+), 131 deletions(-) create mode 100644 src/hooks/context.ts create mode 100644 src/hooks/usePreviewItems.ts create mode 100644 src/hooks/useRegisterImage.ts create mode 100644 src/interface.ts diff --git a/src/Image.tsx b/src/Image.tsx index 0d08140..b884d06 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -5,7 +5,10 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState'; import type { GetContainer } from 'rc-util/lib/PortalWrapper'; import * as React from 'react'; import { useContext, useEffect, useRef, useState } from 'react'; +import { PreviewGroupContext } from './hooks/context'; import type { TransformType } from './hooks/useImageTransform'; +import useRegisterImage from './hooks/useRegisterImage'; +import { ImageElementProps } from './interface'; import type { PreviewProps, ToolbarRenderType } from './Preview'; import Preview from './Preview'; import PreviewGroup, { context } from './PreviewGroup'; @@ -61,6 +64,18 @@ interface CompoundedComponent

extends React.FC

{ type ImageStatus = 'normal' | 'error' | 'loading'; +const COMMON_PROPS: (keyof ImageElementProps)[] = [ + 'crossOrigin', + 'decoding', + 'draggable', + 'loading', + 'referrerPolicy', + 'sizes', + 'srcSet', + 'useMap', + 'alt', +]; + function isImageValid(src) { return new Promise(resolve => { const img = document.createElement('img'); @@ -70,36 +85,29 @@ function isImageValid(src) { }); } -const ImageInternal: CompoundedComponent = ({ - src: imgSrc, - alt, - onPreviewClose: onInitialPreviewClose, - prefixCls = 'rc-image', - previewPrefixCls = `${prefixCls}-preview`, - placeholder, - fallback, - width, - height, - style, - preview = true, - className, - onClick, - onError, - wrapperClassName, - wrapperStyle, - rootClassName, - - // Img - crossOrigin, - decoding, - loading, - referrerPolicy, - sizes, - srcSet, - useMap, - draggable, - ...otherProps -}) => { +const ImageInternal: CompoundedComponent = props => { + const { + src: imgSrc, + alt, + onPreviewClose: onInitialPreviewClose, + prefixCls = 'rc-image', + previewPrefixCls = `${prefixCls}-preview`, + placeholder, + fallback, + width, + height, + style, + preview = true, + className, + onClick, + onError, + wrapperClassName, + wrapperStyle, + rootClassName, + + ...otherProps + } = props; + const isCustomPlaceholder = placeholder && placeholder !== true; const { src: previewSrc, @@ -128,10 +136,12 @@ const ImageInternal: CompoundedComponent = ({ isPreviewGroup, setShowPreview: setGroupShowPreview, setMousePosition: setGroupMousePosition, - registerImage, setCurrentIndex, getStartPreviewIndex, } = useContext(context); + + const groupContext = useContext(PreviewGroupContext); + const [currentId] = useState(() => { uuid += 1; return uuid; @@ -140,43 +150,10 @@ const ImageInternal: CompoundedComponent = ({ const isLoaded = useRef(false); - const imgCommonProps = { - crossOrigin, - decoding, - draggable, - loading, - referrerPolicy, - sizes, - srcSet, - useMap, - alt, - }; - const onLoad = () => { setStatus('normal'); }; - const onPreview: React.MouseEventHandler = e => { - const { left, top } = getOffset(e.target); - if (isPreviewGroup) { - setGroupMousePosition({ - x: left, - y: top, - }); - - setCurrentIndex(getStartPreviewIndex(currentId)); - setGroupShowPreview(true); - } else { - setMousePosition({ - x: left, - y: top, - }); - setShowPreview(true); - } - - onClick?.(e); - }; - const onPreviewClose = () => { setShowPreview(false); setMousePosition(null); @@ -199,23 +176,23 @@ const ImageInternal: CompoundedComponent = ({ }); }, [src]); - // Keep order start - // Resolve https://github.com/ant-design/ant-design/issues/28881 - // Only need unRegister when component unMount - useEffect(() => { - const unRegister = registerImage(currentId, { - src, - imgCommonProps, - canPreview, - }); + // // Keep order start + // // Resolve https://github.com/ant-design/ant-design/issues/28881 + // // Only need unRegister when component unMount + // useEffect(() => { + // const unRegister = registerImage(currentId, { + // src, + // imgCommonProps, + // canPreview, + // }); - return unRegister; - }, []); + // return unRegister; + // }, []); - useEffect(() => { - registerImage(currentId, { src, imgCommonProps, canPreview }); - }, [src, canPreview, JSON.stringify(imgCommonProps)]); - // Keep order end + // useEffect(() => { + // registerImage(currentId, { src, imgCommonProps, canPreview }); + // }, [src, canPreview, JSON.stringify(imgCommonProps)]); + // // Keep order end useEffect(() => { if (isError) { @@ -232,6 +209,57 @@ const ImageInternal: CompoundedComponent = ({ const mergedSrc = isError && fallback ? fallback : src; + // ========================= ImageProps ========================= + const imgCommonProps = React.useMemo( + () => { + const obj: ImageElementProps = {}; + COMMON_PROPS.forEach((prop: any) => { + if (props[prop] !== undefined) { + obj[prop] = props[prop]; + } + }); + + return obj; + }, + COMMON_PROPS.map(prop => props[prop]), + ); + + // ========================== Register ========================== + const registerData: ImageElementProps = React.useMemo( + () => ({ + ...imgCommonProps, + src, + }), + [src, imgCommonProps], + ); + + const imageId = useRegisterImage(canPreview, registerData); + + // ========================== Preview =========================== + const onPreview: React.MouseEventHandler = e => { + const { left, top } = getOffset(e.target); + if (groupContext) { + // setGroupMousePosition({ + // x: left, + // y: top, + // }); + + // setCurrentIndex(getStartPreviewIndex(currentId)); + // setGroupShowPreview(true); + + groupContext.onPreview(imageId, left, top); + } else { + setMousePosition({ + x: left, + y: top, + }); + setShowPreview(true); + } + + onClick?.(e); + }; + + // =========================== Render =========================== return ( <>

{ flipX?: React.ReactNode; flipY?: React.ReactNode; }; + count?: number; countRender?: (current: number, total: number) => string; scaleStep?: number; minScale?: number; @@ -78,6 +79,7 @@ const Preview: React.FC = props => { icons = {}, rootClassName, getContainer, + count = 1, countRender, scaleStep = 0.5, minScale = 1, @@ -100,7 +102,7 @@ const Preview: React.FC = props => { transformY: 0, }); const [isMoving, setMoving] = useState(false); - const { count, currentIndex, isPreviewGroup, setCurrentIndex } = useContext(context); + const { currentIndex, isPreviewGroup, setCurrentIndex } = useContext(context); const showLeftOrRightSwitches = isPreviewGroup && count > 1; const showOperationsProgress = isPreviewGroup && count >= 1; const { transform, resetTransform, updateTransform, dispatchZoomChange } = useImageTransform( diff --git a/src/PreviewGroup.tsx b/src/PreviewGroup.tsx index f400d68..e692d47 100644 --- a/src/PreviewGroup.tsx +++ b/src/PreviewGroup.tsx @@ -1,9 +1,12 @@ import useMergedState from 'rc-util/lib/hooks/useMergedState'; import * as React from 'react'; import { useEffect, useState } from 'react'; +import { PreviewGroupContext } from './hooks/context'; import type { TransformType } from './hooks/useImageTransform'; import usePreviewInfo from './hooks/usePreviewInfo'; +import usePreviewItems from './hooks/usePreviewItems'; import type { ImagePreviewType } from './Image'; +import { ImageElementProps, OnGroupPreview } from './interface'; import type { PreviewProps, ToolbarRenderType } from './Preview'; import Preview from './Preview'; @@ -28,22 +31,10 @@ export interface PreviewGroupPreview onChange?: (current: number, prevCurrent: number) => void; } -type PickImgHTMLAttributes = - | 'src' - | 'crossOrigin' - | 'alt' - | 'decoding' - | 'draggable' - | 'loading' - | 'referrerPolicy' - | 'sizes' - | 'srcSet' - | 'useMap'; - export interface GroupConsumerProps { previewPrefixCls?: string; icons?: PreviewProps['icons']; - items?: (string | Pick, PickImgHTMLAttributes>)[]; + items?: (string | ImageElementProps)[]; preview?: boolean | PreviewGroupPreview; children?: React.ReactNode; } @@ -56,13 +47,11 @@ export interface PreviewData { export interface GroupConsumerValue extends GroupConsumerProps { isPreviewGroup?: boolean; - count: number; currentIndex: number; getStartPreviewIndex: (currentId: number) => number; setCurrentIndex: React.Dispatch>; setShowPreview: React.Dispatch>; setMousePosition: React.Dispatch>; - registerImage: (id: number, data: PreviewData) => () => void; rootClassName?: string; } @@ -71,10 +60,8 @@ export const context = React.createContext({ currentIndex: null, getStartPreviewIndex: () => null, setCurrentIndex: () => null, - count: null, setShowPreview: () => null, setMousePosition: () => null, - registerImage: () => () => null, rootClassName: '', }); @@ -102,10 +89,17 @@ const Group: React.FC = ({ ...dialogProps } = typeof preview === 'object' ? preview : ({} as PreviewGroupPreview); + // ========================== Items =========================== + const [mergedItems, register] = usePreviewItems(items); + + // ========================= Preview ========================== + // >>> Index const [currentIndex, setCurrentIndex] = useMergedState(0, { value: current, }); + const src = mergedItems[currentIndex]?.src; + // >>> Visible const [isShowPreview, setShowPreview] = useMergedState(!!previewVisible, { value: previewVisible, onChange: (val, prevVal) => { @@ -113,9 +107,21 @@ const Group: React.FC = ({ }, }); + // >>> Position const [mousePosition, setMousePosition] = useState(null); - const { count, src, imgCommonProps, registerImage, getStartPreviewIndex } = usePreviewInfo({ + const onPreviewFromImage = React.useCallback( + (id, mouseX, mouseY) => { + setShowPreview(true); + setMousePosition({ x: mouseX, y: mouseY }); + setCurrentIndex(mergedItems.findIndex(item => item.id === id)); + }, + [mergedItems], + ); + + // ========================== Legacy ========================== + + const { imgCommonProps, registerImage, getStartPreviewIndex } = usePreviewInfo({ items, currentIndex, }); @@ -134,40 +140,48 @@ const Group: React.FC = ({ } }, [isShowPreview, isControlledCurrent]); + // ========================= Context ========================== + const previewGroupContext = React.useMemo( + () => ({ register, onPreview: onPreviewFromImage }), + [register, onPreviewFromImage], + ); + + // ========================== Render ========================== return ( - - {children} - - + + + {children} + + + ); }; diff --git a/src/hooks/context.ts b/src/hooks/context.ts new file mode 100644 index 0000000..5a26f9e --- /dev/null +++ b/src/hooks/context.ts @@ -0,0 +1,9 @@ +import * as React from 'react'; +import type { OnGroupPreview, RegisterImage } from '../interface'; + +export interface PreviewGroupContextProps { + register: RegisterImage; + onPreview: OnGroupPreview; +} + +export const PreviewGroupContext = React.createContext(null); diff --git a/src/hooks/usePreviewItems.ts b/src/hooks/usePreviewItems.ts new file mode 100644 index 0000000..83dea0f --- /dev/null +++ b/src/hooks/usePreviewItems.ts @@ -0,0 +1,53 @@ +import * as React from 'react'; +import type { PreviewImageElementProps, RegisterImage } from '../interface'; +import type { GroupConsumerProps } from '../PreviewGroup'; + +export type Items = (PreviewImageElementProps & { + id: number; +})[]; + +/** + * Merge props provided `items` or context collected images + */ +export default function usePreviewItems( + items?: GroupConsumerProps['items'], +): [items: Items, registerImage: RegisterImage] { + // Context collection image data + const [images, setImages] = React.useState>({}); + + const registerImage = React.useCallback((id, data) => { + setImages(imgs => ({ + ...imgs, + [id]: data, + })); + + return () => { + setImages(imgs => { + const cloneImgs = { ...imgs }; + delete cloneImgs[id]; + return cloneImgs; + }); + }; + }, []); + + // items + const mergedItems = React.useMemo(() => { + if (items) { + return items.map((item, index) => { + const itemObj = typeof item === 'string' ? { src: item } : item; + + return { + ...itemObj, + id: index, + }; + }); + } + + return Object.keys(images).map(id => ({ + ...images[id], + id: Number(id), + })); + }, [images]); + + return [mergedItems, registerImage]; +} diff --git a/src/hooks/useRegisterImage.ts b/src/hooks/useRegisterImage.ts new file mode 100644 index 0000000..aef18a7 --- /dev/null +++ b/src/hooks/useRegisterImage.ts @@ -0,0 +1,35 @@ +import * as React from 'react'; +import type { ImageElementProps } from '../interface'; +import { PreviewGroupContext } from './context'; + +let uid = 0; + +export default function useRegisterImage(canPreview: boolean, imgData: ImageElementProps) { + const [id] = React.useState(() => { + uid += 1; + return uid; + }); + const groupContext = React.useContext(PreviewGroupContext); + + const registerData = { + ...imgData, + canPreview, + }; + + // Keep order start + // Resolve https://github.com/ant-design/ant-design/issues/28881 + // Only need unRegister when component unMount + React.useEffect(() => { + if (groupContext) { + return groupContext.register(id, registerData); + } + }, [canPreview]); + + React.useEffect(() => { + if (groupContext) { + return groupContext.register(id, registerData); + } + }, [imgData]); + + return id; +} diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..a10c399 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1,26 @@ +import * as React from 'react'; + +/** + * Used for PreviewGroup passed image data + */ +export type ImageElementProps = Pick< + React.ImgHTMLAttributes, + | 'src' + | 'crossOrigin' + | 'decoding' + | 'draggable' + | 'loading' + | 'referrerPolicy' + | 'sizes' + | 'srcSet' + | 'useMap' + | 'alt' +>; + +export type PreviewImageElementProps = ImageElementProps & { + canPreview: boolean; +}; + +export type RegisterImage = (id: number, data: PreviewImageElementProps) => VoidFunction; + +export type OnGroupPreview = (id: number, mouseX: number, mouseY: number) => void;