1- import { getFileIcon , getDirIcon , extractExtension } from "@/utils/getFileIcon" ;
2- import { Popover , PopoverTrigger , Button , PopoverSurface , Input , Text , Subtitle1 , Tooltip , Menu , MenuItem , MenuList , MenuPopover , MenuTrigger , Subtitle2 , Select } from '@fluentui/react-components' ;
1+ import { checkFileName } from "@/utils/checkFileName" ;
2+ import { extractExtension , getDirIcon , getFileIcon } from "@/utils/getFileIcon" ;
3+ import { Button , Input , Popover , PopoverSurface , PopoverTrigger , Subtitle2 , Text , Tooltip } from '@fluentui/react-components' ;
4+ import { bundleIcon , DeleteFilled , DeleteRegular , RenameFilled , RenameRegular } from "@fluentui/react-icons" ;
5+ import { t } from "@lingui/macro" ;
6+ import { useRef } from "react" ;
7+ import { useValue } from '../../hooks/useValue' ;
38import IconWrapper from "../iconWrapper/IconWrapper" ;
49import { IFile , IViewType } from "./Assets" ;
510import styles from "./FileElement.module.scss" ;
6- import { useValue } from '../../hooks/useValue' ;
7- import { bundleIcon , RenameFilled , RenameRegular , DeleteFilled , DeleteRegular , DesktopMacFilled , DesktopMacRegular , MoreVerticalFilled , MoreVerticalRegular } from "@fluentui/react-icons" ;
8- import { t } from "@lingui/macro" ;
9- import { useRef } from "react" ;
1011
1112const RenameIcon = bundleIcon ( RenameFilled , RenameRegular ) ;
1213const DeleteIcon = bundleIcon ( DeleteFilled , DeleteRegular ) ;
1314
1415export default function FileElement (
15- {
16+ {
1617 rootPath,
1718 file,
1819 type,
@@ -24,17 +25,17 @@ export default function FileElement(
2425 handleDeleteFile,
2526 checkHasFile,
2627 } : {
27- rootPath : string [ ] ,
28- file : IFile ,
29- type : IViewType ,
30- selected ?: boolean ,
31- desc ?: string ,
32- isProtected ?: boolean ,
33- handleOpenFile : ( file : IFile ) => Promise < void > ,
34- handleRenameFile : ( source : string , newName : string ) => Promise < void > ,
35- handleDeleteFile : ( source : string ) => Promise < void > ,
36- checkHasFile : ( fileNmae : string ) => boolean ,
37- } ) {
28+ rootPath : string [ ] ,
29+ file : IFile ,
30+ type : IViewType ,
31+ selected ?: boolean ,
32+ desc ?: string ,
33+ isProtected ?: boolean ,
34+ handleOpenFile : ( file : IFile ) => Promise < void > ,
35+ handleRenameFile : ( source : string , newName : string ) => Promise < void > ,
36+ handleDeleteFile : ( source : string ) => Promise < void > ,
37+ checkHasFile : ( fileNmae : string ) => boolean ,
38+ } ) {
3839 const newFileName = useValue ( file . name ) ;
3940 const FileItemSelfRef = useRef ( null ) ;
4041 const showTooltip = useValue ( false ) ;
@@ -43,11 +44,13 @@ export default function FileElement(
4344
4445 const is_picture = ( extName : string ) => extractExtension ( extName ) === 'image' ? true : false ;
4546
47+ const isAccessible = checkFileName ( newFileName . value ) ;
48+
4649 return (
4750 < Tooltip
4851 content = {
4952 < div
50- style = { { display : 'flex' , flexDirection : 'column' , padding : 0 } }
53+ style = { { display : 'flex' , flexDirection : 'column' , padding : 0 } }
5154 onMouseEnter = { ( ) => showTooltip . set ( false ) }
5255 onMouseMove = { ( ) => showTooltip . set ( false ) }
5356 >
@@ -104,8 +107,8 @@ export default function FileElement(
104107 {
105108 ! file . isDir && (
106109 is_picture ( file . extName ) && type === 'grid'
107- ? < img src = { filePath } draggable = 'false' style = { { width : '100%' , height : '100%' , objectFit : 'contain' , } } />
108- : < IconWrapper src = { getFileIcon ( file . name ) } size = { type === 'grid' ? 44 : 22 } iconSize = { type === 'grid' ? 40 : 20 } />
110+ ? < img src = { filePath } draggable = 'false' style = { { width : '100%' , height : '100%' , objectFit : 'contain' , } } />
111+ : < IconWrapper src = { getFileIcon ( file . name ) } size = { type === 'grid' ? 44 : 22 } iconSize = { type === 'grid' ? 40 : 20 } />
109112 )
110113 }
111114 { file . isDir && < IconWrapper src = { getDirIcon ( file . path ) } size = { type === 'grid' ? 44 : 22 } iconSize = { type === 'grid' ? 40 : 20 } /> }
@@ -136,93 +139,100 @@ export default function FileElement(
136139 < div className = { styles . fileAction } >
137140 {
138141 ! isProtected &&
139- < >
140- < Popover withArrow onOpenChange = { ( ) => newFileName . set ( file . name ) } >
141- < PopoverTrigger >
142- < Tooltip content = { t `重命名` } relationship = "label" positioning = "below" >
143- < Button
144- icon = { < RenameIcon style = { { width : '16px' } } /> }
145- size = 'small'
146- appearance = 'subtle'
147- onClick = { ( e ) => e . stopPropagation ( ) }
148- />
149- </ Tooltip >
150- </ PopoverTrigger >
151- < PopoverSurface
152- onClick = { ( e ) => e . stopPropagation ( ) }
153- onKeyDown = { ( e ) => {
154- e . stopPropagation ( ) ;
155- if ( ( e . key === 'Enter' ) && ! checkHasFile ( newFileName . value ) ) {
156- handleRenameFile ( filePath , newFileName . value . trim ( ) ) ;
157- } ;
158- } }
159- >
160- < div style = { { display : "flex" , flexFlow : "column" , gap : "16px" } } >
161- < Subtitle2 > { t `重命名` } </ Subtitle2 >
162- < Tooltip
163- content = { { children : t `已存在文件或文件夹 ${ newFileName . value } ,请输入其他名称` , style : { color : 'var(--danger)' } } }
164- relationship = "description"
165- visible = { checkHasFile ( newFileName . value ) && newFileName . value !== file . name }
166- positioning = "below"
167- >
168- < Input
169- value = { newFileName . value }
170- className = { checkHasFile ( newFileName . value ) && newFileName . value !== file . name ? styles . inputDanger : '' }
171- onFocus = { ev => {
172- const el = ev . target ;
173- const dotPosition = el . value . indexOf ( '.' ) ;
174- el ?. setSelectionRange ( 0 , dotPosition === - 1 ? el . value . length : dotPosition ) ;
142+ < >
143+ < Popover withArrow onOpenChange = { ( ) => newFileName . set ( file . name ) } >
144+ < PopoverTrigger >
145+ < Tooltip content = { t `重命名` } relationship = "label" positioning = "below" >
146+ < Button
147+ icon = { < RenameIcon style = { { width : '16px' } } /> }
148+ size = 'small'
149+ appearance = 'subtle'
150+ onClick = { ( e ) => e . stopPropagation ( ) }
151+ />
152+ </ Tooltip >
153+ </ PopoverTrigger >
154+ < PopoverSurface
155+ onClick = { ( e ) => e . stopPropagation ( ) }
156+ onKeyDown = { ( e ) => {
157+ e . stopPropagation ( ) ;
158+ if ( ( e . key === 'Enter' ) && ! ( newFileName . value . trim ( ) === '' || ! isAccessible || checkHasFile ( newFileName . value ) && newFileName . value !== file . name ) ) {
159+ handleRenameFile ( filePath , newFileName . value . trim ( ) ) ;
160+ } ;
175161 } }
176- onChange = { ( _ , data ) => {
177- newFileName . set ( data . value ?? "" ) ;
178- } }
179- />
180- </ Tooltip >
181- < Button
182- appearance = "primary"
183- disabled = { newFileName . value . trim ( ) === '' || checkHasFile ( newFileName . value ) && newFileName . value !== file . name }
184- onClick = { ( ) => handleRenameFile ( filePath , newFileName . value . trim ( ) ) }
185- > { t `重命名` } </ Button >
186- </ div >
187- </ PopoverSurface >
188- </ Popover >
162+ >
163+ < div style = { { display : "flex" , flexFlow : "column" , gap : "16px" } } >
164+ < Subtitle2 > { t `重命名` } </ Subtitle2 >
165+ < Tooltip
166+ content = { { children : t `文件名不可包含特殊符号: '/\\:*?"<>|'` , style : { color : 'var(--danger)' } } }
167+ relationship = "inaccessible"
168+ visible = { ! isAccessible }
169+ positioning = "below"
170+ >
171+ < Tooltip
172+ content = { { children : t `已存在文件或文件夹 ${ newFileName . value } ,请输入其他名称` , style : { color : 'var(--danger)' } } }
173+ relationship = "description"
174+ visible = { checkHasFile ( newFileName . value ) && newFileName . value !== file . name }
175+ positioning = "below"
176+ >
177+ < Input
178+ value = { newFileName . value }
179+ className = { checkHasFile ( newFileName . value ) && newFileName . value !== file . name ? styles . inputDanger : '' }
180+ onFocus = { ev => {
181+ const el = ev . target ;
182+ const dotPosition = el . value . indexOf ( '.' ) ;
183+ el ?. setSelectionRange ( 0 , dotPosition === - 1 ? el . value . length : dotPosition ) ;
184+ } }
185+ onChange = { ( _ , data ) => {
186+ newFileName . set ( data . value ?? "" ) ;
187+ } }
188+ />
189+ </ Tooltip >
190+ </ Tooltip >
191+ < Button
192+ appearance = "primary"
193+ disabled = { newFileName . value . trim ( ) === '' || ! isAccessible || checkHasFile ( newFileName . value ) && newFileName . value !== file . name }
194+ onClick = { ( ) => handleRenameFile ( filePath , newFileName . value . trim ( ) ) }
195+ > { t `重命名` } </ Button >
196+ </ div >
197+ </ PopoverSurface >
198+ </ Popover >
189199
190- < Popover withArrow >
191- < PopoverTrigger >
192- < Tooltip content = { t `删除` } relationship = "label" positioning = "below" >
193- < Button
194- icon = { < DeleteIcon style = { { width : '16px' } } /> }
195- size = 'small'
196- appearance = 'subtle'
197- onClick = { ( e ) => e . stopPropagation ( ) }
198- />
199- </ Tooltip >
200- </ PopoverTrigger >
201- < PopoverSurface
202- onClick = { ( e ) => e . stopPropagation ( ) }
203- onKeyDown = { ( e ) => {
204- e . stopPropagation ( ) ;
205- ( e . key === 'Enter' ) && handleDeleteFile ( filePath ) ;
206- } }
207- >
208- < div style = { { display : "flex" , flexFlow : "column" , gap : "16px" } } >
209- < Subtitle2 > { t `删除` } </ Subtitle2 >
210- < Text > { t `是否要删除 "${ file . name } " ?` } </ Text >
211- < Button
212- appearance = "primary"
213- onClick = { ( e ) => {
214- e . stopPropagation ( ) ;
215- handleDeleteFile ( filePath ) ;
216- } }
217- > { t `删除` } </ Button >
218- </ div >
219- </ PopoverSurface >
220- </ Popover >
221- </ >
200+ < Popover withArrow >
201+ < PopoverTrigger >
202+ < Tooltip content = { t `删除` } relationship = "label" positioning = "below" >
203+ < Button
204+ icon = { < DeleteIcon style = { { width : '16px' } } /> }
205+ size = 'small'
206+ appearance = 'subtle'
207+ onClick = { ( e ) => e . stopPropagation ( ) }
208+ />
209+ </ Tooltip >
210+ </ PopoverTrigger >
211+ < PopoverSurface
212+ onClick = { ( e ) => e . stopPropagation ( ) }
213+ onKeyDown = { ( e ) => {
214+ e . stopPropagation ( ) ;
215+ ( e . key === 'Enter' ) && handleDeleteFile ( filePath ) ;
216+ } }
217+ >
218+ < div style = { { display : "flex" , flexFlow : "column" , gap : "16px" } } >
219+ < Subtitle2 > { t `删除` } </ Subtitle2 >
220+ < Text > { t `是否要删除 "${ file . name } " ?` } </ Text >
221+ < Button
222+ appearance = "primary"
223+ onClick = { ( e ) => {
224+ e . stopPropagation ( ) ;
225+ handleDeleteFile ( filePath ) ;
226+ } }
227+ > { t `删除` } </ Button >
228+ </ div >
229+ </ PopoverSurface >
230+ </ Popover >
231+ </ >
222232 }
223233 </ div >
224234 </ div >
225235 </ div >
226- </ Tooltip >
236+ </ Tooltip >
227237 ) ;
228238}
0 commit comments