Skip to content

Commit 3b7ebc0

Browse files
committed
feat(web): support drag and drop plugin upload
1 parent e7101e8 commit 3b7ebc0

1 file changed

Lines changed: 45 additions & 15 deletions

File tree

web/src/pages/admin/PluginsPage.tsx

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useRef, useEffect } from 'react';
1+
import { useState, useRef, useEffect, type DragEvent } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
44
import { pluginsApi } from '../../shared/api/plugins';
@@ -207,11 +207,10 @@ export default function PluginsPage() {
207207
{tabs.map((tab) => (
208208
<button
209209
key={tab.key}
210-
className={`px-4 py-2.5 text-xs font-semibold uppercase tracking-wider border-b-2 transition-all duration-200 cursor-pointer ${
211-
activeTab === tab.key
210+
className={`px-4 py-2.5 text-xs font-semibold uppercase tracking-wider border-b-2 transition-all duration-200 cursor-pointer ${activeTab === tab.key
212211
? 'border-primary text-primary shadow-[0_2px_8px_var(--ag-primary-glow)]'
213212
: 'border-transparent text-text-tertiary hover:text-text-secondary'
214-
}`}
213+
}`}
215214
onClick={() => setActiveTab(tab.key)}
216215
>
217216
{tab.label}
@@ -395,8 +394,8 @@ function PluginConfigModal({
395394
{(plugin?.config_schema || []).map((field) => {
396395
const inputType =
397396
field.type === 'password' ? 'password' :
398-
field.type === 'int' || field.type === 'float' ? 'number' :
399-
'text';
397+
field.type === 'int' || field.type === 'float' ? 'number' :
398+
'text';
400399

401400
// bool 渲染为复选框
402401
if (field.type === 'bool') {
@@ -462,6 +461,7 @@ function InstallPluginModal({
462461

463462
const [installTab, setInstallTab] = useState<'upload' | 'github'>('upload');
464463
const [selectedFile, setSelectedFile] = useState<File | null>(null);
464+
const [dragActive, setDragActive] = useState(false);
465465
const [pluginName, setPluginName] = useState('');
466466
const [githubRepo, setGithubRepo] = useState('');
467467

@@ -496,9 +496,35 @@ function InstallPluginModal({
496496

497497
function handleClose() {
498498
resetForm();
499+
setDragActive(false);
499500
onClose();
500501
}
501502

503+
function handleFileSelect(file: File | null) {
504+
setSelectedFile(file);
505+
setDragActive(false);
506+
}
507+
508+
function handleDragEvent(e: DragEvent<HTMLDivElement>) {
509+
e.preventDefault();
510+
e.stopPropagation();
511+
}
512+
513+
function handleDragEnter(e: DragEvent<HTMLDivElement>) {
514+
handleDragEvent(e);
515+
setDragActive(true);
516+
}
517+
518+
function handleDragLeave(e: DragEvent<HTMLDivElement>) {
519+
handleDragEvent(e);
520+
setDragActive(false);
521+
}
522+
523+
function handleDrop(e: DragEvent<HTMLDivElement>) {
524+
handleDragEvent(e);
525+
handleFileSelect(e.dataTransfer.files?.[0] || null);
526+
}
527+
502528
const installing = uploadMutation.isPending || githubMutation.isPending;
503529

504530
const installTabs = [
@@ -542,11 +568,10 @@ function InstallPluginModal({
542568
{installTabs.map((tab) => (
543569
<button
544570
key={tab.key}
545-
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer ${
546-
installTab === tab.key
571+
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all cursor-pointer ${installTab === tab.key
547572
? 'bg-primary text-white'
548573
: 'bg-surface text-text-secondary hover:bg-[var(--ag-bg-muted)]'
549-
}`}
574+
}`}
550575
onClick={() => setInstallTab(tab.key)}
551576
disabled={installing}
552577
>
@@ -564,18 +589,23 @@ function InstallPluginModal({
564589
{t('plugins.plugin_file')} <span className="text-danger">*</span>
565590
</label>
566591
<div
567-
className={`border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors ${
568-
selectedFile
592+
className={`border-2 border-dashed rounded-md p-6 text-center cursor-pointer transition-colors ${selectedFile
569593
? 'border-primary bg-primary-subtle'
570-
: 'border-glass-border hover:border-border-focus'
571-
}`}
594+
: dragActive
595+
? 'border-border-focus bg-[var(--ag-bg-muted)]'
596+
: 'border-glass-border hover:border-border-focus'
597+
}`}
572598
onClick={() => fileInputRef.current?.click()}
599+
onDragOver={handleDragEvent}
600+
onDragEnter={handleDragEnter}
601+
onDragLeave={handleDragLeave}
602+
onDrop={handleDrop}
573603
>
574604
<input
575605
ref={fileInputRef}
576606
type="file"
577607
className="hidden"
578-
onChange={(e) => setSelectedFile(e.target.files?.[0] || null)}
608+
onChange={(e) => handleFileSelect(e.target.files?.[0] || null)}
579609
/>
580610
{selectedFile ? (
581611
<div className="flex items-center justify-center gap-2">
@@ -587,7 +617,7 @@ function InstallPluginModal({
587617
</div>
588618
) : (
589619
<div>
590-
<Upload className="w-8 h-8 mx-auto mb-2 text-text-tertiary" />
620+
<Upload className={`w-8 h-8 mx-auto mb-2 ${dragActive ? 'text-primary' : 'text-text-tertiary'}`} />
591621
<p className="text-sm text-text-tertiary">
592622
{t('plugins.upload_hint')}
593623
</p>

0 commit comments

Comments
 (0)