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:
parent
2b698c6c3d
commit
2e15125445
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -43,6 +43,10 @@ export class Plugin<T = any> {
|
||||
return this.app.dataSourceManager;
|
||||
}
|
||||
|
||||
get AttachmentPreviewManager() {
|
||||
return this.app.AttachmentPreviewManager;
|
||||
}
|
||||
|
||||
async afterAdd() {}
|
||||
|
||||
async beforeLoad() {}
|
||||
|
@ -12,3 +12,4 @@ export * from './NoticesManager';
|
||||
export * from './hoc';
|
||||
export { ApplicationContext } from './context';
|
||||
export * from './PluginContextMenu';
|
||||
export * from './AttachmentPreviewManager';
|
||||
|
@ -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>
|
||||
);
|
||||
},
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user