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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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. }) {
  24. const { t } = useTranslation()
  25. // the value is stored in a context provider, so it's available elsewhere in the form
  26. const { name, setName, touchedName, validName } = useFileTreeCreateName()
  27. // focus the first part of the filename if needed
  28. const inputRef = useCallback(
  29. element => {
  30. if (element && focusName) {
  31. window.requestAnimationFrame(() => {
  32. element.focus()
  33. element.setSelectionRange(0, element.value.lastIndexOf('.'))
  34. })
  35. }
  36. },
  37. [focusName]
  38. )
  39. return (
  40. <FormGroup controlId="new-doc-name" className={classes.formGroup}>
  41. <ControlLabel>{label || t('file_name')}</ControlLabel>
  42. <FormControl
  43. type="text"
  44. placeholder={placeholder || t('file_name')}
  45. required
  46. value={name}
  47. onChange={event => setName(event.target.value)}
  48. inputRef={inputRef}
  49. />
  50. <FormControl.Feedback />
  51. {touchedName && !validName && (
  52. <Alert bsStyle="danger" className="row-spaced-small">
  53. {t('files_cannot_include_invalid_characters')}
  54. </Alert>
  55. )}
  56. {error && <ErrorMessage error={error} />}
  57. </FormGroup>
  58. )
  59. }
  60. FileTreeCreateNameInput.propTypes = {
  61. focusName: PropTypes.bool,
  62. label: PropTypes.string,
  63. classes: PropTypes.shape({
  64. formGroup: PropTypes.string,
  65. }),
  66. placeholder: PropTypes.string,
  67. error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
  68. }
  69. function ErrorMessage({ error }) {
  70. const { t } = useTranslation()
  71. // if (typeof error === 'string') {
  72. // return error
  73. // }
  74. switch (error.constructor) {
  75. case DuplicateFilenameError:
  76. return (
  77. <Alert bsStyle="danger" className="row-spaced-small">
  78. {t('file_already_exists')}
  79. </Alert>
  80. )
  81. case InvalidFilenameError:
  82. return (
  83. <Alert bsStyle="danger" className="row-spaced-small">
  84. {t('files_cannot_include_invalid_characters')}
  85. </Alert>
  86. )
  87. case BlockedFilenameError:
  88. return (
  89. <Alert bsStyle="danger" className="row-spaced-small">
  90. {t('blocked_filename')}
  91. </Alert>
  92. )
  93. default:
  94. // return <Trans i18nKey="generic_something_went_wrong" />
  95. return null // other errors are displayed elsewhere
  96. }
  97. }
  98. ErrorMessage.propTypes = {
  99. error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
  100. }