1- import { useState , useRef , useEffect } from 'react' ;
1+ import { useState , useRef , useEffect , type DragEvent } from 'react' ;
22import { useTranslation } from 'react-i18next' ;
33import { useQuery , useMutation , useQueryClient } from '@tanstack/react-query' ;
44import { 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