Skip to content

Commit

Permalink
Upload support file list thumbnail; Image and ImageViewer support src…
Browse files Browse the repository at this point in the history
… is File type (#2494)

* feat(image): support Image.fallback or Image.src is a type of File

* feat(image-viewer): support File

* fix(image): fallback

* fix(image): set hasError to be false on src change

* feat(upload): thunbnail

* fix(upload): image preview

* fix(upload): preview click

* fix(upload): preview

* test: update snapshots
  • Loading branch information
chaishi authored Sep 13, 2023
1 parent 25252f7 commit 8cb757e
Show file tree
Hide file tree
Showing 30 changed files with 1,308 additions and 228 deletions.
27 changes: 27 additions & 0 deletions src/hooks/useImagePreviewUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
import log from '../_common/js/log';
import { getFileUrlByFileRaw } from '../_common/js/upload/utils';

export function useImagePreviewUrl(imgUrl: string | File) {
const [previewUrl, setPreviewUrl] = useState('');

useEffect(() => {
if (!imgUrl) return;
if (typeof imgUrl === 'string') {
setPreviewUrl(imgUrl);
return;
}
getFileUrlByFileRaw(imgUrl).then(
(url) => {
setPreviewUrl(url);
},
() => {
log.error('Image', 'Image.src is not a valid file');
},
);
}, [imgUrl]);

return { previewUrl };
}

export default useImagePreviewUrl;
2 changes: 1 addition & 1 deletion src/image-viewer/ImageViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const ImageViewer: React.FC<ImageViewerProps> = (originalProps) => {
return (
<>
{uiImage}
{visibled &&
{(visibled || visible) &&
createPortal(
<ImageModal
title={title}
Expand Down
31 changes: 19 additions & 12 deletions src/image-viewer/ImageViewerModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RotationIcon as TdRotationIcon,
} from 'tdesign-icons-react';
import classNames from 'classnames';
import useImagePreviewUrl from '../hooks/useImagePreviewUrl';
import { TooltipLite } from '../tooltip';
import useConfig from '../hooks/useConfig';
import { useLocaleReceiver } from '../locale/LocalReceiver';
Expand All @@ -22,6 +23,7 @@ import useRotate from './hooks/useRotate';
import useScale from './hooks/useScale';
import useGlobalIcon from '../hooks/useGlobalIcon';
import useIconMap from './hooks/useIconMap';
import Image from '../image';

const ImageError = ({ errorText }: { errorText: string }) => {
const { classPrefix } = useConfig();
Expand All @@ -42,8 +44,8 @@ interface ImageModelItemProps {
rotateZ: number;
scale: number;
mirror: number;
src: string;
preSrc?: string;
src: string | File;
preSrc?: string | File;
errorText: string;
}

Expand All @@ -62,9 +64,12 @@ export const ImageModelItem: React.FC<ImageModelItemProps> = ({ rotateZ, scale,
const preImgStyle = { transform: `rotateZ(${rotateZ}deg) scale(${scale})`, display: !loaded ? 'block' : 'none' };
const boxStyle = { transform: `translate(${position[0]}px, ${position[1]}px) scale(${mirror}, 1)` };

const { previewUrl: preSrcImagePreviewUrl } = useImagePreviewUrl(preSrc);
const { previewUrl: mainImagePreviewUrl } = useImagePreviewUrl(src);

useEffect(() => {
setError(false);
}, [src]);
}, [preSrcImagePreviewUrl, mainImagePreviewUrl]);

return (
<div className={`${classPrefix}-image-viewer__modal-pic`}>
Expand All @@ -77,7 +82,7 @@ export const ImageModelItem: React.FC<ImageModelItemProps> = ({ rotateZ, scale,
event.stopPropagation();
onMouseDown(event);
}}
src={preSrc}
src={preSrcImagePreviewUrl}
style={preImgStyle}
data-testid="img-drag"
alt="image"
Expand All @@ -91,7 +96,7 @@ export const ImageModelItem: React.FC<ImageModelItemProps> = ({ rotateZ, scale,
event.stopPropagation();
onMouseDown(event);
}}
src={src}
src={mainImagePreviewUrl}
onLoad={() => setLoaded(true)}
onError={() => setError(true)}
style={imgStyle}
Expand Down Expand Up @@ -220,11 +225,16 @@ export const ImageViewerUtils: React.FC<ImageViewerUtilsProps> = ({
};

type ImageViewerHeaderProps = {
onImgClick: (index: number) => void;
onImgClick: (index: number, ctx: { trigger: 'current' }) => void;
images: ImageInfo[];
currentIndex: number;
};

function OneImagePreview({ image, classPrefix }: { image: ImageInfo; classPrefix: string }) {
const { previewUrl } = useImagePreviewUrl(image.thumbnail || image.mainImage);
return <Image alt="" src={previewUrl} className={`${classPrefix}-image-viewer__header-img`} />;
}

const ImageViewerHeader = (props: ImageViewerHeaderProps) => {
const { classPrefix } = useConfig();
const { images, currentIndex, onImgClick } = props;
Expand Down Expand Up @@ -253,13 +263,9 @@ const ImageViewerHeader = (props: ImageViewerHeaderProps) => {
className={classNames(`${classPrefix}-image-viewer__header-box`, {
[`${classPrefix}-is-active`]: index === currentIndex,
})}
onClick={() => onImgClick(index)}
onClick={() => onImgClick(index, { trigger: 'current' })}
>
<img
alt=""
src={image.thumbnail || image.mainImage}
className={`${classPrefix}-image-viewer__header-img`}
/>
<OneImagePreview image={image} classPrefix={classPrefix} />
</div>
))}
</div>
Expand Down Expand Up @@ -369,6 +375,7 @@ export const ImageModal: React.FC<ImageModalProps> = (props) => {
if (!isArray(images) || images.length < 1) return null;

const currentImage: ImageInfo = images[index];

const tipText = {
mirror: t(locale.mirrorTipText),
rotate: t(locale.rotateTipText),
Expand Down
3 changes: 3 additions & 0 deletions src/image-viewer/_example/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export default function BasicImageViewer() {
return (
<Space breakLine size={16}>
<ImageViewer trigger={trigger} images={[img]}/>

{/* TODO: fix visible=true can not show image previewer */}
{/* <ImageViewer images={[img]} visible={true} /> */}
</Space>
);
}
3 changes: 2 additions & 1 deletion src/image-viewer/defaultProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export const imageViewerDefaultProps: TdImageViewerProps = {
closeBtn: true,
draggable: undefined,
images: [],
defaultIndex: 0,
mode: 'modal',
navigationArrow: true,
showOverlay: undefined,
defaultVisible: false,
defaultIndex: 0,
};
13 changes: 6 additions & 7 deletions src/image-viewer/hooks/useList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import { ImageInfo } from '../type';

const checkImages = (images) =>
images.map((image) => {
const result: ImageInfo = { mainImage: '' };
if (typeof image === 'string' || !image) result.mainImage = image;
else {
result.mainImage = image.mainImage;
result.thumbnail = image.thumbnail;
result.download = image.download;
let result: ImageInfo = { mainImage: '' };
if (typeof image === 'object' && !(image instanceof File)) {
result = image;
} else {
result.mainImage = image;
result.thumbnail = image;
}
return result;
});

// 业务组件
const useList = (images) => {
const [list, setList] = useState(() => checkImages(images));

Expand Down
12 changes: 6 additions & 6 deletions src/image-viewer/image-viewer.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ closeBtn | TNode | true | Typescript:`boolean \| TNode`。[see more ts definit
closeOnOverlay | Boolean | - | \- | N
draggable | Boolean | undefined | \- | N
imageScale | Object | - | Typescript:`ImageScale` `interface ImageScale { max: number; min: number; step: number }`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
images | Array | [] | Typescript:`Array<string \| ImageInfo>` `interface ImageInfo { mainImage: string; thumbnail?: string; download?: boolean }`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
index | Number | - | \- | N
defaultIndex | Number | - | uncontrolled property | N
mode | String | modal | optionsmodal/modeless | N
images | Array | [] | Typescript:`Array<string \| File \| ImageInfo>` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
index | Number | 0 | \- | N
defaultIndex | Number | 0 | uncontrolled property | N
mode | String | modal | options: modal/modeless | N
navigationArrow | TNode | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
showOverlay | Boolean | undefined | \- | N
title | TNode | - | preview title。Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
trigger | TNode | - | trigger element。Typescript:`string \| TNode<{ open: () => void }>`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
trigger | TNode | - | trigger element。Typescript:`TNode \| TNode<{ open: () => void }>`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
viewerScale | Object | - | Typescript:`ImageViewerScale` `interface ImageViewerScale { minWidth: number; minHeight: number }`[see more ts definition](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
visible | Boolean | false | \- | N
defaultVisible | Boolean | false | uncontrolled property | N
zIndex | Number | - | \- | N
onClose | Function | | Typescript:`(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent }) => void`<br/> | N
onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/> | N
onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`<br/> | N
10 changes: 5 additions & 5 deletions src/image-viewer/image-viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ closeBtn | TNode | true | 是否展示关闭按钮,值为 `true` 显示默认
closeOnOverlay | Boolean | - | 是否在点击遮罩层时,触发预览关闭 | N
draggable | Boolean | undefined | 是否允许拖拽调整位置。`mode=modal` 时,默认不允许拖拽;`mode=modeless` 时,默认允许拖拽 | N
imageScale | Object | - | 图片缩放相关配置。`imageScale.max` 缩放的最大比例;`imageScale.min` 缩放的最小比例;`imageScale.step` 缩放的步长速度。TS 类型:`ImageScale` `interface ImageScale { max: number; min: number; step: number }`[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
images | Array | [] | 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']``[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`。TS 类型:`Array<string \| ImageInfo>` `interface ImageInfo { mainImage: string; thumbnail?: string; download?: boolean }`[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
index | Number | - | 当前预览图片所在的下标 | N
defaultIndex | Number | - | 当前预览图片所在的下标。非受控属性 | N
images | Array | [] | 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']``[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`。TS 类型:`Array<string \| File \| ImageInfo>` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
index | Number | 0 | 当前预览图片所在的下标 | N
defaultIndex | Number | 0 | 当前预览图片所在的下标。非受控属性 | N
mode | String | modal | 模态预览(modal)和非模态预览(modeless)。可选项:modal/modeless | N
navigationArrow | TNode | true | 切换预览图片的左图标,可自定义。TS 类型:`boolean \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
showOverlay | Boolean | undefined | 是否显示遮罩层。`mode=modal` 时,默认显示;`mode=modeless` 时,默认不显示 | N
title | TNode | - | 预览标题。TS 类型:`string \| TNode`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
trigger | TNode | - | 触发图片预览的元素,可能是一个预览按钮,可能是一张缩略图,完全自定义。TS 类型:`string \| TNode<{ open: () => void }>`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
trigger | TNode | - | 触发图片预览的元素,可能是一个预览按钮,可能是一张缩略图,完全自定义。TS 类型:`TNode \| TNode<{ open: () => void }>`[通用类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/common.ts) | N
viewerScale | Object | - | 限制预览器缩放的最小宽度和最小高度,仅 `mode=modeless` 时有效。TS 类型:`ImageViewerScale` `interface ImageViewerScale { minWidth: number; minHeight: number }`[详细类型定义](https://github.com/Tencent/tdesign-react/blob/develop/src/image-viewer/type.ts) | N
visible | Boolean | false | 隐藏/显示预览 | N
defaultVisible | Boolean | false | 隐藏/显示预览。非受控属性 | N
zIndex | Number | - | 层级,默认为 2000 | N
onClose | Function | | TS 类型:`(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent }) => void`<br/>关闭时触发,事件参数包含触发关闭的来源:关闭按钮、遮罩层、ESC 键 | N
onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`<br/>预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N
onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`<br/>预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N
13 changes: 8 additions & 5 deletions src/image-viewer/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ export interface TdImageViewerProps {
* 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']`,`[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`
* @default []
*/
images?: Array<string | ImageInfo>;
images?: Array<string | File | ImageInfo>;
/**
* 当前预览图片所在的下标
* @default 0
*/
index?: number;
/**
* 当前预览图片所在的下标,非受控属性
* @default 0
*/
defaultIndex?: number;
/**
Expand All @@ -45,6 +47,7 @@ export interface TdImageViewerProps {
mode?: 'modal' | 'modeless';
/**
* 切换预览图片的左图标,可自定义
* @default true
*/
navigationArrow?: TNode;
/**
Expand All @@ -58,7 +61,7 @@ export interface TdImageViewerProps {
/**
* 触发图片预览的元素,可能是一个预览按钮,可能是一张缩略图,完全自定义
*/
trigger?: TNode | TNode<{ onOpen: () => void }>;
trigger?: TNode | TNode<{ open: () => void }>;
/**
* 限制预览器缩放的最小宽度和最小高度,仅 `mode=modeless` 时有效
*/
Expand All @@ -84,7 +87,7 @@ export interface TdImageViewerProps {
/**
* 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片
*/
onIndexChange?: (index: number, context: { trigger: 'prev' | 'next' }) => void;
onIndexChange?: (index: number, context: { trigger: 'prev' | 'next' | 'current' }) => void;
}

export interface ImageScale {
Expand All @@ -94,8 +97,8 @@ export interface ImageScale {
}

export interface ImageInfo {
mainImage: string;
thumbnail?: string;
mainImage: string | File;
thumbnail?: string | File;
download?: boolean;
}

Expand Down
Loading

0 comments on commit 8cb757e

Please sign in to comment.