file-tree-modal-create-folder.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import React, { useEffect, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
  4. import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
  5. import { DuplicateFilenameError } from '../../errors'
  6. import { isCleanFilename } from '../../util/safe-path'
  7. import OLModal, {
  8. OLModalBody,
  9. OLModalFooter,
  10. OLModalHeader,
  11. OLModalTitle,
  12. } from '@/shared/components/ol/ol-modal'
  13. import OLButton from '@/shared/components/ol/ol-button'
  14. import OLFormControl from '@/shared/components/ol/ol-form-control'
  15. import OLFormLabel from '@/shared/components/ol/ol-form-label'
  16. import OLFormGroup from '@/shared/components/ol/ol-form-group'
  17. function FileTreeModalCreateFolder() {
  18. const { t } = useTranslation()
  19. const [name, setName] = useState('')
  20. const [validName, setValidName] = useState(true)
  21. const { isCreatingFolder, inFlight, finishCreatingFolder, cancel, error } =
  22. useFileTreeActionable()
  23. useEffect(() => {
  24. if (!isCreatingFolder) {
  25. // clear the input when the modal is closed
  26. setName('')
  27. }
  28. }, [isCreatingFolder])
  29. if (!isCreatingFolder) return null // the modal will not be rendered; return early
  30. function handleHide() {
  31. cancel()
  32. }
  33. function handleCreateFolder() {
  34. finishCreatingFolder(name)
  35. }
  36. function errorMessage() {
  37. switch (error.constructor) {
  38. case DuplicateFilenameError:
  39. return t('file_already_exists')
  40. default:
  41. return t('generic_something_went_wrong')
  42. }
  43. }
  44. return (
  45. <OLModal show onHide={handleHide}>
  46. <OLModalHeader>
  47. <OLModalTitle>{t('new_folder')}</OLModalTitle>
  48. </OLModalHeader>
  49. <OLModalBody>
  50. <InputName
  51. name={name}
  52. setName={setName}
  53. validName={validName}
  54. setValidName={setValidName}
  55. handleCreateFolder={handleCreateFolder}
  56. />
  57. {!validName ? (
  58. <div
  59. role="alert"
  60. aria-label={t('files_cannot_include_invalid_characters')}
  61. className="alert alert-danger file-tree-modal-alert"
  62. >
  63. {t('files_cannot_include_invalid_characters')}
  64. </div>
  65. ) : null}
  66. {error ? (
  67. <div
  68. role="alert"
  69. aria-label={errorMessage()}
  70. className="alert alert-danger file-tree-modal-alert"
  71. >
  72. {errorMessage()}
  73. </div>
  74. ) : null}
  75. </OLModalBody>
  76. <OLModalFooter>
  77. {inFlight ? (
  78. <OLButton
  79. variant="primary"
  80. disabled
  81. isLoading={inFlight}
  82. loadingLabel={t('creating')}
  83. />
  84. ) : (
  85. <>
  86. <OLButton variant="secondary" onClick={handleHide}>
  87. {t('cancel')}
  88. </OLButton>
  89. <OLButton
  90. variant="primary"
  91. onClick={handleCreateFolder}
  92. disabled={!validName}
  93. >
  94. {t('create')}
  95. </OLButton>
  96. </>
  97. )}
  98. </OLModalFooter>
  99. </OLModal>
  100. )
  101. }
  102. function InputName({
  103. name,
  104. setName,
  105. validName,
  106. setValidName,
  107. handleCreateFolder,
  108. }: {
  109. name: string
  110. setName: (name: string) => void
  111. validName: boolean
  112. setValidName: (validName: boolean) => void
  113. handleCreateFolder: () => void
  114. }) {
  115. const { autoFocusedRef } = useRefWithAutoFocus<HTMLInputElement>()
  116. const { t } = useTranslation()
  117. function handleFocus(ev: React.FocusEvent<HTMLInputElement>) {
  118. ev.target.setSelectionRange(0, -1)
  119. }
  120. function handleChange(ev: React.ChangeEvent<HTMLInputElement>) {
  121. setValidName(isCleanFilename(ev.target.value.trim()))
  122. setName(ev.target.value)
  123. }
  124. function handleKeyDown(ev: React.KeyboardEvent<HTMLInputElement>) {
  125. if (ev.key === 'Enter' && validName) {
  126. handleCreateFolder()
  127. }
  128. }
  129. return (
  130. <OLFormGroup controlId="folder-name">
  131. <OLFormLabel>{t('folder_name')}</OLFormLabel>
  132. <OLFormControl
  133. type="text"
  134. ref={autoFocusedRef}
  135. value={name}
  136. onKeyDown={handleKeyDown}
  137. onChange={handleChange}
  138. onFocus={handleFocus}
  139. />
  140. </OLFormGroup>
  141. )
  142. }
  143. export default FileTreeModalCreateFolder