BucketFilePickerRow.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { FilesBucket as FilesBucketIcon } from 'icons'
  2. import { AlertCircle, File, Film, FolderOpen, Image, LoaderCircle, Music } from 'lucide-react'
  3. import type { CSSProperties, MouseEvent } from 'react'
  4. import { Checkbox, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  5. import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES, STORAGE_VIEWS } from '../Storage.constants'
  6. import { type StorageItem } from '../Storage.types'
  7. import { formatBytes } from '@/lib/helpers'
  8. const RowIcon = ({
  9. view,
  10. status,
  11. fileType,
  12. isOpened = false,
  13. mimeType,
  14. }: {
  15. view: STORAGE_VIEWS
  16. status: STORAGE_ROW_STATUS
  17. fileType: string
  18. isOpened?: boolean
  19. mimeType: string | undefined
  20. }) => {
  21. if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) {
  22. return (
  23. <LoaderCircle size={14} strokeWidth={2} className="animate-spin text-foreground-lighter" />
  24. )
  25. }
  26. if (fileType === STORAGE_ROW_TYPES.FOLDER) {
  27. return isOpened ? (
  28. <FolderOpen size={16} strokeWidth={2} className="text-foreground-lighter" />
  29. ) : (
  30. <FilesBucketIcon size={16} strokeWidth={2} className="text-foreground-lighter" />
  31. )
  32. }
  33. if (mimeType?.includes('image')) {
  34. return <Image size={16} className="text-foreground-lighter" />
  35. }
  36. if (mimeType?.includes('audio')) {
  37. return <Music size={16} strokeWidth={2} className="text-foreground-lighter" />
  38. }
  39. if (mimeType?.includes('video')) {
  40. return <Film size={16} strokeWidth={2} className="text-foreground-lighter" />
  41. }
  42. return <File size={16} strokeWidth={2} className="text-foreground-lighter" />
  43. }
  44. interface BucketFilePickerRowProps {
  45. item: StorageItem
  46. view: STORAGE_VIEWS
  47. isSelected: boolean
  48. isPreviewed: boolean
  49. isOpened: boolean
  50. isDisabled?: boolean
  51. hideCheckbox: boolean
  52. onCheck: (isShiftKeyHeld: boolean) => void
  53. onClick?: (event: MouseEvent<HTMLDivElement>) => void
  54. style?: CSSProperties
  55. }
  56. export const BucketFilePickerRow = ({
  57. item,
  58. view = STORAGE_VIEWS.COLUMNS,
  59. onCheck,
  60. onClick,
  61. isSelected,
  62. isPreviewed,
  63. isOpened,
  64. isDisabled = false,
  65. hideCheckbox,
  66. style,
  67. }: BucketFilePickerRowProps) => {
  68. const size = item.metadata ? formatBytes(item.metadata.size) : '-'
  69. const mimeType = item.metadata ? item.metadata.mimetype : '-'
  70. const createdAt = item.created_at ? new Date(item.created_at).toLocaleString() : '-'
  71. const updatedAt = item.updated_at ? new Date(item.updated_at).toLocaleString() : '-'
  72. const nameWidth =
  73. view === STORAGE_VIEWS.LIST && item.isCorrupted
  74. ? `calc(100% - 60px)`
  75. : view === STORAGE_VIEWS.LIST && !item.isCorrupted
  76. ? `calc(100% - 50px)`
  77. : '100%'
  78. return (
  79. <div style={style} className="h-full border-b border-default">
  80. <div
  81. className={cn(
  82. 'storage-row group flex h-full items-center px-2.5',
  83. 'hover:bg-panel-footer-light in-data-[theme*=dark]:hover:bg-panel-footer-dark',
  84. `${isOpened ? 'bg-selection' : ''}`,
  85. `${isSelected ? 'bg-selection' : ''}`,
  86. `${isPreviewed ? 'bg-selection hover:bg-selection' : ''}`,
  87. `${item.status !== STORAGE_ROW_STATUS.LOADING ? 'cursor-pointer' : ''}`,
  88. isDisabled && 'cursor-not-allowed opacity-40 hover:bg-transparent'
  89. )}
  90. onClick={isDisabled ? undefined : onClick}
  91. >
  92. <div
  93. className={cn(
  94. 'flex items-center',
  95. view === STORAGE_VIEWS.LIST ? 'w-[40%] min-w-[250px]' : 'w-[90%]'
  96. )}
  97. >
  98. <div className="relative w-[30px]" onClick={(event) => event.stopPropagation()}>
  99. <div
  100. className={cn('top-0.5', {
  101. absolute: !hideCheckbox,
  102. 'group-hover:hidden': !hideCheckbox && item.type === STORAGE_ROW_TYPES.FILE,
  103. hidden: isSelected,
  104. })}
  105. >
  106. <RowIcon
  107. view={view}
  108. status={item.status}
  109. fileType={item.type}
  110. isOpened={isOpened}
  111. mimeType={item.metadata?.mimetype}
  112. />
  113. </div>
  114. {!hideCheckbox && (
  115. <Checkbox
  116. className={cn(
  117. 'w-full',
  118. { invisible: item.type !== STORAGE_ROW_TYPES.FILE },
  119. isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
  120. )}
  121. checked={isSelected}
  122. onChange={(event) => {
  123. event.stopPropagation()
  124. onCheck((event.nativeEvent as KeyboardEvent).shiftKey)
  125. }}
  126. />
  127. )}
  128. </div>
  129. <p title={item.name} className="truncate text-sm" style={{ width: nameWidth }}>
  130. {item.name}
  131. </p>
  132. {item.isCorrupted && (
  133. <Tooltip>
  134. <TooltipTrigger>
  135. <AlertCircle size={18} strokeWidth={2} className="text-foreground-light" />
  136. </TooltipTrigger>
  137. <TooltipContent side="bottom">
  138. File is corrupted, please delete and reupload again.
  139. </TooltipContent>
  140. </Tooltip>
  141. )}
  142. </div>
  143. {view === STORAGE_VIEWS.LIST && (
  144. <>
  145. <p className="w-[11%] min-w-[100px] truncate text-sm">{size}</p>
  146. <p className="w-[14%] min-w-[100px] truncate text-sm">{mimeType}</p>
  147. <p className="w-[15%] min-w-[160px] truncate text-sm">{createdAt}</p>
  148. <p className="w-[15%] min-w-[160px] truncate text-sm">{updatedAt}</p>
  149. </>
  150. )}
  151. <div
  152. className={`flex items-center justify-end ${
  153. view === STORAGE_VIEWS.LIST ? 'grow' : 'w-[10%]'
  154. }`}
  155. onClick={(event) =>
  156. // Stops click event from this div, to resolve an issue with menu item's click event triggering unexpected row select
  157. event.stopPropagation()
  158. }
  159. >
  160. {item.status === STORAGE_ROW_STATUS.LOADING && (
  161. <LoaderCircle
  162. className={`animate-spin text-foreground-lighter ${view === STORAGE_VIEWS.LIST ? 'invisible' : ''}`}
  163. size={14}
  164. strokeWidth={2}
  165. />
  166. )}
  167. </div>
  168. </div>
  169. </div>
  170. )
  171. }