file-tree-create-name-input.jsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import ControlLabel from 'react-bootstrap/lib/ControlLabel'
  2. import { Alert, FormControl } from 'react-bootstrap'
  3. import FormGroup from 'react-bootstrap/lib/FormGroup'
  4. import { useCallback } from 'react'
  5. import { useTranslation } from 'react-i18next'
  6. import { useFileTreeCreateName } from '../../contexts/file-tree-create-name'
  7. import PropTypes from 'prop-types'
  8. import {
  9. BlockedFilenameError,
  10. DuplicateFilenameError,
  11. InvalidFilenameError,
  12. } from '../../errors'
  13. /**
  14. * A form component that renders a text input with label,
  15. * plus a validation warning and/or an error message when needed
  16. */
  17. export default function FileTreeCreateNameInput({
  18. label,
  19. focusName = false,
  20. classes = {},
  21. placeholder,
  22. error,
  23. inFlight,
  24. }) {
  25. const { t } = useTranslation()
  26. // the value is stored in a context provider, so it's available elsewhere in the form
  27. const { name, setName, touchedName, validName } = useFileTreeCreateName()
  28. // focus the first part of the filename if needed
  29. const inputRef = useCallback(
  30. element => {
  31. if (element && focusName) {
  32. window.requestAnimationFrame(() => {
  33. element.focus()
  34. element.setSelectionRange(0, element.value.lastIndexOf('.'))
  35. })
  36. }
  37. },
  38. [focusName]
  39. )
  40. return (
  41. <FormGroup controlId="new-doc-name" className={classes.formGroup}>
  42. <ControlLabel>{label || t('file_name')}</ControlLabel>
  43. <FormControl
  44. type="text"
  45. placeholder={placeholder || t('file_name')}
  46. required
  47. value={name}
  48. onChange={event => setName(event.target.value)}
  49. inputRef={inputRef}
  50. disabled={inFlight}
  51. />
  52. <FormControl.Feedback />
  53. {touchedName && !validName && (
  54. <Alert bsStyle="danger" className="row-spaced-small">
  55. {t('files_cannot_include_invalid_characters')}
  56. </Alert>
  57. )}
  58. {error && <ErrorMessage error={error} />}
  59. </FormGroup>
  60. )
  61. }
  62. FileTreeCreateNameInput.propTypes = {
  63. focusName: PropTypes.bool,
  64. label: PropTypes.string,
  65. classes: PropTypes.shape({
  66. formGroup: PropTypes.string,
  67. }),
  68. placeholder: PropTypes.string,
  69. error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  70. inFlight: PropTypes.bool.isRequired,
  71. }
  72. function ErrorMessage({ error }) {
  73. const { t } = useTranslation()
  74. // if (typeof error === 'string') {
  75. // return error
  76. // }
  77. switch (error.constructor) {
  78. case DuplicateFilenameError:
  79. return (
  80. <Alert bsStyle="danger" className="row-spaced-small">
  81. {t('file_already_exists')}
  82. </Alert>
  83. )
  84. case InvalidFilenameError:
  85. return (
  86. <Alert bsStyle="danger" className="row-spaced-small">
  87. {t('files_cannot_include_invalid_characters')}
  88. </Alert>
  89. )
  90. case BlockedFilenameError:
  91. return (
  92. <Alert bsStyle="danger" className="row-spaced-small">
  93. {t('blocked_filename')}
  94. </Alert>
  95. )
  96. default:
  97. // return <Trans i18nKey="generic_something_went_wrong" />
  98. return null // other errors are displayed elsewhere
  99. }
  100. }
  101. ErrorMessage.propTypes = {
  102. error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
  103. }