[ABANDONED] React/Redux front end for the Flexor social network.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
4.7 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. import React, { FC, ChangeEvent, useState } from 'react'
  2. import { useSelector, useDispatch } from 'react-redux'
  3. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
  4. import { faUpload } from '@fortawesome/free-solid-svg-icons'
  5. import { BlockBlobClient, AnonymousCredential } from '@azure/storage-blob'
  6. import { useTheme } from '../../hooks'
  7. import { setFieldValue } from '../../actions/forms'
  8. import { showNotification } from '../../actions/notifications'
  9. import { getFieldValue } from '../../selectors/forms'
  10. import { apiFetch } from '../../api/fetch'
  11. import { MEDIA_DEFAULT_MAX_SIZE } from '../../constants'
  12. import { AppState, AppThunkDispatch, SasResponse, NotificationType } from '../../types'
  13. import Progress from '../../components/progress'
  14. import FieldLabel from '../../components/controls/field-label'
  15. interface Props {
  16. name: string
  17. label: string
  18. help?: string
  19. previewWidth?: number
  20. maxSize?: number
  21. }
  22. const FileField: FC<Props> = props => {
  23. const theme = useTheme()
  24. const value = useSelector<AppState, string>(state => getFieldValue<string>(state, props.name, ''))
  25. const dispatch = useDispatch<AppThunkDispatch>()
  26. const { name, label, help, previewWidth = 128, maxSize = MEDIA_DEFAULT_MAX_SIZE } = props
  27. const [progress, setProgress] = useState(0)
  28. const [uploading, setUploading] = useState(false)
  29. const [uploaded, setUploaded] = useState(false)
  30. const handleChange = async (event: ChangeEvent<HTMLInputElement>) => {
  31. if (event.target.files) {
  32. const file = event.target.files[0]
  33. if (file.size > maxSize) {
  34. const maxSizeString = Math.round(maxSize / 1024 / 1024)
  35. dispatch(showNotification(NotificationType.Error, `Files must be less than ${maxSizeString} MBs`))
  36. return
  37. }
  38. const ext = file.name.substring(file.name.lastIndexOf('.'))
  39. const { sas, blobUrl, id } = await apiFetch<SasResponse>({ path: '/v1/sas' })
  40. const filename = `${id}${ext}`
  41. setUploading(true)
  42. try {
  43. const blockBlobClient = new BlockBlobClient(`${blobUrl}/${filename}?${sas}`, new AnonymousCredential())
  44. await blockBlobClient.uploadBrowserData(file, {
  45. onProgress: p => {
  46. setProgress((p.loadedBytes / file.size) * 100)
  47. }
  48. })
  49. await apiFetch({
  50. path: '/v1/media',
  51. method: 'post',
  52. body: {
  53. name: filename,
  54. size: file.size,
  55. type: file.type,
  56. originalName: file.name,
  57. }
  58. })
  59. dispatch(setFieldValue(name, `${blobUrl}/${filename}`))
  60. setUploaded(true)
  61. } catch (err) {
  62. console.error(err)
  63. dispatch(showNotification(NotificationType.Error, `Upload error: ${err}`))
  64. }
  65. setUploading(false)
  66. }
  67. }
  68. const handleDelete = async () => {
  69. if (uploaded) {
  70. await apiFetch({
  71. path: `/v1/media?name=${value}`,
  72. method: 'delete',
  73. })
  74. }
  75. dispatch(setFieldValue(name, ''))
  76. }
  77. if (uploading) {
  78. return (
  79. <div className="field">
  80. <FieldLabel>{label}</FieldLabel>
  81. <Progress value={progress} />
  82. </div>
  83. )
  84. }
  85. return (
  86. <div className="field">
  87. <FieldLabel>{label}</FieldLabel>
  88. {value &&
  89. <div style={{ padding: '1rem 0px' }}>
  90. <img src={value} style={{ width: previewWidth }} />
  91. <div style={{ color: theme.secondary, fontSize: '0.8rem' }}>
  92. {value.split('/').pop()}
  93. &nbsp;&nbsp;
  94. (<a style={{ color: theme.red }} onClick={() => handleDelete()}>Delete</a>)
  95. </div>
  96. </div>
  97. }
  98. {!value &&
  99. <div>
  100. <label className="file-input" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
  101. <input type="file" name={name} onChange={handleChange} />
  102. <div className="icon">
  103. <FontAwesomeIcon icon={faUpload} />
  104. </div>
  105. <span>Choose a file...</span>
  106. </label>
  107. <p className="help" style={{ color: theme.text }}>{help}</p>
  108. </div>
  109. }
  110. </div>
  111. )
  112. }
  113. export default FileField