@agentscope-ai/chat 1.1.53 → 1.1.54-beta.1773466321797
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Attachments/FileList/FileListCard.tsx +57 -97
- package/components/Attachments/FileList/ImageCard.tsx +197 -0
- package/components/Attachments/FileList/index.tsx +7 -0
- package/components/Attachments/index.tsx +29 -0
- package/components/Attachments/style/fileCard.ts +42 -3
- package/components/ChatAnywhere/Input/index.tsx +1 -0
- package/components/Conversations/Item.tsx +65 -54
- package/components/Conversations/demo/avatar.tsx +110 -0
- package/components/Conversations/index.en-US.md +1 -0
- package/components/Conversations/style.ts +7 -0
- package/lib/Attachments/FileList/FileListCard.d.ts +2 -1
- package/lib/Attachments/FileList/FileListCard.js +27 -61
- package/lib/Attachments/FileList/ImageCard.d.ts +31 -0
- package/lib/Attachments/FileList/ImageCard.js +162 -0
- package/lib/Attachments/FileList/index.d.ts +5 -0
- package/lib/Attachments/FileList/index.js +2 -0
- package/lib/Attachments/index.d.ts +7 -0
- package/lib/Attachments/index.js +23 -1
- package/lib/Attachments/style/fileCard.js +11 -1
- package/lib/ChatAnywhere/Input/index.js +1 -0
- package/lib/Conversations/Item.js +74 -72
- package/lib/Conversations/style.js +1 -1
- package/package.json +1 -1
|
@@ -2,16 +2,14 @@ import classnames from 'classnames';
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import type { Attachment } from '..';
|
|
4
4
|
import { AttachmentContext } from '../context';
|
|
5
|
-
import { previewImage } from '../util';
|
|
6
|
-
import AudioIcon from './AudioIcon';
|
|
7
|
-
import Progress from './Progress';
|
|
8
|
-
import VideoIcon from './VideoIcon';
|
|
9
5
|
import Style from '../style/fileCard';
|
|
10
6
|
import { useProviderContext } from '@agentscope-ai/chat';
|
|
11
7
|
import { SparkFalseLine } from '@agentscope-ai/icons';
|
|
8
|
+
import ImageCard from './ImageCard';
|
|
9
|
+
import type { ImageCardProps } from './ImageCard';
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
export interface FileListCardProps {
|
|
12
|
+
export interface FileListCardProps extends Pick<ImageCardProps, 'onReplace'> {
|
|
15
13
|
/**
|
|
16
14
|
* @description 自定义CSS类名前缀,用于样式隔离和主题定制
|
|
17
15
|
* @descriptionEn Custom CSS class name prefix for style isolation and theme customization
|
|
@@ -42,7 +40,6 @@ export interface FileListCardProps {
|
|
|
42
40
|
* @descriptionEn Render type, currently only supports default render mode
|
|
43
41
|
*/
|
|
44
42
|
renderType?: 'default',
|
|
45
|
-
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
const EMPTY = '\u00A0';
|
|
@@ -126,7 +123,7 @@ function getSize(size: number) {
|
|
|
126
123
|
|
|
127
124
|
function FileListCard(props: FileListCardProps, ref: React.Ref<HTMLDivElement>) {
|
|
128
125
|
const { getPrefixCls } = useProviderContext();
|
|
129
|
-
const { item, onRemove, className, style } = props;
|
|
126
|
+
const { item, onRemove, onReplace, className, style } = props;
|
|
130
127
|
const context = React.useContext(AttachmentContext);
|
|
131
128
|
const { disabled } = context || {};
|
|
132
129
|
const { name, size, percent, status = 'done', description } = item;
|
|
@@ -141,7 +138,23 @@ function FileListCard(props: FileListCardProps, ref: React.Ref<HTMLDivElement>)
|
|
|
141
138
|
|
|
142
139
|
const isImg = React.useMemo(() => matchExt(nameSuffix, IMG_EXTS), [nameSuffix]);
|
|
143
140
|
|
|
144
|
-
const
|
|
141
|
+
const renderType = props.renderType || 'default';
|
|
142
|
+
const isImgPreview = isImg && (item.originFileObj || item.thumbUrl || item.url) && renderType === 'default';
|
|
143
|
+
|
|
144
|
+
if (isImgPreview) {
|
|
145
|
+
return (
|
|
146
|
+
<ImageCard
|
|
147
|
+
ref={ref}
|
|
148
|
+
item={item}
|
|
149
|
+
onRemove={onRemove}
|
|
150
|
+
onReplace={onReplace}
|
|
151
|
+
className={className}
|
|
152
|
+
style={style}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const desc = (() => {
|
|
145
158
|
if (description) {
|
|
146
159
|
return description;
|
|
147
160
|
}
|
|
@@ -155,9 +168,9 @@ function FileListCard(props: FileListCardProps, ref: React.Ref<HTMLDivElement>)
|
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
return size ? getSize(size) : EMPTY;
|
|
158
|
-
}
|
|
171
|
+
})();
|
|
159
172
|
|
|
160
|
-
const [icon, iconColor] =
|
|
173
|
+
const [icon, iconColor] = (() => {
|
|
161
174
|
for (const { ext, icon, color } of PRESET_FILE_ICONS) {
|
|
162
175
|
if (matchExt(nameSuffix, ext)) {
|
|
163
176
|
return [icon, color];
|
|
@@ -165,57 +178,25 @@ function FileListCard(props: FileListCardProps, ref: React.Ref<HTMLDivElement>)
|
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
return [<IconImage url="https://gw.alicdn.com/imgextra/i1/O1CN01K7jgEj1sywWTkPSGY_!!6000000005836-55-tps-40-40.svg" key="defaultIcon" />, DEFAULT_ICON_COLOR];
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
setPreviewImg(undefined);
|
|
186
|
-
}, [item.originFileObj]);
|
|
187
|
-
|
|
188
|
-
let content: React.ReactNode = null;
|
|
189
|
-
const previewUrl = item.thumbUrl || item.url || previewImg;
|
|
190
|
-
const renderType = props.renderType || 'default';
|
|
191
|
-
const isImgPreview = isImg && (item.originFileObj || previewUrl) && renderType === 'default';
|
|
192
|
-
|
|
193
|
-
if (isImgPreview) {
|
|
194
|
-
content = (
|
|
195
|
-
<>
|
|
196
|
-
{
|
|
197
|
-
previewUrl && (
|
|
198
|
-
<img alt="preview" src={previewUrl} />
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
{status !== 'done' && (
|
|
203
|
-
<div className={`${cardCls}-img-mask`}>
|
|
204
|
-
{status === 'uploading' && percent !== undefined && (
|
|
205
|
-
<Progress percent={percent} prefixCls={cardCls} />
|
|
206
|
-
)}
|
|
207
|
-
{status === 'error' && (
|
|
208
|
-
<div className={`${cardCls}-desc`}>
|
|
209
|
-
<div className={`${cardCls}-ellipsis-prefix`}>{desc}</div>
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
</div>
|
|
181
|
+
})();
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
<Style />
|
|
186
|
+
<div
|
|
187
|
+
className={classnames(
|
|
188
|
+
cardCls,
|
|
189
|
+
{
|
|
190
|
+
[`${cardCls}-status-${status}`]: status,
|
|
191
|
+
[`${cardCls}-type-overview`]: true,
|
|
192
|
+
[`${cardCls}-type-${renderType}`]: true,
|
|
193
|
+
[`${cardCls}-hoverable`]: !disabled && onRemove,
|
|
194
|
+
},
|
|
195
|
+
className,
|
|
213
196
|
)}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
content = (
|
|
218
|
-
<>
|
|
197
|
+
style={style}
|
|
198
|
+
ref={ref}
|
|
199
|
+
>
|
|
219
200
|
<div className={`${cardCls}-icon`} style={{ color: iconColor }}>
|
|
220
201
|
{icon}
|
|
221
202
|
</div>
|
|
@@ -227,44 +208,23 @@ function FileListCard(props: FileListCardProps, ref: React.Ref<HTMLDivElement>)
|
|
|
227
208
|
<div className={`${cardCls}-ellipsis-prefix`}>{desc}</div>
|
|
228
209
|
</div>
|
|
229
210
|
</div>
|
|
230
|
-
</>
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return <>
|
|
235
|
-
<Style />
|
|
236
|
-
<div
|
|
237
|
-
className={classnames(
|
|
238
|
-
cardCls,
|
|
239
|
-
{
|
|
240
|
-
[`${cardCls}-status-${status}`]: status,
|
|
241
|
-
[`${cardCls}-type-preview`]: isImgPreview,
|
|
242
|
-
[`${cardCls}-type-overview`]: !isImgPreview,
|
|
243
|
-
[`${cardCls}-type-${renderType}`]: true,
|
|
244
|
-
[`${cardCls}-hoverable`]: !disabled && onRemove,
|
|
245
|
-
},
|
|
246
|
-
className,
|
|
247
|
-
)}
|
|
248
|
-
style={style}
|
|
249
|
-
ref={ref}
|
|
250
|
-
>
|
|
251
|
-
{content}
|
|
252
|
-
|
|
253
|
-
<button
|
|
254
|
-
style={{
|
|
255
|
-
opacity: !disabled && onRemove ? 1 : 0,
|
|
256
|
-
}}
|
|
257
|
-
className={`${cardCls}-remove`}
|
|
258
|
-
onClick={() => {
|
|
259
|
-
if (!disabled && onRemove) {
|
|
260
|
-
onRemove(item);
|
|
261
|
-
}
|
|
262
|
-
}}
|
|
263
|
-
>
|
|
264
|
-
<SparkFalseLine />
|
|
265
211
|
|
|
266
|
-
|
|
267
|
-
|
|
212
|
+
<button
|
|
213
|
+
style={{
|
|
214
|
+
opacity: !disabled && onRemove ? 1 : 0,
|
|
215
|
+
}}
|
|
216
|
+
className={`${cardCls}-remove`}
|
|
217
|
+
onClick={() => {
|
|
218
|
+
if (!disabled && onRemove) {
|
|
219
|
+
onRemove(item);
|
|
220
|
+
}
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
<SparkFalseLine />
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
268
228
|
}
|
|
269
229
|
|
|
270
230
|
export default React.forwardRef(FileListCard);
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import classnames from 'classnames';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Image } from 'antd';
|
|
4
|
+
import type { Attachment } from '..';
|
|
5
|
+
import { AttachmentContext } from '../context';
|
|
6
|
+
import { previewImage } from '../util';
|
|
7
|
+
import Progress from './Progress';
|
|
8
|
+
import Style from '../style/fileCard';
|
|
9
|
+
import { useProviderContext } from '@agentscope-ai/chat';
|
|
10
|
+
import { SparkFalseLine, SparkVisibleLine, SparkRefreshLine, SparkReplaceLine } from '@agentscope-ai/icons';
|
|
11
|
+
|
|
12
|
+
export interface ImageCardProps {
|
|
13
|
+
/**
|
|
14
|
+
* @description 文件附件数据对象,包含文件的基本信息
|
|
15
|
+
* @descriptionEn File attachment data object containing basic file information
|
|
16
|
+
*/
|
|
17
|
+
item: Attachment;
|
|
18
|
+
/**
|
|
19
|
+
* @description 文件移除时的回调函数,用于处理文件删除操作
|
|
20
|
+
* @descriptionEn Callback function when file is removed for handling file deletion operations
|
|
21
|
+
*/
|
|
22
|
+
onRemove?: (item: Attachment) => void;
|
|
23
|
+
/**
|
|
24
|
+
* @description 替换当前图片的回调,传入原始附件和新选择的文件
|
|
25
|
+
* @descriptionEn Callback to replace current image, receives the original attachment and newly selected file
|
|
26
|
+
*/
|
|
27
|
+
onReplace?: (oldItem: Attachment, file: File) => void;
|
|
28
|
+
/**
|
|
29
|
+
* @description 组件的CSS类名
|
|
30
|
+
* @descriptionEn CSS class name for the component
|
|
31
|
+
*/
|
|
32
|
+
className?: string;
|
|
33
|
+
/**
|
|
34
|
+
* @description 组件的内联样式对象
|
|
35
|
+
* @descriptionEn Inline style object for the component
|
|
36
|
+
*/
|
|
37
|
+
style?: React.CSSProperties;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const EMPTY = '\u00A0';
|
|
41
|
+
|
|
42
|
+
const IMG_ACCEPT = 'image/png,image/jpeg,image/jpg,image/gif,image/bmp,image/webp,image/svg+xml';
|
|
43
|
+
|
|
44
|
+
function ImageCard(props: ImageCardProps, ref: React.Ref<HTMLDivElement>) {
|
|
45
|
+
const { getPrefixCls } = useProviderContext();
|
|
46
|
+
const { item, onRemove, onReplace, className, style } = props;
|
|
47
|
+
const context = React.useContext(AttachmentContext);
|
|
48
|
+
const { disabled } = context || {};
|
|
49
|
+
const { percent, status = 'done', description } = item;
|
|
50
|
+
const prefixCls = getPrefixCls('attachment');
|
|
51
|
+
const cardCls = `${prefixCls}-list-card`;
|
|
52
|
+
|
|
53
|
+
const [previewVisible, setPreviewVisible] = React.useState(false);
|
|
54
|
+
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
|
55
|
+
|
|
56
|
+
const previewConfig = React.useMemo(() => ({
|
|
57
|
+
visible: previewVisible,
|
|
58
|
+
onVisibleChange: setPreviewVisible,
|
|
59
|
+
}), [previewVisible]);
|
|
60
|
+
|
|
61
|
+
const desc = React.useMemo(() => {
|
|
62
|
+
if (description) {
|
|
63
|
+
return description;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (status === 'uploading') {
|
|
67
|
+
return `${percent || 0}%`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (status === 'error') {
|
|
71
|
+
return item.response || EMPTY;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return EMPTY;
|
|
75
|
+
}, [description, status, percent, item.response]);
|
|
76
|
+
|
|
77
|
+
const [previewImg, setPreviewImg] = React.useState<string>();
|
|
78
|
+
|
|
79
|
+
React.useEffect(() => {
|
|
80
|
+
if (item.originFileObj) {
|
|
81
|
+
let synced = true;
|
|
82
|
+
previewImage(item.originFileObj).then((url) => {
|
|
83
|
+
if (synced) {
|
|
84
|
+
setPreviewImg(url);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
synced = false;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
setPreviewImg(undefined);
|
|
93
|
+
}, [item.originFileObj]);
|
|
94
|
+
|
|
95
|
+
const previewUrl = item.thumbUrl || item.url || previewImg;
|
|
96
|
+
|
|
97
|
+
const handleRefreshClick = (e: React.MouseEvent) => {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
fileInputRef.current?.click();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
103
|
+
const file = e.target.files?.[0];
|
|
104
|
+
if (file && onReplace) {
|
|
105
|
+
onReplace(item, file);
|
|
106
|
+
}
|
|
107
|
+
if (fileInputRef.current) {
|
|
108
|
+
fileInputRef.current.value = '';
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<>
|
|
114
|
+
<Style />
|
|
115
|
+
<div
|
|
116
|
+
className={classnames(
|
|
117
|
+
cardCls,
|
|
118
|
+
{
|
|
119
|
+
[`${cardCls}-status-${status}`]: status,
|
|
120
|
+
[`${cardCls}-type-preview`]: true,
|
|
121
|
+
[`${cardCls}-hoverable`]: !disabled && onRemove,
|
|
122
|
+
},
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
style={style}
|
|
126
|
+
ref={ref}
|
|
127
|
+
>
|
|
128
|
+
{previewUrl && <img alt="preview" src={previewUrl} />}
|
|
129
|
+
|
|
130
|
+
<Image
|
|
131
|
+
src={previewUrl}
|
|
132
|
+
style={{ display: 'none' }}
|
|
133
|
+
preview={previewConfig}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
{status !== 'done' && (
|
|
137
|
+
<div className={`${cardCls}-img-mask`}>
|
|
138
|
+
{status === 'uploading' && percent !== undefined && (
|
|
139
|
+
<Progress percent={percent} prefixCls={cardCls} />
|
|
140
|
+
)}
|
|
141
|
+
{status === 'error' && (
|
|
142
|
+
<div className={`${cardCls}-desc`}>
|
|
143
|
+
<div className={`${cardCls}-ellipsis-prefix`}>{desc}</div>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
|
|
149
|
+
{status === 'done' && (
|
|
150
|
+
<div className={`${cardCls}-img-hover-mask`}>
|
|
151
|
+
<button
|
|
152
|
+
className={`${cardCls}-img-action`}
|
|
153
|
+
onClick={(e) => {
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
setPreviewVisible(true);
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
<SparkVisibleLine />
|
|
159
|
+
</button>
|
|
160
|
+
{onReplace && (
|
|
161
|
+
<button
|
|
162
|
+
className={`${cardCls}-img-action`}
|
|
163
|
+
onClick={handleRefreshClick}
|
|
164
|
+
>
|
|
165
|
+
<SparkReplaceLine />
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
<input
|
|
172
|
+
ref={fileInputRef}
|
|
173
|
+
type="file"
|
|
174
|
+
accept={IMG_ACCEPT}
|
|
175
|
+
style={{ display: 'none' }}
|
|
176
|
+
onChange={handleFileChange}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
<button
|
|
180
|
+
style={{
|
|
181
|
+
opacity: !disabled && onRemove ? 1 : 0,
|
|
182
|
+
}}
|
|
183
|
+
className={`${cardCls}-remove`}
|
|
184
|
+
onClick={() => {
|
|
185
|
+
if (!disabled && onRemove) {
|
|
186
|
+
onRemove(item);
|
|
187
|
+
}
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<SparkFalseLine />
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export default React.forwardRef(ImageCard);
|
|
@@ -22,6 +22,11 @@ export interface FileListProps {
|
|
|
22
22
|
* @descriptionEn Callback function when file is removed for handling file deletion operations
|
|
23
23
|
*/
|
|
24
24
|
onRemove: (item: Attachment) => void;
|
|
25
|
+
/**
|
|
26
|
+
* @description 替换图片文件的回调,传入原始附件和新选择的文件
|
|
27
|
+
* @descriptionEn Callback to replace an image file, receives the original attachment and newly selected file
|
|
28
|
+
*/
|
|
29
|
+
onReplace?: (oldItem: Attachment, file: File) => void;
|
|
25
30
|
/**
|
|
26
31
|
* @description 文件列表的溢出处理方式,影响滚动和布局行为
|
|
27
32
|
* @descriptionEn Overflow handling method for file list, affects scrolling and layout behavior
|
|
@@ -68,6 +73,7 @@ export default function FileList(props: FileListProps) {
|
|
|
68
73
|
prefixCls,
|
|
69
74
|
items,
|
|
70
75
|
onRemove,
|
|
76
|
+
onReplace,
|
|
71
77
|
overflow,
|
|
72
78
|
listClassName,
|
|
73
79
|
listStyle,
|
|
@@ -167,6 +173,7 @@ export default function FileList(props: FileListProps) {
|
|
|
167
173
|
prefixCls={prefixCls}
|
|
168
174
|
item={item}
|
|
169
175
|
onRemove={onRemove}
|
|
176
|
+
onReplace={onReplace}
|
|
170
177
|
className={classnames(motionCls, itemClassName)}
|
|
171
178
|
style={{
|
|
172
179
|
...motionStyle,
|
|
@@ -7,6 +7,7 @@ import { useEvent, useMergedState } from 'rc-util';
|
|
|
7
7
|
import DropArea from './DropArea';
|
|
8
8
|
import FileList, { type FileListProps } from './FileList';
|
|
9
9
|
import FileListCard from './FileList/FileListCard';
|
|
10
|
+
import ImageCard from './FileList/ImageCard';
|
|
10
11
|
import PlaceholderUploader, {
|
|
11
12
|
type PlaceholderProps,
|
|
12
13
|
type PlaceholderType,
|
|
@@ -92,6 +93,11 @@ export interface AttachmentsProps extends Omit<UploadProps, 'fileList'> {
|
|
|
92
93
|
* @descriptionEn Render type, currently only supports default render mode
|
|
93
94
|
*/
|
|
94
95
|
renderType?: 'default',
|
|
96
|
+
/**
|
|
97
|
+
* @description 图片类型文件是否支持点击刷新按钮直接替换上传
|
|
98
|
+
* @descriptionEn Whether image files support direct replacement upload via the refresh button
|
|
99
|
+
*/
|
|
100
|
+
replaceable?: boolean;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
export interface AttachmentsRef {
|
|
@@ -121,6 +127,7 @@ function Attachments(props: AttachmentsProps, ref: React.Ref<AttachmentsRef>) {
|
|
|
121
127
|
onChange,
|
|
122
128
|
overflow,
|
|
123
129
|
disabled,
|
|
130
|
+
replaceable,
|
|
124
131
|
classNames = {},
|
|
125
132
|
styles = {},
|
|
126
133
|
...uploadProps
|
|
@@ -170,6 +177,25 @@ function Attachments(props: AttachmentsProps, ref: React.Ref<AttachmentsRef>) {
|
|
|
170
177
|
});
|
|
171
178
|
};
|
|
172
179
|
|
|
180
|
+
const onItemReplace = useEvent((oldItem: Attachment, file: File) => {
|
|
181
|
+
const newAttachment: Attachment = {
|
|
182
|
+
uid: oldItem.uid,
|
|
183
|
+
name: file.name,
|
|
184
|
+
size: file.size,
|
|
185
|
+
type: file.type,
|
|
186
|
+
originFileObj: file as any,
|
|
187
|
+
status: 'done',
|
|
188
|
+
percent: 100,
|
|
189
|
+
};
|
|
190
|
+
const newFileList = fileList.map((fileItem) =>
|
|
191
|
+
fileItem.uid === oldItem.uid ? newAttachment : fileItem,
|
|
192
|
+
);
|
|
193
|
+
triggerChange({
|
|
194
|
+
file: newAttachment,
|
|
195
|
+
fileList: newFileList,
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
173
199
|
let renderChildren: React.ReactElement;
|
|
174
200
|
|
|
175
201
|
const getPlaceholderNode = (
|
|
@@ -233,6 +259,7 @@ function Attachments(props: AttachmentsProps, ref: React.Ref<AttachmentsRef>) {
|
|
|
233
259
|
prefixCls={prefixCls}
|
|
234
260
|
items={fileList}
|
|
235
261
|
onRemove={onItemRemove}
|
|
262
|
+
onReplace={replaceable ? onItemReplace : undefined}
|
|
236
263
|
overflow={overflow}
|
|
237
264
|
upload={mergedUploadProps}
|
|
238
265
|
listClassName={classnames(classNames.list)}
|
|
@@ -267,8 +294,10 @@ const ForwardAttachments = React.forwardRef(Attachments) as React.ForwardRefExot
|
|
|
267
294
|
AttachmentsProps & React.RefAttributes<AttachmentsRef>
|
|
268
295
|
> & {
|
|
269
296
|
FileCard: typeof FileListCard;
|
|
297
|
+
ImageCard: typeof ImageCard;
|
|
270
298
|
};
|
|
271
299
|
|
|
272
300
|
ForwardAttachments.FileCard = FileListCard;
|
|
301
|
+
ForwardAttachments.ImageCard = ImageCard;
|
|
273
302
|
|
|
274
303
|
export default ForwardAttachments;
|
|
@@ -61,7 +61,7 @@ export default createGlobalStyle`
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
&-type-preview {
|
|
64
|
-
width:
|
|
64
|
+
width: 100px;
|
|
65
65
|
height: 56px;
|
|
66
66
|
line-height: 1;
|
|
67
67
|
|
|
@@ -70,7 +70,7 @@ export default createGlobalStyle`
|
|
|
70
70
|
height: 100%;
|
|
71
71
|
vertical-align: top;
|
|
72
72
|
object-fit: cover;
|
|
73
|
-
border-radius:
|
|
73
|
+
border-radius: 6px;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
.${(p) => p.theme.prefixCls}-attachment-list-card-img-mask {
|
|
@@ -83,6 +83,44 @@ export default createGlobalStyle`
|
|
|
83
83
|
border-radius: inherit;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
.${(p) => p.theme.prefixCls}-attachment-list-card-img-hover-mask {
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset: 0;
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: row;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 16px;
|
|
94
|
+
background: rgba(20, 19, 39, 0.45);
|
|
95
|
+
border-radius: 6px;
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transition: opacity 0.2s;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&:hover .${(p) => p.theme.prefixCls}-attachment-list-card-img-hover-mask {
|
|
101
|
+
opacity: 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.${(p) => p.theme.prefixCls}-attachment-list-card-img-action {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
width: 20px;
|
|
109
|
+
height: 20px;
|
|
110
|
+
padding: 0;
|
|
111
|
+
border: none;
|
|
112
|
+
background: transparent;
|
|
113
|
+
color: ${(p) => p.theme.colorWhite};
|
|
114
|
+
font-size: 20px;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
line-height: 1;
|
|
117
|
+
transition: opacity 0.2s;
|
|
118
|
+
|
|
119
|
+
&:hover {
|
|
120
|
+
opacity: 0.8;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
86
124
|
&.${(p) => p.theme.prefixCls}-attachment-list-card-status-error {
|
|
87
125
|
|
|
88
126
|
img,
|
|
@@ -161,7 +199,9 @@ export default createGlobalStyle`
|
|
|
161
199
|
|
|
162
200
|
&:hover {
|
|
163
201
|
border-color: ${(p) => p.theme.colorPrimary};
|
|
202
|
+
}
|
|
164
203
|
|
|
204
|
+
&.${(p) => p.theme.prefixCls}-attachment-list-card-type-overview:hover {
|
|
165
205
|
&::after {
|
|
166
206
|
content: '';
|
|
167
207
|
position: absolute;
|
|
@@ -173,6 +213,5 @@ export default createGlobalStyle`
|
|
|
173
213
|
background-color: rgba(0, 0, 0, 0.45);
|
|
174
214
|
}
|
|
175
215
|
}
|
|
176
|
-
|
|
177
216
|
}
|
|
178
217
|
`;
|