index.jsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { h } from 'preact'
  2. import { useEffect, useState, useCallback } from 'preact/hooks'
  3. import classNames from 'classnames'
  4. import { nanoid } from 'nanoid/non-secure'
  5. import getFileTypeIcon from '../../utils/getFileTypeIcon.jsx'
  6. import ignoreEvent from '../../utils/ignoreEvent.js'
  7. import FilePreview from '../FilePreview.jsx'
  8. import RenderMetaFields from './RenderMetaFields.jsx'
  9. export default function FileCard (props) {
  10. const {
  11. files,
  12. fileCardFor,
  13. toggleFileCard,
  14. saveFileCard,
  15. metaFields,
  16. requiredMetaFields,
  17. openFileEditor,
  18. i18n,
  19. i18nArray,
  20. className,
  21. canEditFile,
  22. } = props
  23. const getMetaFields = () => {
  24. return typeof metaFields === 'function'
  25. ? metaFields(files[fileCardFor])
  26. : metaFields
  27. }
  28. const file = files[fileCardFor]
  29. const computedMetaFields = getMetaFields() ?? []
  30. const showEditButton = canEditFile(file)
  31. const storedMetaData = {}
  32. computedMetaFields.forEach((field) => {
  33. storedMetaData[field.id] = file.meta[field.id] ?? ''
  34. })
  35. const [formState, setFormState] = useState(storedMetaData)
  36. const handleSave = useCallback((ev) => {
  37. ev.preventDefault()
  38. saveFileCard(formState, fileCardFor)
  39. }, [saveFileCard, formState, fileCardFor])
  40. const updateMeta = (newVal, name) => {
  41. setFormState({
  42. ...formState,
  43. [name]: newVal,
  44. })
  45. }
  46. const handleCancel = () => {
  47. toggleFileCard(false)
  48. }
  49. const [form] = useState(() => {
  50. const formEl = document.createElement('form')
  51. formEl.setAttribute('tabindex', '-1')
  52. formEl.id = nanoid()
  53. return formEl
  54. })
  55. useEffect(() => {
  56. document.body.appendChild(form)
  57. form.addEventListener('submit', handleSave)
  58. return () => {
  59. form.removeEventListener('submit', handleSave)
  60. document.body.removeChild(form)
  61. }
  62. }, [form, handleSave])
  63. return (
  64. <div
  65. className={classNames('uppy-Dashboard-FileCard', className)}
  66. data-uppy-panelType="FileCard"
  67. onDragOver={ignoreEvent}
  68. onDragLeave={ignoreEvent}
  69. onDrop={ignoreEvent}
  70. onPaste={ignoreEvent}
  71. >
  72. <div className="uppy-DashboardContent-bar">
  73. <div className="uppy-DashboardContent-title" role="heading" aria-level="1">
  74. {i18nArray('editing', {
  75. file: <span className="uppy-DashboardContent-titleFile">{file.meta ? file.meta.name : file.name}</span>,
  76. })}
  77. </div>
  78. <button
  79. className="uppy-DashboardContent-back"
  80. type="button"
  81. form={form.id}
  82. title={i18n('finishEditingFile')}
  83. onClick={handleCancel}
  84. >
  85. {i18n('cancel')}
  86. </button>
  87. </div>
  88. <div className="uppy-Dashboard-FileCard-inner">
  89. <div className="uppy-Dashboard-FileCard-preview" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
  90. <FilePreview file={file} />
  91. {showEditButton
  92. && (
  93. <button
  94. type="button"
  95. className="uppy-u-reset uppy-c-btn uppy-Dashboard-FileCard-edit"
  96. onClick={(event) => {
  97. // When opening the image editor we want to save any meta fields changes.
  98. // Otherwise it's confusing for the user to click save in the editor,
  99. // but the changes here are discarded. This bypasses validation,
  100. // but we are okay with that.
  101. handleSave(event)
  102. openFileEditor(file)
  103. }}
  104. >
  105. {/* At the moment we only have one file editor - image editor.
  106. If we get editors for other file formats (e.g. pdfs),
  107. we can conditionally display i18n('editFile')/i18n('editImage'). */}
  108. {i18n('editImage')}
  109. </button>
  110. )}
  111. </div>
  112. <div className="uppy-Dashboard-FileCard-info">
  113. <RenderMetaFields
  114. computedMetaFields={computedMetaFields}
  115. requiredMetaFields={requiredMetaFields}
  116. updateMeta={updateMeta}
  117. form={form}
  118. formState={formState}
  119. />
  120. </div>
  121. <div className="uppy-Dashboard-FileCard-actions">
  122. <button
  123. className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Dashboard-FileCard-actionsBtn"
  124. // If `form` attribute is supported, we want a submit button to trigger the form validation.
  125. // Otherwise, fallback to a classic button with a onClick event handler.
  126. type="submit"
  127. form={form.id}
  128. >
  129. {i18n('saveChanges')}
  130. </button>
  131. <button
  132. className="uppy-u-reset uppy-c-btn uppy-c-btn-link uppy-Dashboard-FileCard-actionsBtn"
  133. type="button"
  134. onClick={handleCancel}
  135. form={form.id}
  136. >
  137. {i18n('cancel')}
  138. </button>
  139. </div>
  140. </div>
  141. </div>
  142. )
  143. }