Skip to content

Commit

Permalink
[Upload] support file list thumbnail (Tencent#2665)
Browse files Browse the repository at this point in the history
* feat(image): support fallback & referrerpolicy & src File

* feat(upload): code save

* feat(upload): support file thumnail

* feat(upload): docs & features

* test: update snapshots
  • Loading branch information
chaishi authored and methodchen committed Aug 25, 2023
1 parent 904ea4b commit 21f38d5
Show file tree
Hide file tree
Showing 27 changed files with 1,063 additions and 134 deletions.
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './useLazyLoad';
export * from './useResizeObserver';
export * from './useVirtualScrollNew';
export * from './useVModel';
export * from './useImagePreviewUrl';
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 {
ComputedRef, ref, Ref, watch,
} from '@vue/composition-api';
import { getFileUrlByFileRaw } from '../_common/js/upload/utils';

export function useImagePreviewUrl(imgUrl: Ref<string | File> | ComputedRef<string | File>) {
const previewUrl = ref('');

watch(
[imgUrl],
([imgUrl], [preImgUrl]) => {
if (preImgUrl === imgUrl) return;
if (typeof imgUrl === 'string') {
previewUrl.value = imgUrl;
return;
}
getFileUrlByFileRaw(imgUrl).then((url) => {
previewUrl.value = url;
});
},
{ immediate: true },
);

return { previewUrl };
}

export default useImagePreviewUrl;
22 changes: 15 additions & 7 deletions src/image-viewer/base/ImageItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import {
computed, defineComponent, ref, watch,
computed, defineComponent, PropType, ref, toRefs, watch,
} from '@vue/composition-api';
import { ImageErrorIcon } from 'tdesign-icons-vue';
import { useConfig } from '../../hooks/useConfig';
import { useDrag } from '../hooks';
import { setTransform } from '../../utils/helper';
import { useImagePreviewUrl } from '../../hooks';

export default defineComponent({
name: 'TImageItem',
props: {
rotate: Number,
scale: Number,
mirror: Number,
src: String,
placementSrc: String,
src: [String, File] as PropType<string | File>,
placementSrc: [String, File] as PropType<string | File>,
},

setup(props) {
const { src, placementSrc } = toRefs(props);
const { classPrefix, global: globalConfig } = useConfig('imageViewer');
const error = ref(false);
const loaded = ref(false);
const { transform, mouseDownHandler } = useDrag({ translateX: 0, translateY: 0 });

const { previewUrl: mainImagePreviewUrl } = useImagePreviewUrl(src);
const { previewUrl: placementImagePreviewUrl } = useImagePreviewUrl(placementSrc);

const imgStyle = computed(() => ({
...setTransform(`rotate(${props.rotate}deg) scale(${props.scale})`),
display: !props.placementSrc || loaded.value ? 'block' : 'none',
Expand Down Expand Up @@ -55,6 +61,8 @@ export default defineComponent({
mouseDownHandler,
placementImgStyle,
imgStyle,
mainImagePreviewUrl,
placementImagePreviewUrl,
};
},
render() {
Expand All @@ -71,28 +79,28 @@ export default defineComponent({
</div>
)}

{!this.error && !!this.placementSrc && (
{!this.error && !!this.placementSrc && this.placementImagePreviewUrl && (
<img
class={`${this.classPrefix}-image-viewer__modal-image`}
onMousedown={(event: MouseEvent) => {
event.stopPropagation();
this.mouseDownHandler(event);
}}
src={this.placementSrc}
src={this.placementImagePreviewUrl}
style={this.placementImgStyle}
alt="image"
draggable="false"
/>
)}

{!this.error && (
{!this.error && this.mainImagePreviewUrl && (
<img
class={`${this.classPrefix}-image-viewer__modal-image`}
onMousedown={(event: MouseEvent) => {
event.stopPropagation();
this.mouseDownHandler(event);
}}
src={this.src}
src={this.mainImagePreviewUrl}
onLoad={() => (this.loaded = true)}
onError={() => (this.error = true)}
style={this.imgStyle}
Expand Down
14 changes: 9 additions & 5 deletions src/image-viewer/base/ImageViewerUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { defineComponent, PropType } from '@vue/composition-api';
import { computed, defineComponent, PropType } from '@vue/composition-api';
import {
ImageIcon, ZoomInIcon, ZoomOutIcon, DownloadIcon, MirrorIcon, RotationIcon,
} from 'tdesign-icons-vue';

import TImageViewerIcon from './ImageModalIcon';
import TToolTip from '../../tooltip';
import { useConfig } from '../../hooks/useConfig';
import { downloadFile } from '../utils';

import { useImagePreviewUrl } from '../../hooks';
import { ImageInfo } from '../type';

const currentImage = {
Expand All @@ -28,12 +27,17 @@ export default defineComponent({
resetHandler: Function as PropType<() => void>,
currentImage,
},
setup() {
setup(props) {
const { classPrefix, global: globalConfig } = useConfig('imageViewer');

const imageUrl = computed(() => props.currentImage.mainImage);

const { previewUrl } = useImagePreviewUrl(imageUrl);

return {
classPrefix,
globalConfig,
previewUrl,
};
},
render() {
Expand Down Expand Up @@ -83,7 +87,7 @@ export default defineComponent({
<TImageViewerIcon
icon={() => <DownloadIcon size="medium" />}
clickHandler={() => {
downloadFile(this.currentImage.mainImage);
downloadFile(this.previewUrl);
}}
/>
)}
Expand Down
9 changes: 4 additions & 5 deletions src/image-viewer/image-viewer.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,23 @@ closeBtn | Boolean / Slot / Function | true | Typescript:`boolean \| TNode`。
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-vue/tree/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-vue/tree/develop/src/image-viewer/type.ts) | 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-vue/tree/develop/src/image-viewer/type.ts) | N
index | Number | - | `.sync` is supported | N
defaultIndex | Number | - | uncontrolled property | N
mode | String | modal | optionsmodal/modeless | N
mode | String | modal | options: modal/modeless | N
navigationArrow | Boolean / Slot / Function | true | Typescript:`boolean \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
showOverlay | Boolean | undefined | \- | N
title | String / Slot / Function | - | preview title。Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
trigger | String / Slot / Function | - | trigger element。Typescript:`string \| TNode<{ open: () => void }>`[see more ts definition](https://github.com/Tencent/tdesign-vue/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-vue/tree/develop/src/image-viewer/type.ts) | N
visible | Boolean | false | `v-model` is supported | 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

### ImageViewer Events

name | params | description
-- | -- | --
close | `(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent })` | \-
index-change | `(index: number, context: { trigger: 'prev' \| 'next' })` | \-
index-change | `(index: number, context: { trigger: 'prev' \| 'next' \| 'current' })` | \-
7 changes: 3 additions & 4 deletions src/image-viewer/image-viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ closeBtn | Boolean / Slot / Function | 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-vue/tree/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-vue/tree/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 \| File \| ImageInfo>` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N
index | Number | - | 当前预览图片所在的下标。支持语法糖 `.sync` | N
defaultIndex | Number | - | 当前预览图片所在的下标。非受控属性 | N
mode | String | modal | 模态预览(modal)和非模态预览(modeless)。可选项:modal/modeless | N
Expand All @@ -19,14 +19,13 @@ title | String / Slot / Function | - | 预览标题。TS 类型:`string \| TNo
trigger | String / Slot / Function | - | 触发图片预览的元素,可能是一个预览按钮,可能是一张缩略图,完全自定义。TS 类型:`string \| TNode<{ open: () => void }>`[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
viewerScale | Object | - | 限制预览器缩放的最小宽度和最小高度,仅 `mode=modeless` 时有效。TS 类型:`ImageViewerScale` `interface ImageViewerScale { minWidth: number; minHeight: number }`[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N
visible | Boolean | false | 隐藏/显示预览。支持语法糖 `v-model` | 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

### ImageViewer Events

名称 | 参数 | 描述
-- | -- | --
close | `(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent })` | 关闭时触发,事件参数包含触发关闭的来源:关闭按钮、遮罩层、ESC 键
index-change | `(index: number, context: { trigger: 'prev' \| 'next' })` | 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片
index-change | `(index: number, context: { trigger: 'prev' \| 'next' \| 'current' })` | 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片
7 changes: 3 additions & 4 deletions src/image-viewer/image-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
computed, defineComponent, ref, toRefs, watch,
} from '@vue/composition-api';
import { ChevronLeftIcon, ChevronDownIcon, CloseIcon } from 'tdesign-icons-vue';

import props from './props';
import Container from './base/Container';
import TImageViewerIcon from './base/ImageModalIcon';
Expand All @@ -14,11 +13,11 @@ import useDefaultValue from '../hooks/useDefaultValue';
import { usePrefixClass } from '../hooks/useConfig';
import { renderTNodeJSX } from '../utils/render-tnode';
import { setTransform } from '../utils/helper';

import { TdImageViewerProps } from './type';
import { useMirror, useRotate, useScale } from './hooks';
import { formatImages, getOverlay } from './utils';
import { EVENT_CODE } from './const';
import Image from '../image';

export default defineComponent({
name: 'TImageViewer',
Expand Down Expand Up @@ -87,7 +86,7 @@ export default defineComponent({
};

const onImgClick = (i: number) => {
setIndexValue(i, { trigger: i > indexValue.value ? 'next' : 'prev' });
setIndexValue(i, { trigger: 'current' });
};

const openHandler = () => {
Expand Down Expand Up @@ -223,7 +222,7 @@ export default defineComponent({
},
]}
>
<img
<Image
alt=""
src={image.thumbnail || image.mainImage}
class={`${this.COMPONENT_NAME}__header-img`}
Expand Down
8 changes: 4 additions & 4 deletions src/image-viewer/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ 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>;
/**
* 当前预览图片所在的下标
*/
Expand Down Expand Up @@ -84,7 +84,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 +94,8 @@ export interface ImageScale {
}

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

Expand Down
2 changes: 1 addition & 1 deletion src/image-viewer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const downloadFile = function (imgSrc: string) {
image.src = imgSrc;
};

const isImageInfo = (image: string | ImageInfo): image is ImageInfo => typeof image !== 'string';
const isImageInfo = (image: string | File | ImageInfo): image is ImageInfo => typeof image !== 'string' && !(image instanceof File);

export const formatImages = (images: TdImageViewerProps['images']): ImageInfo[] => {
if (!Array.isArray(images)) return [];
Expand Down
12 changes: 7 additions & 5 deletions src/image/image.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ name | type | default | description | required
-- | -- | -- | -- | --
alt | String | - | \- | N
error | String / Slot / Function | - | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
fit | String | fill | options:contain/cover/fill/none/scale-down | N
fallback | String | - | display `fallback` image on `src` loading failed. you can also use `error` to define more complex error content | N
fit | String | fill | options: contain/cover/fill/none/scale-down | N
gallery | Boolean | false | \- | N
lazy | Boolean | false | \- | N
loading | String / Slot / Function | - | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
overlayContent | String / Slot / Function | - | overlay on the top of image。Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
overlayTrigger | String | always | optionsalways/hover | N
overlayTrigger | String | always | options: always/hover | N
placeholder | String / Slot / Function | - | Typescript:`string \| TNode`[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N
position | String | center | \- | N
shape | String | square | options:circle/round/square | N
src | String | - | \- | N
srcset | Object | - | for `.avif` and `.webp` image url。Typescript:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N
referrerpolicy | String | - | attribute of `<img>`, [MDN Definition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。options: no-referrer/no-referrer-when-downgrade/origin/origin-when-cross-origin/same-origin/strict-origin/strict-origin-when-cross-origin/unsafe-url | N
shape | String | square | options: circle/round/square | N
src | String / File | - | src attribute of `<img>`. image File can also be loaded。Typescript:`string \| File` | N
srcset | Object | - | for `.avif` and `.webp` image url, load `srcset` before `src`。Typescript:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N
onError | Function | | Typescript:`(context: { e: ImageEvent }) => void`<br/>trigger on image load failed | N
onLoad | Function | | Typescript:`(context: { e: ImageEvent }) => void`<br/>trigger on image loaded | N

Expand Down
Loading

0 comments on commit 21f38d5

Please sign in to comment.