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

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