Skip to content

Commit 1c3162a

Browse files
authored
feat: enhance DynamicTab with paste and drag-and-drop file support (#66)
- Add support for pasting images and videos directly into the DynamicTab - Implement drag-and-drop functionality for file uploads - Restrict video uploads to a single file - Improve file handling and URL creation for pasted/dropped files - Minor UI and code cleanup
1 parent 9d3f1f5 commit 1c3162a

File tree

2 files changed

+110
-16
lines changed

2 files changed

+110
-16
lines changed

src/components/Sync/DynamicTab.tsx

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
1919
const [content, setContent] = useState<string>('');
2020
const imageInputRef = useRef<HTMLInputElement>(null);
2121
const videoInputRef = useRef<HTMLInputElement>(null);
22+
const dropAreaRef = useRef<HTMLDivElement>(null);
2223
const [selectedPlatforms, setSelectedPlatforms] = useState<string[]>([]);
2324
const [autoPublish, setAutoPublish] = useState<boolean>(false);
2425
const [viewerVisible, setViewerVisible] = useState(false);
@@ -29,6 +30,24 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
2930
setTitle('开发环境标题');
3031
setContent('开发环境内容');
3132
}
33+
34+
// 添加粘贴事件监听器
35+
document.addEventListener('paste', handlePaste);
36+
37+
// 添加拖拽事件监听器
38+
const dropArea = dropAreaRef.current;
39+
if (dropArea) {
40+
dropArea.addEventListener('dragover', handleDragOver);
41+
dropArea.addEventListener('drop', handleDrop);
42+
}
43+
44+
return () => {
45+
document.removeEventListener('paste', handlePaste);
46+
if (dropArea) {
47+
dropArea.removeEventListener('dragover', handleDragOver);
48+
dropArea.removeEventListener('drop', handleDrop);
49+
}
50+
};
3251
}, []);
3352

3453
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>, fileType: 'image' | 'video') => {
@@ -45,9 +64,92 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
4564
if (fileType === 'image') {
4665
setImages((prevImages) => [...prevImages, ...newFiles]);
4766
} else {
48-
setVideos((prevVideos) => [...prevVideos, ...newFiles]);
67+
// 只允许一个视频,如果已有视频则替换
68+
setVideos([newFiles[0]]);
69+
}
70+
}
71+
};
72+
73+
const handlePaste = (event: ClipboardEvent) => {
74+
const items = event.clipboardData?.items;
75+
if (!items) return;
76+
77+
for (let i = 0; i < items.length; i++) {
78+
const item = items[i];
79+
if (item.kind === 'file') {
80+
const file = item.getAsFile();
81+
if (!file) continue;
82+
83+
if (file.type.startsWith('image/')) {
84+
setImages((prevImages) => [
85+
...prevImages,
86+
{
87+
name: file.name || `pasted-image-${Date.now()}.png`,
88+
type: file.type,
89+
size: file.size,
90+
url: URL.createObjectURL(file),
91+
},
92+
]);
93+
} else if (file.type.startsWith('video/')) {
94+
// 只允许一个视频
95+
if (videos.length === 0) {
96+
setVideos([
97+
{
98+
name: file.name || `pasted-video-${Date.now()}.mp4`,
99+
type: file.type,
100+
size: file.size,
101+
url: URL.createObjectURL(file),
102+
},
103+
]);
104+
}
105+
break; // 处理完第一个视频后退出循环
106+
}
107+
}
108+
}
109+
};
110+
111+
const handleDragOver = (event: DragEvent) => {
112+
event.preventDefault();
113+
event.stopPropagation();
114+
};
115+
116+
const handleDrop = (event: DragEvent) => {
117+
event.preventDefault();
118+
event.stopPropagation();
119+
120+
const files = event.dataTransfer?.files;
121+
if (!files) return;
122+
123+
const imageFiles: FileData[] = [];
124+
let videoFile: FileData | null = null;
125+
126+
for (let i = 0; i < files.length; i++) {
127+
const file = files[i];
128+
if (file.type.startsWith('image/')) {
129+
imageFiles.push({
130+
name: file.name,
131+
type: file.type,
132+
size: file.size,
133+
url: URL.createObjectURL(file),
134+
});
135+
} else if (file.type.startsWith('video/') && !videoFile) {
136+
// 只取第一个视频文件
137+
videoFile = {
138+
name: file.name,
139+
type: file.type,
140+
size: file.size,
141+
url: URL.createObjectURL(file),
142+
};
49143
}
50144
}
145+
146+
if (imageFiles.length > 0) {
147+
setImages((prevImages) => [...prevImages, ...imageFiles]);
148+
}
149+
150+
if (videoFile && videos.length === 0) {
151+
setVideos([videoFile]);
152+
}
51153
};
52154

