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

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