VideoTile.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import React from 'react'
  2. import { useVideo } from '@/api/hooks'
  3. import { AssetAvailability } from '@/api/queries'
  4. import { absoluteRoutes } from '@/config/routes'
  5. import { AssetType, singleDraftSelector, useAsset, useDraftStore } from '@/providers'
  6. import {
  7. VideoTileBase,
  8. VideoTileBaseMetaProps,
  9. VideoTileBaseProps,
  10. VideoTilePublisherProps,
  11. } from '@/shared/components/VideoTileBase/VideoTileBase'
  12. import { copyToClipboard, openInNewTab } from '@/utils/browser'
  13. import { SentryLogger } from '@/utils/logs'
  14. export type VideoTileProps = {
  15. id?: string
  16. onNotFound?: () => void
  17. } & VideoTileBaseMetaProps &
  18. Pick<VideoTileBaseProps, 'progress' | 'className'>
  19. export const VideoTile: React.FC<VideoTileProps> = ({ id, onNotFound, ...metaProps }) => {
  20. const { video, loading, videoHref } = useVideoSharedLogic({ id, isDraft: false, onNotFound })
  21. const { url: thumbnailPhotoUrl } = useAsset({
  22. entity: video,
  23. assetType: AssetType.THUMBNAIL,
  24. })
  25. const { url: avatarPhotoUrl } = useAsset({
  26. entity: video?.channel,
  27. assetType: AssetType.AVATAR,
  28. })
  29. return (
  30. <VideoTileBase
  31. publisherMode={false}
  32. title={video?.title}
  33. channelTitle={video?.channel.title}
  34. channelAvatarUrl={avatarPhotoUrl}
  35. createdAt={video?.createdAt}
  36. duration={video?.duration}
  37. views={video?.views}
  38. videoHref={videoHref}
  39. channelHref={id ? absoluteRoutes.viewer.channel(video?.channel.id) : undefined}
  40. onCopyVideoURLClick={() => copyToClipboard(videoHref ? location.origin + videoHref : '')}
  41. thumbnailUrl={thumbnailPhotoUrl}
  42. isLoading={loading}
  43. contentKey={id}
  44. {...metaProps}
  45. />
  46. )
  47. }
  48. export type VideoTileWPublisherProps = VideoTileProps &
  49. Omit<VideoTilePublisherProps, 'publisherMode' | 'videoPublishState'>
  50. export const VideoTilePublisher: React.FC<VideoTileWPublisherProps> = ({ id, isDraft, onNotFound, ...metaProps }) => {
  51. const { video, loading, videoHref } = useVideoSharedLogic({ id, isDraft, onNotFound })
  52. const draft = useDraftStore(singleDraftSelector(id ?? ''))
  53. const { url: thumbnailPhotoUrl } = useAsset({
  54. entity: video,
  55. assetType: AssetType.THUMBNAIL,
  56. })
  57. const { url: avatarPhotoUrl } = useAsset({
  58. entity: video?.channel,
  59. assetType: AssetType.AVATAR,
  60. })
  61. const hasThumbnailUploadFailed = video?.thumbnailPhotoAvailability === AssetAvailability.Pending
  62. return (
  63. <VideoTileBase
  64. publisherMode
  65. title={isDraft ? draft?.title : video?.title}
  66. channelTitle={video?.channel.title}
  67. channelAvatarUrl={avatarPhotoUrl}
  68. createdAt={isDraft ? new Date(draft?.updatedAt ?? '') : video?.createdAt}
  69. duration={video?.duration}
  70. views={video?.views}
  71. thumbnailUrl={thumbnailPhotoUrl}
  72. hasThumbnailUploadFailed={hasThumbnailUploadFailed}
  73. channelHref={id ? absoluteRoutes.viewer.channel(video?.channel.id) : undefined}
  74. isLoading={loading}
  75. onOpenInTabClick={isDraft || !id ? undefined : () => openInNewTab(absoluteRoutes.viewer.video(id), true)}
  76. onCopyVideoURLClick={isDraft ? undefined : () => copyToClipboard(videoHref ? location.origin + videoHref : '')}
  77. videoPublishState={video?.isPublic || video?.isPublic === undefined ? 'default' : 'unlisted'}
  78. isDraft={isDraft}
  79. contentKey={id}
  80. {...metaProps}
  81. />
  82. )
  83. }
  84. type UseVideoSharedLogicOpts = {
  85. id?: string
  86. isDraft?: boolean
  87. onNotFound?: () => void
  88. }
  89. const useVideoSharedLogic = ({ id, isDraft, onNotFound }: UseVideoSharedLogicOpts) => {
  90. const { video, loading } = useVideo(id ?? '', {
  91. skip: !id || isDraft,
  92. onCompleted: (data) => !data && onNotFound?.(),
  93. onError: (error) => SentryLogger.error('Failed to fetch video', 'VideoTile', error, { video: { id } }),
  94. })
  95. const internalIsLoadingState = loading || !id
  96. const videoHref = id ? absoluteRoutes.viewer.video(id) : undefined
  97. return { video, loading: internalIsLoadingState, videoHref }
  98. }