fix: 优化附件显示 (#1545)

Co-authored-by: sealday <zhanglin@daoyoucloud.com>
Reviewed-on: daoyoucloud/tachybase#1545
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: wjh <wwwjh0710@163.com>
Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
wjh 2024-09-24 18:23:40 +08:00 committed by sealday
parent 2b698c6c3d
commit 2e15125445
10 changed files with 528 additions and 396 deletions

View File

@ -18,6 +18,7 @@ import { i18n } from '../i18n';
import { CSSVariableProvider } from '../style/css-variable';
import { AntdAppProvider, GlobalThemeProvider } from '../style/theme';
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
import { AttachmentPreviewManager, PluginAttachmentItemsOptions } from './AttachmentPreviewManager';
import { AppComponent, BlankComponent, defaultAppComponents } from './components';
import { NoticeManager } from './NoticesManager';
import type { Plugin } from './Plugin';
@ -63,6 +64,7 @@ export interface ApplicationOptions {
devDynamicImport?: DevDynamicImport;
dataSourceManager?: DataSourceManagerOptions;
pluginMenuItems?: Record<string, PluginItemsOptions>;
attachmentItem?: Record<string, PluginAttachmentItemsOptions>;
}
export class Application {
@ -89,7 +91,7 @@ export class Application {
public dataSourceManager: DataSourceManager;
public noticeManager: NoticeManager;
public pluginContextMenu: PluginContextMenu;
public AttachmentPreviewManager: AttachmentPreviewManager;
public name: string;
loading = true;
@ -137,6 +139,7 @@ export class Application {
this.addRoutes();
this.name = this.options.name || getSubAppName(options.publicPath) || 'main';
this.pluginContextMenu = new PluginContextMenu(options.pluginMenuItems, this);
this.AttachmentPreviewManager = new AttachmentPreviewManager(options.attachmentItem, this);
}
private initRequireJs() {

View File

@ -0,0 +1,32 @@
import type { Application } from './Application';
export interface PluginAttachmentItemsOptions {
key: string;
type: string;
viewComponet: (any) => JSX.Element;
checkedComponent: (any) => JSX.Element;
}
export class AttachmentPreviewManager {
protected attachmentItem: Record<string, PluginAttachmentItemsOptions> = {};
public app: Application;
constructor(_pluginAttachmentItems: Record<string, PluginAttachmentItemsOptions>, app: Application) {
this.app = app;
Object.entries(_pluginAttachmentItems || {}).forEach(([name, PluginItemsOptions]) => {
this.add(PluginItemsOptions);
});
}
get() {
return this.attachmentItem;
}
getTypeItem(key) {
return this.attachmentItem[key];
}
add(options: PluginAttachmentItemsOptions) {
this.attachmentItem[options.key] = options;
}
}

View File

@ -43,6 +43,10 @@ export class Plugin<T = any> {
return this.app.dataSourceManager;
}
get AttachmentPreviewManager() {
return this.app.AttachmentPreviewManager;
}
async afterAdd() {}
async beforeLoad() {}

View File

@ -12,3 +12,4 @@ export * from './NoticesManager';
export * from './hoc';
export { ApplicationContext } from './context';
export * from './PluginContextMenu';
export * from './AttachmentPreviewManager';

View File

@ -0,0 +1,146 @@
import React from 'react';
import { FileFilled } from '@ant-design/icons';
import { saveAs } from 'file-saver';
export const imagejpeg = {
key: 'image/jpeg',
type: 'image/jpeg',
viewComponet: (props) => {
const { file, prefixCls } = props;
return (
file.imageUrl && (
<img
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
style={{ width: '100%', height: '100%' }}
alt={file.title}
className={`${prefixCls}-list-item-image`}
/>
)
);
},
checkedComponent: (props) => {
const { file } = props;
return (
file.imageUrl && (
<img
{...props}
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
alt={file.title}
style={{
width: '90%',
height: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
/>
)
);
},
};
export const imagePng = {
key: 'image/png',
type: 'image/png',
viewComponet: (props) => {
const { file, prefixCls } = props;
return (
file.imageUrl && (
<img
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
style={{ width: '100%', height: '100%' }}
alt={file.title}
className={`${prefixCls}-list-item-image`}
/>
)
);
},
checkedComponent: (props) => {
const { file } = props;
return (
file.imageUrl && (
<img
{...props}
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
alt={file.title}
style={{
width: '90%',
height: 'auto',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
/>
)
);
},
};
export const fileDef = {
key: 'default',
type: 'default',
viewComponet: (props) => {
const { prefixCls } = props;
return (
<FileFilled
className={`${prefixCls}-list-item-image`}
style={{ width: '100%', height: '100%', lineHeight: '100%', color: '#000000' }}
/>
);
},
checkedComponent: (props) => {
return (
<div {...props}>
<FileFilled style={{ color: '#ffffff', fontSize: '30rem', display: 'block', lineHeight: '100vh' }} />
</div>
);
},
};
export const filePdf = {
key: 'application/pdf',
type: 'application/pdf',
viewComponet: (props) => {
const { file, prefixCls } = props;
return (
file.imageUrl && (
<img
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
style={{ width: '100%', height: '100%' }}
alt={file.title}
className={`${prefixCls}-list-item-image`}
/>
)
);
},
checkedComponent: (props) => {
const { file } = props;
return (
<div
{...props}
style={{
maxWidth: '100%',
maxHeight: '100%',
height: '100%',
width: '100%',
background: 'white',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
overflowY: 'auto',
}}
>
<iframe
src={file.url}
style={{
width: '100%',
maxHeight: '90vh',
height: '90vh',
flex: '1 1 auto',
}}
/>
</div>
);
},
};

View File

@ -0,0 +1,11 @@
import { Plugin } from '../../application/Plugin';
import { fileDef, filePdf, imagejpeg, imagePng } from './AttachmentItems';
export class AttachmentPreviewPlugin extends Plugin {
async load() {
this.app.AttachmentPreviewManager.add(imagejpeg);
this.app.AttachmentPreviewManager.add(fileDef);
this.app.AttachmentPreviewManager.add(imagePng);
this.app.AttachmentPreviewManager.add(filePdf);
}
}

View File

@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import { Navigate, useNavigate } from 'react-router-dom';
import { useAPIClient } from '../api-client';
import { Application, NoticeLevel, useApp } from '../application';
import { Application, AttachmentPreviewManager, NoticeLevel, useApp } from '../application';
import { Plugin } from '../application/Plugin';
import { BlockSchemaComponentPlugin } from '../block-provider';
import { CollectionPlugin } from '../collection-manager';
@ -23,6 +23,7 @@ import { CurrentUserProvider, CurrentUserSettingsMenuProvider } from '../user';
import { ACLPlugin } from './acl';
import { AdminLayoutPlugin } from './admin-layout';
import { PluginAssistant } from './assistant';
import { AttachmentPreviewPlugin } from './attachment-preview';
import { PluginBuiltInCollections } from './built-in-collections';
import { PluginContextMenu } from './context-menu';
import { RemoteDocumentTitlePlugin } from './document-title';
@ -357,5 +358,6 @@ export class BuiltInPlugin extends Plugin {
await this.app.pm.add(PluginContextMenu, { name: 'context-menu' });
await this.app.pm.add(PluginAssistant, { name: 'drag-assistant' });
await this.app.pm.add(UserSettingsPlugin, { name: 'user-settings' });
await this.app.pm.add(AttachmentPreviewPlugin, { name: 'attachment-preview' });
}
}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Lightbox } from '@tachybase/components';
import { Field, useField } from '@tachybase/schema';
import { isString } from '@tachybase/utils/client';
@ -10,8 +10,10 @@ import cls from 'classnames';
import { saveAs } from 'file-saver';
import { useTranslation } from 'react-i18next';
import { SchemaComponent } from '../..';
import { useApp } from '../../../application/hooks';
import { useRecord } from '../../../record-provider';
import { isImage, isPdf, toArr, toImages } from './shared';
import { isImage, isPdf, toArr, toFileList, toImages } from './shared';
import { useStyles } from './style';
import type { UploadProps } from './type';
@ -24,23 +26,17 @@ export const ReadPretty: Composed = () => null;
ReadPretty.File = function File(props: UploadProps) {
const { size, showCount = 0 } = props;
const { t } = useTranslation();
const record = useRecord();
const field = useField<Field>();
const value = isString(field.value) ? record : field.value;
const images = toImages(showCount === 0 ? toArr(value) : toArr(value).slice(0, showCount));
const images = isString(field.value) ? record : toFileList(field.value);
const app = useApp();
const [fileIndex, setFileIndex] = useState(0);
const [visible, setVisible] = useState(false);
const [fileType, setFileType] = useState<'image' | 'pdf'>();
const { wrapSSR, hashId, componentCls: prefixCls } = useStyles();
const useUploadStyleVal = (useUploadStyle as any).default ? (useUploadStyle as any).default : useUploadStyle;
const previewList = app.AttachmentPreviewManager.get();
// 加载 antd 的样式
useUploadStyleVal(prefixCls);
function closeIFrameModal() {
setVisible(false);
}
return wrapSSR(
<div>
<div
@ -53,95 +49,49 @@ ReadPretty.File = function File(props: UploadProps) {
)}
>
<div className={cls(`${prefixCls}-list`, `${prefixCls}-list-picture-card`)}>
{images.map((file) => {
const handleClick = (e) => {
const index = images.indexOf(file);
if (isImage(file.extname)) {
e.preventDefault();
e.stopPropagation();
setVisible(true);
setFileIndex(index);
setFileType('image');
} else if (isPdf(file.extname)) {
e.preventDefault();
e.stopPropagation();
setVisible(true);
setFileIndex(index);
setFileType('pdf');
}
// else {
// saveAs(file.url, `${file.title}${file.extname}`);
// }
};
{images.map((file, index) => {
if (size === 'small') {
return (
index === 0 && (
<ReadFile
file={file}
prefixCls={prefixCls}
// handleClick={handleClick}
size={size}
images={images}
setFileIndex={setFileIndex}
setVisible={setVisible}
preview={previewList[file?.mimetype] || previewList['default']}
key={index}
/>
)
);
}
return (
<div
key={file.name}
className={cls(`${prefixCls}-list-picture-card-container`, `${prefixCls}-list-item-container`)}
>
<div
className={cls(
`${prefixCls}-list-item`,
`${prefixCls}-list-item-done`,
`${prefixCls}-list-item-list-type-picture-card`,
)}
>
<div className={`${prefixCls}-list-item-info`}>
<span className={`${prefixCls}-span`}>
<a
className={`${prefixCls}-list-item-thumbnail`}
href={file.url}
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
>
{file.imageUrl && (
<img
src={`${file.imageUrl}${file.thumbnailRule || ''}`}
alt={file.title}
className={`${prefixCls}-list-item-image`}
/>
)}
</a>
<a
target="_blank"
rel="noopener noreferrer"
className={`${prefixCls}-list-item-name`}
title={file.title}
href={file.url}
onClick={handleClick}
>
{file.title}
</a>
</span>
</div>
{size !== 'small' && (
<span className={`${prefixCls}-list-item-actions`}>
<Space size={3}>
<Button
size={'small'}
type={'text'}
icon={<DownloadOutlined />}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
saveAs(file.url, `${file.title}${file.extname}`);
}}
/>
</Space>
</span>
)}
</div>
</div>
<ReadFile
file={file}
prefixCls={prefixCls}
size={size}
images={images}
setFileIndex={setFileIndex}
setVisible={setVisible}
preview={previewList['image/jpeg']}
key={index}
/>
);
})}
</div>
</div>
{visible && fileType === 'image' && (
{visible && (
<Lightbox
// discourageDownloads={true}
mainSrc={images[fileIndex]?.imageUrl}
nextSrc={images[(fileIndex + 1) % images.length]?.imageUrl}
prevSrc={images[(fileIndex + images.length - 1) % images.length]?.imageUrl}
mainFile={images[fileIndex]}
nextFile={images[(fileIndex + 1) % images.length]}
prevFile={images[(fileIndex + images.length - 1) % images.length]}
previewList={previewList}
// @ts-ignore
onCloseRequest={(e) => {
e.preventDefault();
@ -171,59 +121,6 @@ ReadPretty.File = function File(props: UploadProps) {
]}
/>
)}
{visible && fileType === 'pdf' && (
<Modal
open={visible}
title={'PDF - ' + images[fileIndex].title}
onCancel={closeIFrameModal}
footer={[
<Button
key={'download'}
style={{
textTransform: 'capitalize',
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
const file = images[fileIndex];
saveAs(file.url, `${file.title}${file.extname}`);
}}
>
{t('download')}
</Button>,
<Button key={'close'} onClick={closeIFrameModal} style={{ textTransform: 'capitalize' }}>
{t('close')}
</Button>,
]}
width={'85vw'}
centered={true}
>
<div
style={{
padding: '8px',
maxWidth: '100%',
maxHeight: 'calc(100vh - 256px)',
height: '90vh',
width: '100%',
background: 'white',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
overflowY: 'auto',
}}
>
<iframe
src={images[fileIndex].url}
style={{
width: '100%',
maxHeight: '90vh',
flex: '1 1 auto',
}}
></iframe>
</div>
</Modal>
)}
</div>,
);
};
@ -242,3 +139,87 @@ ReadPretty.Upload = function Upload() {
</div>
));
};
export const ReadFile = ({ file, prefixCls, size, images, setFileIndex, setVisible, preview }) => {
const { viewComponet } = preview;
const handleClick = (e) => {
const index = images.indexOf(file);
e.preventDefault();
e.stopPropagation();
setVisible(true);
setFileIndex(index);
};
return (
<div
key={file.name}
className={cls(`${prefixCls}-list-picture-card-container`, `${prefixCls}-list-item-container`)}
style={{ position: 'relative' }}
>
<div
className={cls(
`${prefixCls}-list-item`,
`${prefixCls}-list-item-done`,
`${prefixCls}-list-item-list-type-picture-card`,
)}
>
<div className={`${prefixCls}-list-item-info`}>
<span className={`${prefixCls}-span`}>
<a
className={`${prefixCls}-list-item-thumbnail`}
href={file.url}
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
style={{ lineHeight: '100%' }}
>
{viewComponet({ images, size, prefixCls, file, setFileIndex, setVisible })}
</a>
<a
target="_blank"
rel="noopener noreferrer"
className={`${prefixCls}-list-item-name`}
title={file.title}
href={file.url}
onClick={handleClick}
>
{file.title}
</a>
</span>
</div>
{size !== 'small' && (
<span className={`${prefixCls}-list-item-actions`}>
<Space size={3}>
<Button
size={'small'}
type={'text'}
icon={<DownloadOutlined />}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
saveAs(file.url, `${file.title}${file.extname}`);
}}
/>
</Space>
</span>
)}
</div>
{images.length > 1 && size === 'small' && (
<div
style={{
position: 'absolute',
bottom: '0',
right: '0',
backgroundColor: '#9b999992',
width: '50%',
height: '30%',
lineHeight: '30%',
borderRadius: '40%',
textAlign: 'center',
}}
>
...
</div>
)}
</div>
);
};

View File

@ -8,6 +8,7 @@ import cls from 'classnames';
import { saveAs } from 'file-saver';
import { useTranslation } from 'react-i18next';
import { useApp } from '../../../application';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
import { useProps } from '../../hooks/useProps';
import { ReadPretty } from './ReadPretty';
@ -29,18 +30,15 @@ Upload.Attachment = connect((props: UploadProps) => {
const { disabled, multiple, value, onChange } = props;
const [fileList, setFileList] = useState<any[]>([]);
const [sync, setSync] = useState(true);
const app = useApp();
const previewList = app.AttachmentPreviewManager.get();
const images = fileList;
const [fileIndex, setFileIndex] = useState(0);
const [visible, setVisible] = useState(false);
const [fileType, setFileType] = useState<'image' | 'pdf'>();
const { t } = useTranslation();
const uploadProps = useUploadProps({ ...props });
const { wrapSSR, hashId, componentCls: prefixCls } = useStyles();
const internalFileList = useRef([]);
function closeIFrameModal() {
setVisible(false);
}
useEffect(() => {
if (sync) {
const fileList = toFileList(value);
@ -53,104 +51,23 @@ Upload.Attachment = connect((props: UploadProps) => {
<div>
<div className={cls(`${prefixCls}-wrapper`, `${prefixCls}-picture-card-wrapper`, 'nb-upload', hashId)}>
<div className={cls(`${prefixCls}-list`, `${prefixCls}-list-picture-card`)}>
{fileList.map((file) => {
const handleClick = (e) => {
e.preventDefault();
e.stopPropagation();
const index = fileList.indexOf(file);
if (isImage(file.extname)) {
setFileType('image');
setVisible(true);
setFileIndex(index);
} else if (isPdf(file.extname)) {
setVisible(true);
setFileIndex(index);
setFileType('pdf');
} else {
saveAs(file.url, `${file.title}${file.extname}`);
}
{fileList.map((file, index) => {
const fileProps = {
file,
prefixCls,
fileList,
setFileIndex,
setVisible,
disabled,
t,
setSync,
setFileList,
multiple,
onChange,
internalFileList,
preview: previewList[file?.mimetype] || previewList['default'],
};
return (
<div
key={file.uid || file.id}
className={`${prefixCls}-list-picture-card-container ${prefixCls}-list-item-container`}
>
<div
className={cls(
`${prefixCls}-list-item`,
`${prefixCls}-list-item-done`,
`${prefixCls}-list-item-list-type-picture-card`,
)}
>
<div className={`${prefixCls}-list-item-info`}>
<span className={`${prefixCls}-span`}>
<a
className={`${prefixCls}-list-item-thumbnail`}
href={file.url}
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
>
{file.imageUrl && (
<img src={file.imageUrl} alt={file.title} className={`${prefixCls}-list-item-image`} />
)}
</a>
<a
target="_blank"
rel="noopener noreferrer"
className={`${prefixCls}-list-item-name`}
title={file.title}
href={file.url}
onClick={handleClick}
>
{file.status === 'uploading' ? t('Uploading') : file.title}
</a>
</span>
</div>
<span className={`${prefixCls}-list-item-actions`}>
<Space size={3}>
<Button
size={'small'}
type={'text'}
icon={<DownloadOutlined />}
onClick={() => {
saveAs(file.url, `${file.title}${file.extname}`);
}}
/>
{!disabled && (
<Button
size={'small'}
type={'text'}
icon={<DeleteOutlined />}
onClick={() => {
setSync(false);
setFileList((prevFileList) => {
if (!multiple) {
onChange?.(null as any);
setSync(true);
return [];
}
const index = prevFileList.indexOf(file);
prevFileList.splice(index, 1);
internalFileList.current = internalFileList.current.filter(
(item) => item.uid !== file.uid,
);
onChange?.(toValue([...prevFileList]));
return [...prevFileList];
});
}}
/>
)}
</Space>
</span>
{file.status === 'uploading' && (
<div className={`${prefixCls}-list-item-progress`}>
<Progress strokeWidth={2} type={'line'} showInfo={false} percent={file.percent} />
</div>
)}
</div>
</div>
);
return <UploadReadFile {...fileProps} key={index} />;
})}
{!disabled && (multiple || toArr(value).length < 1) && (
<div className={cls(`${prefixCls}-list-picture-card-container`, `${prefixCls}-list-item-container`)}>
@ -198,12 +115,16 @@ Upload.Attachment = connect((props: UploadProps) => {
</div>
</div>
{/* 预览图片的弹框 */}
{visible && fileType === 'image' && (
{visible && (
<Lightbox
// discourageDownloads={true}
mainSrc={images[fileIndex]?.imageUrl}
nextSrc={images[(fileIndex + 1) % images.length]?.imageUrl}
prevSrc={images[(fileIndex + images.length - 1) % images.length]?.imageUrl}
mainFile={images[fileIndex]}
nextFile={images[(fileIndex + 1) % images.length]}
prevFile={images[(fileIndex + images.length - 1) % images.length]}
previewList={previewList}
onCloseRequest={() => setVisible(false)}
onMovePrevRequest={() => setFileIndex((fileIndex + images.length - 1) % images.length)}
onMoveNextRequest={() => setFileIndex((fileIndex + 1) % images.length)}
@ -227,59 +148,6 @@ Upload.Attachment = connect((props: UploadProps) => {
]}
/>
)}
{visible && fileType === 'pdf' && (
<Modal
open={visible}
title={'PDF - ' + images[fileIndex].title}
onCancel={closeIFrameModal}
footer={[
<Button
key="download"
style={{
textTransform: 'capitalize',
}}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
const file = images[fileIndex];
saveAs(file.url, `${file.title}${file.extname}`);
}}
>
{t('download')}
</Button>,
<Button key="close" onClick={closeIFrameModal} style={{ textTransform: 'capitalize' }}>
{t('close')}
</Button>,
]}
width={'85vw'}
centered={true}
>
<div
style={{
padding: '8px',
maxWidth: '100%',
maxHeight: 'calc(100vh - 256px)',
height: '90vh',
width: '100%',
background: 'white',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
overflowY: 'auto',
}}
>
<iframe
src={images[fileIndex].url}
style={{
width: '100%',
maxHeight: '90vh',
flex: '1 1 auto',
}}
></iframe>
</div>
</Modal>
)}
</div>,
);
}, mapReadPretty(ReadPretty.File));
@ -355,3 +223,107 @@ function updateFileList(file: UploadFile, fileList: (UploadFile | Readonly<Uploa
}
return nextFileList;
}
export const UploadReadFile = (props) => {
const {
file,
prefixCls,
fileList,
setFileIndex,
setVisible,
disabled,
t,
setSync,
setFileList,
multiple,
onChange,
internalFileList,
preview,
} = props;
const handleClick = (e) => {
e.preventDefault();
e.stopPropagation();
const index = fileList.indexOf(file);
setVisible(true);
setFileIndex(index);
};
const { viewComponet } = preview;
return (
<div
key={file.uid || file.id}
className={`${prefixCls}-list-picture-card-container ${prefixCls}-list-item-container`}
>
<div
className={cls(
`${prefixCls}-list-item`,
`${prefixCls}-list-item-done`,
`${prefixCls}-list-item-list-type-picture-card`,
)}
>
<div className={`${prefixCls}-list-item-info`}>
<span className={`${prefixCls}-span`}>
<a
className={`${prefixCls}-list-item-thumbnail`}
href={file.url}
target="_blank"
rel="noopener noreferrer"
onClick={handleClick}
style={{ lineHeight: '100%' }}
>
{viewComponet({ prefixCls, file, setFileIndex, setVisible })}
</a>
<a
target="_blank"
rel="noopener noreferrer"
className={`${prefixCls}-list-item-name`}
title={file.title}
href={file.url}
onClick={handleClick}
>
{file.status === 'uploading' ? t('Uploading') : file.title}
</a>
</span>
</div>
<span className={`${prefixCls}-list-item-actions`}>
<Space size={3}>
<Button
size={'small'}
type={'text'}
icon={<DownloadOutlined />}
onClick={() => {
saveAs(file.url, `${file.title}${file.extname}`);
}}
/>
{!disabled && (
<Button
size={'small'}
type={'text'}
icon={<DeleteOutlined />}
onClick={() => {
setSync(false);
setFileList((prevFileList) => {
if (!multiple) {
onChange?.(null as any);
setSync(true);
return [];
}
const index = prevFileList.indexOf(file);
prevFileList.splice(index, 1);
internalFileList.current = internalFileList.current.filter((item) => item.uid !== file.uid);
onChange?.(toValue([...prevFileList]));
return [...prevFileList];
});
}}
/>
)}
</Space>
</span>
{file.status === 'uploading' && (
<div className={`${prefixCls}-list-item-progress`}>
<Progress strokeWidth={2} type={'line'} showInfo={false} percent={file.percent} />
</div>
)}
</div>
</div>
);
};

View File

@ -25,6 +25,8 @@ import { getHighestSafeWindowContext, getWindowHeight, getWindowWidth, translate
import './style.css';
import { css } from '@tachybase/client';
class ReactImageLightbox extends Component {
static isTargetMatchImage(target) {
return target && /ril-image-current/.test(target.className);
@ -282,10 +284,9 @@ class ReactImageLightbox extends Component {
}
// Get info for the best suited image to display with the given srcType
getBestImageForType(srcType) {
let imageSrc = this.props[srcType];
getBestImageForType(srcType, fileSrc?) {
let imageSrc = fileSrc || this.props[srcType];
let fitSizes = {};
if (this.isImageLoaded(imageSrc)) {
// Use full-size image if available
fitSizes = this.getFitSizes(this.imageCache[imageSrc].width, this.imageCache[imageSrc].height);
@ -394,6 +395,30 @@ class ReactImageLightbox extends Component {
name: 'prevSrcThumbnail',
keyEnding: `t${this.keyCounter - 1}`,
},
{
name: 'mainFile',
keyEnding: `i${this.keyCounter}`,
},
{
name: 'mainSrcThumbnail',
keyEnding: `t${this.keyCounter}`,
},
{
name: 'nextFile',
keyEnding: `i${this.keyCounter + 1}`,
},
{
name: 'nextFileThumbnail',
keyEnding: `t${this.keyCounter + 1}`,
},
{
name: 'prevFile',
keyEnding: `i${this.keyCounter - 1}`,
},
{
name: 'prevFileThumbnail',
keyEnding: `t${this.keyCounter - 1}`,
},
];
}
@ -1175,6 +1200,10 @@ class ReactImageLightbox extends Component {
imageTitle,
nextSrc,
prevSrc,
mainFile,
nextFile,
prevFile,
previewList,
toolbarButtons,
reactModalStyle,
onAfterOpen,
@ -1203,17 +1232,21 @@ class ReactImageLightbox extends Component {
// Images to be displayed
const images = [];
const addImage = (srcType, imageClass, transforms) => {
// Ignore types that have no source defined for their full size image
if (!this.props[srcType]) {
return;
}
const bestImageInfo = this.getBestImageForType(srcType);
const addImage = (srcType, imageClass, transforms) => {
const fieldType = this.props[srcType]?.mimetype;
const { checkedComponent } = previewList[fieldType] || previewList['default'];
const bestImageInfo = (this.props[srcType]?.mimetype as string).includes('image')
? this.getBestImageForType(srcType, this.props[srcType]?.imageUrl)
: {};
const imageStyle = {
...transitionStyle,
...ReactImageLightbox.getTransform({
...transforms,
height: boxSize.height - 20,
width: boxSize.width - 20,
targetWidth: boxSize.width - 50,
targeHeight: boxSize.height - 50,
...bestImageInfo,
}),
};
@ -1221,99 +1254,43 @@ class ReactImageLightbox extends Component {
if (zoomLevel > MIN_ZOOM_LEVEL) {
imageStyle.cursor = 'move';
}
// support IE 9 and 11
const hasTrueValue = (object) => Object.keys(object).some((key) => object[key]);
// when error on one of the loads then push custom error stuff
if (bestImageInfo === null && hasTrueValue(loadErrorStatus)) {
images.push(
<div
className={`${imageClass} ril__image ril-errored`}
style={imageStyle}
key={this.props[srcType] + keyEndings[srcType]}
>
<div className="ril__errorContainer">{this.props.imageLoadErrorMessage}</div>
</div>,
);
return;
}
if (bestImageInfo === null) {
const loadingIcon =
loader !== undefined ? (
loader
) : (
<div className="ril-loading-circle ril__loadingCircle ril__loadingContainer__icon">
{[...new Array(12)].map((_, index) => (
<div
// eslint-disable-next-line react/no-array-index-key
key={index}
className="ril-loading-circle-point ril__loadingCirclePoint"
/>
))}
</div>
);
// Fall back to loading icon if the thumbnail has not been loaded
images.push(
<div
className={`${imageClass} ril__image ril-not-loaded`}
style={imageStyle}
key={this.props[srcType] + keyEndings[srcType]}
>
<div className="ril__loadingContainer">{loadingIcon}</div>
</div>,
);
return;
}
const imageSrc = bestImageInfo.src;
if (discourageDownloads) {
imageStyle.backgroundImage = `url('${imageSrc}')`;
images.push(
<div
className={`${imageClass} ril__image ril__imageDiscourager`}
onDoubleClick={this.handleImageDoubleClick}
onWheel={this.handleImageMouseWheel}
style={imageStyle}
key={imageSrc + keyEndings[srcType]}
>
<div className="ril-download-blocker ril__downloadBlocker" />
</div>,
);
} else {
images.push(
<img
{...(imageCrossOrigin ? { crossOrigin: imageCrossOrigin } : {})}
className={`${imageClass} ril__image`}
onDoubleClick={this.handleImageDoubleClick}
onWheel={this.handleImageMouseWheel}
onDragStart={(e) => e.preventDefault()}
style={imageStyle}
src={imageSrc}
key={imageSrc + keyEndings[srcType]}
alt={typeof imageTitle === 'string' ? imageTitle : translate('Image')}
draggable={false}
/>,
);
}
images.push(
<div
className={`${imageClass} ril__image`}
style={{
...imageStyle,
width: bestImageInfo?.width || '90%',
height: '90%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{checkedComponent({
key: `${fieldType + keyEndings[srcType]}`,
onDoubleClick: this.handleImageDoubleClick,
onWheel: this.handleImageMouseWheel,
onDragStart: (e) => e.preventDefault(),
file: this.props[srcType],
bestImageInfo,
})}
</div>,
);
};
const zoomMultiplier = this.getZoomMultiplier();
// Next Image (displayed on the right)
addImage('nextSrc', 'ril-image-next ril__imageNext', {
addImage('nextFile', 'ril-image-next ril__imageNext', {
x: boxSize.width,
});
// Main Image
addImage('mainSrc', 'ril-image-current', {
addImage('mainFile', 'ril-image-current', {
x: -1 * offsetX,
y: -1 * offsetY,
zoom: zoomMultiplier,
});
// Previous Image (displayed on the left)
addImage('prevSrc', 'ril-image-prev ril__imagePrev', {
addImage('prevFile', 'ril-image-prev ril__imagePrev', {
x: -1 * boxSize.width,
});
@ -1336,7 +1313,6 @@ class ReactImageLightbox extends Component {
...reactModalStyle.content, // Allow style overrides via props
},
};
return (
<Modal
isOpen
@ -1497,33 +1473,37 @@ class ReactImageLightbox extends Component {
}
ReactImageLightbox.propTypes = {
previewList: PropTypes.object,
//-----------------------------
// Image sources
//-----------------------------
// Main display image url
mainSrc: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
mainSrc: PropTypes.string.isRequired,
mainFile: PropTypes.object,
// Previous display image url (displayed to the left)
// If left undefined, movePrev actions will not be performed, and the button not displayed
prevSrc: PropTypes.string,
prevFile: PropTypes.object,
// Next display image url (displayed to the right)
// If left undefined, moveNext actions will not be performed, and the button not displayed
nextSrc: PropTypes.string,
nextFile: PropTypes.object,
//-----------------------------
// Image thumbnail sources
//-----------------------------
// Thumbnail image url corresponding to props.mainSrc
mainSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
mainSrcThumbnail: PropTypes.string,
// Thumbnail image url corresponding to props.prevSrc
prevSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
prevSrcThumbnail: PropTypes.string,
// Thumbnail image url corresponding to props.nextSrc
nextSrcThumbnail: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
nextSrcThumbnail: PropTypes.string,
//-----------------------------
// Event Handlers