53155
const handlePlatformChange = (platform: string, isSelected: boolean) => {
@@ -65,12 +167,7 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
65167
alert(chrome.i18n.getMessage('optionsSelectPublishPlatforms'));
66168
return;
67169
}
68-
// const needImage = PLATFORM_NEED_IMAGE.some((platform) => selectedPlatforms.includes(platform));
69-
// if (needImage && images.length === 0) {
70-
// console.log('至少一张图片');
71-
// alert(chrome.i18n.getMessage('optionsAtLeastOneImage'));
72-
// return;
73-
// }
170+
74171
const data: SyncData = {
75172
platforms: selectedPlatforms,
76173
data: {
@@ -110,7 +207,7 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
110207
if (fileType === 'image') {
111208
setImages((prevImages) => prevImages.filter((_, i) => i !== index));
112209
} else {
113-
setVideos((prevVideos) => prevVideos.filter((_, i) => i !== index));
210+
setVideos([]);
114211
}
115212
};
116213

@@ -124,14 +221,13 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
124221
};
125222

126223
return (
127-
<div className="flex flex-col gap-4">
224+
<div className="flex flex-col gap-4" ref={dropAreaRef}>
128225
<Card className="shadow-none bg-default-50">
129226
<CardHeader className="flex flex-col gap-4">
130227
<Input
131228
isClearable
132229
variant="underlined"
133230
label={chrome.i18n.getMessage('optionsEnterDynamicTitle')}
134-
// placeholder={chrome.i18n.getMessage('optionsEnterDynamicTitle')}
135231
value={title}
136232
onChange={(e) => setTitle(e.target.value)}
137233
onClear={() => setTitle('')}
@@ -143,7 +239,6 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
143239
<Textarea
144240
isClearable
145241
label={chrome.i18n.getMessage('optionsEnterDynamicContent')}
146-
// placeholder={chrome.i18n.getMessage('optionsEnterDynamicContent')}
147242
value={content}
148243
onChange={(e) => setContent(e.target.value)}
149244
variant="underlined"
@@ -177,7 +272,6 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
177272
accept="video/*"
178273
onChange={(e) => handleFileChange(e, 'video')}
179274
className="hidden"
180-
multiple
181275
/>
182276
<Button
183277
isIconOnly
@@ -230,7 +324,7 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
230324
</Card>
231325
)}
232326

233-
<div className="flex flex-col gap-4 bg-default-50 p-4 rounded-lg">
327+
<div className="flex flex-col gap-4 p-4 rounded-lg bg-default-50">
234328
<Switch
235329
isSelected={autoPublish}
236330
onValueChange={setAutoPublish}
@@ -261,7 +355,7 @@ const DynamicTab: React.FC<DynamicTabProps> = ({ funcPublish }) => {
261355
variant="flat"
262356
disabled={!title || !content || selectedPlatforms.length === 0}
263357
className="w-full font-medium shadow-none">
264-
<SendIcon className="size-4 mr-2" />
358+
<SendIcon className="mr-2 size-4" />
265359
{chrome.i18n.getMessage('optionsSyncDynamic')}
266360
</Button>
267361

src/components/Sync/VideoTab.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const VideoTab: React.FC<VideoTabProps> = ({ funcPublish }) => {
181181
</Card>
182182
)}
183183

184-
<div className="flex flex-col gap-4 bg-default-50 p-4 rounded-lg">
184+
<div className="flex flex-col gap-4 p-4 rounded-lg bg-default-50">
185185
<div className="flex flex-col gap-2">
186186
<p className="text-sm font-medium">{chrome.i18n.getMessage('optionsSelectPublishPlatforms')}</p>
187187
<div className="grid grid-cols-2 gap-3">
@@ -204,7 +204,7 @@ const VideoTab: React.FC<VideoTabProps> = ({ funcPublish }) => {
204204
variant="flat"
205205
disabled={!videoFile || !title || !content || selectedPlatforms.length === 0}
206206
className="w-full font-medium shadow-none">
207-
<SendIcon className="size-4 mr-2" />
207+
<SendIcon className="mr-2 size-4" />
208208
{chrome.i18n.getMessage('optionsSyncVideo')}
209209
</Button>
210210
</div>

0 commit comments

Comments
 (0)