file-tree-modal-create-folder.js 3.8 KB

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