file-name-input.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import {
  2. DetailedHTMLProps,
  3. InputHTMLAttributes,
  4. useCallback,
  5. useEffect,
  6. useState,
  7. } from 'react'
  8. import { File, FileOrDirectory } from '../../utils/file'
  9. import useScopeValue from '../../../../shared/hooks/use-scope-value'
  10. import { Alert } from 'react-bootstrap'
  11. import { useTranslation } from 'react-i18next'
  12. type FileNameInputProps = Omit<
  13. DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
  14. 'onFocus'
  15. > & { targetFolder: File | null }
  16. function findFile(
  17. folder: { id: string; name: string },
  18. project: FileOrDirectory
  19. ): FileOrDirectory | null {
  20. if (project.id === folder.id) {
  21. return project
  22. }
  23. if (project.type !== 'folder') {
  24. return null
  25. }
  26. for (const child of project.children ?? []) {
  27. const search = findFile(folder, child)
  28. if (search) {
  29. return search
  30. }
  31. }
  32. return null
  33. }
  34. function hasOverlap(
  35. name: string,
  36. folder: { id: string; name: string },
  37. project: FileOrDirectory
  38. ): boolean {
  39. const directory = findFile(folder, project)
  40. if (!directory) {
  41. return false
  42. }
  43. for (const child of directory.children ?? []) {
  44. if (child.name === name) {
  45. return true
  46. }
  47. }
  48. return false
  49. }
  50. export const FileNameInput = ({
  51. targetFolder,
  52. ...props
  53. }: FileNameInputProps) => {
  54. const { t } = useTranslation()
  55. const [overlap, setOverlap] = useState<boolean>(false)
  56. const [rootFolder] = useScopeValue<FileOrDirectory>('rootFolder')
  57. const { value } = props
  58. useEffect(() => {
  59. if (value) {
  60. setOverlap(
  61. hasOverlap(String(value), targetFolder ?? rootFolder, rootFolder)
  62. )
  63. } else {
  64. setOverlap(false)
  65. }
  66. }, [value, targetFolder, rootFolder])
  67. const onFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
  68. if (!event.target) {
  69. return true
  70. }
  71. const fileName = event.target.value
  72. const fileExtensionIndex = fileName.lastIndexOf('.')
  73. if (fileExtensionIndex >= 0) {
  74. event.target.setSelectionRange(0, fileExtensionIndex)
  75. }
  76. }, [])
  77. return (
  78. <>
  79. <input {...props} type="text" onFocus={onFocus} />
  80. {overlap && (
  81. <Alert bsStyle="warning" className="mt-1 mb-0">
  82. {t('a_file_with_that_name_already_exists_and_will_be_overriden')}
  83. </Alert>
  84. )}
  85. </>
  86. )
  87. }