|
@@ -39,9 +39,8 @@ import {
|
|
CoverVideoPublishingStateOverlay,
|
|
CoverVideoPublishingStateOverlay,
|
|
CoverWrapper,
|
|
CoverWrapper,
|
|
InfoContainer,
|
|
InfoContainer,
|
|
- KebabMenuIconContainer,
|
|
|
|
|
|
+ KebabMenuButtonIcon,
|
|
MetaContainer,
|
|
MetaContainer,
|
|
- MetaText,
|
|
|
|
ProgressBar,
|
|
ProgressBar,
|
|
ProgressOverlay,
|
|
ProgressOverlay,
|
|
PublishingStateText,
|
|
PublishingStateText,
|
|
@@ -60,11 +59,9 @@ import { Text } from '../Text'
|
|
export type VideoTileBaseMetaProps = {
|
|
export type VideoTileBaseMetaProps = {
|
|
showChannel?: boolean
|
|
showChannel?: boolean
|
|
showMeta?: boolean
|
|
showMeta?: boolean
|
|
- main?: boolean
|
|
|
|
removeButton?: boolean
|
|
removeButton?: boolean
|
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
|
onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
|
|
onChannelClick?: (e: React.MouseEvent<HTMLElement>) => void
|
|
- onCoverResize?: (width: number | undefined, height: number | undefined) => void
|
|
|
|
onRemoveButtonClick?: (e: React.MouseEvent<HTMLElement>) => void
|
|
onRemoveButtonClick?: (e: React.MouseEvent<HTMLElement>) => void
|
|
}
|
|
}
|
|
|
|
|
|
@@ -112,15 +109,10 @@ export type VideoTileBaseProps = {
|
|
} & VideoTileBaseMetaProps &
|
|
} & VideoTileBaseMetaProps &
|
|
VideoTilePublisherProps
|
|
VideoTilePublisherProps
|
|
|
|
|
|
-export const MIN_VIDEO_TILE_WIDTH = 300
|
|
|
|
-const MAX_VIDEO_PREVIEW_WIDTH = 600
|
|
|
|
-const MIN_SCALING_FACTOR = 1
|
|
|
|
-const MAX_SCALING_FACTOR = 1.375
|
|
|
|
-// Linear Interpolation, see https://en.wikipedia.org/wiki/Linear_interpolation
|
|
|
|
-const calculateScalingFactor = (videoTileWidth: number) =>
|
|
|
|
- MIN_SCALING_FACTOR +
|
|
|
|
- ((videoTileWidth - MIN_VIDEO_TILE_WIDTH) * (MAX_SCALING_FACTOR - MIN_SCALING_FACTOR)) /
|
|
|
|
- (MAX_VIDEO_PREVIEW_WIDTH - MIN_VIDEO_TILE_WIDTH)
|
|
|
|
|
|
+type TileSize = 'small' | 'big' | undefined
|
|
|
|
+
|
|
|
|
+export const MIN_VIDEO_TILE_WIDTH = 250
|
|
|
|
+const SMALL_SIZE_WIDTH = 300
|
|
|
|
|
|
export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
title,
|
|
title,
|
|
@@ -132,7 +124,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
views,
|
|
views,
|
|
thumbnailUrl,
|
|
thumbnailUrl,
|
|
hasThumbnailUploadFailed,
|
|
hasThumbnailUploadFailed,
|
|
- onCoverResize,
|
|
|
|
channelHref,
|
|
channelHref,
|
|
videoHref,
|
|
videoHref,
|
|
isLoadingThumbnail,
|
|
isLoadingThumbnail,
|
|
@@ -140,7 +131,6 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
isLoading = true,
|
|
isLoading = true,
|
|
showChannel = true,
|
|
showChannel = true,
|
|
showMeta = true,
|
|
showMeta = true,
|
|
- main = false,
|
|
|
|
removeButton = false,
|
|
removeButton = false,
|
|
videoPublishState = 'default',
|
|
videoPublishState = 'default',
|
|
publisherMode = false,
|
|
publisherMode = false,
|
|
@@ -157,20 +147,22 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
isPullupDisabled,
|
|
isPullupDisabled,
|
|
}) => {
|
|
}) => {
|
|
const { openContextMenu, contextMenuOpts } = useContextMenu()
|
|
const { openContextMenu, contextMenuOpts } = useContextMenu()
|
|
- const [scalingFactor, setScalingFactor] = useState(MIN_SCALING_FACTOR)
|
|
|
|
|
|
+ const [tileSize, setTileSize] = useState<TileSize>(undefined)
|
|
|
|
+
|
|
const { ref: imgRef } = useResizeObserver<HTMLImageElement>({
|
|
const { ref: imgRef } = useResizeObserver<HTMLImageElement>({
|
|
onResize: (size) => {
|
|
onResize: (size) => {
|
|
- const { width: videoTileWidth, height: videoTileHeight } = size
|
|
|
|
- if (onCoverResize) {
|
|
|
|
- onCoverResize(videoTileWidth, videoTileHeight)
|
|
|
|
- }
|
|
|
|
- if (videoTileWidth && !main) {
|
|
|
|
- setScalingFactor(calculateScalingFactor(videoTileWidth))
|
|
|
|
|
|
+ const { width: videoTileWidth } = size
|
|
|
|
+ if (videoTileWidth) {
|
|
|
|
+ if (tileSize !== 'small' && videoTileWidth < SMALL_SIZE_WIDTH) {
|
|
|
|
+ setTileSize('small')
|
|
|
|
+ }
|
|
|
|
+ if (tileSize !== 'big' && videoTileWidth >= SMALL_SIZE_WIDTH) {
|
|
|
|
+ setTileSize('big')
|
|
|
|
+ }
|
|
}
|
|
}
|
|
},
|
|
},
|
|
})
|
|
})
|
|
const [failedLoadImage, setFailedLoadImage] = useState(false)
|
|
const [failedLoadImage, setFailedLoadImage] = useState(false)
|
|
- const displayChannel = showChannel && !main
|
|
|
|
const clickable = (!!onClick || !!videoHref) && !isLoading
|
|
const clickable = (!!onClick || !!videoHref) && !isLoading
|
|
const channelClickable = (!!onChannelClick || !!channelHref) && !isLoading
|
|
const channelClickable = (!!onChannelClick || !!channelHref) && !isLoading
|
|
|
|
|
|
@@ -202,8 +194,8 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
}
|
|
}
|
|
|
|
|
|
return (
|
|
return (
|
|
- <Container main={main} className={className}>
|
|
|
|
- <CoverWrapper main={main}>
|
|
|
|
|
|
+ <Container className={className} isLoading={isLoading}>
|
|
|
|
+ <CoverWrapper>
|
|
<CoverContainer ref={imgRef} clickable={clickable}>
|
|
<CoverContainer ref={imgRef} clickable={clickable}>
|
|
<SwitchTransition>
|
|
<SwitchTransition>
|
|
<CSSTransition
|
|
<CSSTransition
|
|
@@ -278,15 +270,15 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
<ProgressBar style={{ width: `${progress}%` }} />
|
|
<ProgressBar style={{ width: `${progress}%` }} />
|
|
</ProgressOverlay>
|
|
</ProgressOverlay>
|
|
)}
|
|
)}
|
|
- <InfoContainer main={main}>
|
|
|
|
- {displayChannel && (
|
|
|
|
|
|
+ <InfoContainer>
|
|
|
|
+ {showChannel && (
|
|
<SwitchTransition>
|
|
<SwitchTransition>
|
|
<CSSTransition
|
|
<CSSTransition
|
|
key={isLoadingAvatar ? 'avatar-placeholder' : 'avatar'}
|
|
key={isLoadingAvatar ? 'avatar-placeholder' : 'avatar'}
|
|
timeout={parseInt(transitions.timings.sharp)}
|
|
timeout={parseInt(transitions.timings.sharp)}
|
|
classNames={transitions.names.fade}
|
|
classNames={transitions.names.fade}
|
|
>
|
|
>
|
|
- <AvatarContainer scalingFactor={scalingFactor}>
|
|
|
|
|
|
+ <AvatarContainer>
|
|
{isLoading || isLoadingAvatar ? (
|
|
{isLoading || isLoadingAvatar ? (
|
|
<SkeletonLoader rounded />
|
|
<SkeletonLoader rounded />
|
|
) : (
|
|
) : (
|
|
@@ -310,30 +302,23 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
>
|
|
>
|
|
<TextContainer>
|
|
<TextContainer>
|
|
{isLoading ? (
|
|
{isLoading ? (
|
|
- <SkeletonLoader height={main ? 45 : 18} width="60%" />
|
|
|
|
|
|
+ <SkeletonLoader height={18} width="60%" />
|
|
) : (
|
|
) : (
|
|
<TitleHeaderAnchor to={videoHref ?? ''} onClick={createAnchorClickHandler(videoHref)}>
|
|
<TitleHeaderAnchor to={videoHref ?? ''} onClick={createAnchorClickHandler(videoHref)}>
|
|
- <TitleHeader
|
|
|
|
- variant="h6"
|
|
|
|
- main={main}
|
|
|
|
- scalingFactor={scalingFactor}
|
|
|
|
- onClick={onClick}
|
|
|
|
- clickable={clickable}
|
|
|
|
- >
|
|
|
|
|
|
+ <TitleHeader variant="h6" size={tileSize} onClick={onClick} clickable={clickable}>
|
|
{title || 'Untitled'}
|
|
{title || 'Untitled'}
|
|
</TitleHeader>
|
|
</TitleHeader>
|
|
</TitleHeaderAnchor>
|
|
</TitleHeaderAnchor>
|
|
)}
|
|
)}
|
|
- {displayChannel &&
|
|
|
|
|
|
+ {showChannel &&
|
|
(isLoading ? (
|
|
(isLoading ? (
|
|
<SpacedSkeletonLoader height="12px" width="60%" />
|
|
<SpacedSkeletonLoader height="12px" width="60%" />
|
|
) : (
|
|
) : (
|
|
<Anchor to={channelHref ?? ''} onClick={createAnchorClickHandler(channelHref)}>
|
|
<Anchor to={channelHref ?? ''} onClick={createAnchorClickHandler(channelHref)}>
|
|
<ChannelHandle
|
|
<ChannelHandle
|
|
- variant="subtitle2"
|
|
|
|
|
|
+ variant="body2"
|
|
channelClickable={channelClickable}
|
|
channelClickable={channelClickable}
|
|
onClick={handleChannelClick}
|
|
onClick={handleChannelClick}
|
|
- scalingFactor={scalingFactor}
|
|
|
|
secondary
|
|
secondary
|
|
>
|
|
>
|
|
{channelTitle}
|
|
{channelTitle}
|
|
@@ -341,63 +326,61 @@ export const VideoTileBase: React.FC<VideoTileBaseProps> = ({
|
|
</Anchor>
|
|
</Anchor>
|
|
))}
|
|
))}
|
|
{showMeta && (
|
|
{showMeta && (
|
|
- <MetaContainer noMarginTop={!showChannel} main={main}>
|
|
|
|
|
|
+ <MetaContainer noMarginTop={!showChannel}>
|
|
{isLoading ? (
|
|
{isLoading ? (
|
|
- <SpacedSkeletonLoader height={main ? 16 : 12} width={main ? '40%' : '80%'} />
|
|
|
|
|
|
+ <SpacedSkeletonLoader height={12} width={'80%'} />
|
|
) : createdAt ? (
|
|
) : createdAt ? (
|
|
- <MetaText variant="subtitle2" main={main} scalingFactor={scalingFactor} secondary>
|
|
|
|
|
|
+ <Text variant="body2" secondary>
|
|
{isDraft
|
|
{isDraft
|
|
? `Last updated ${formatDateAgo(createdAt)}`
|
|
? `Last updated ${formatDateAgo(createdAt)}`
|
|
- : formatVideoViewsAndDate(views ?? null, createdAt, { fullViews: main })}
|
|
|
|
- </MetaText>
|
|
|
|
|
|
+ : formatVideoViewsAndDate(views ?? null, createdAt)}
|
|
|
|
+ </Text>
|
|
) : null}
|
|
) : null}
|
|
</MetaContainer>
|
|
</MetaContainer>
|
|
)}
|
|
)}
|
|
</TextContainer>
|
|
</TextContainer>
|
|
</CSSTransition>
|
|
</CSSTransition>
|
|
</SwitchTransition>
|
|
</SwitchTransition>
|
|
- {!isLoading && (
|
|
|
|
- <>
|
|
|
|
- <KebabMenuIconContainer
|
|
|
|
- onClick={(event) => openContextMenu(event, 200)}
|
|
|
|
- isActive={contextMenuOpts.isActive}
|
|
|
|
- >
|
|
|
|
- <SvgGlyphMore />
|
|
|
|
- </KebabMenuIconContainer>
|
|
|
|
- <ContextMenu contextMenuOpts={contextMenuOpts}>
|
|
|
|
- {publisherMode ? (
|
|
|
|
- <>
|
|
|
|
- {onOpenInTabClick && (
|
|
|
|
- <ContextMenuItem icon={<SvgGlyphPlay />} onClick={onOpenInTabClick}>
|
|
|
|
- Play in Joystream
|
|
|
|
- </ContextMenuItem>
|
|
|
|
- )}
|
|
|
|
- {onCopyVideoURLClick && (
|
|
|
|
- <ContextMenuItem icon={<SvgGlyphCopy />} onClick={onCopyVideoURLClick}>
|
|
|
|
- Copy video URL
|
|
|
|
- </ContextMenuItem>
|
|
|
|
- )}
|
|
|
|
- {onEditVideoClick && (
|
|
|
|
- <ContextMenuItem icon={<SvgGlyphEdit />} onClick={onEditVideoClick}>
|
|
|
|
- {isDraft ? 'Edit draft' : 'Edit video'}
|
|
|
|
- </ContextMenuItem>
|
|
|
|
- )}
|
|
|
|
- {onDeleteVideoClick && (
|
|
|
|
- <ContextMenuItem icon={<SvgGlyphTrash />} onClick={onDeleteVideoClick}>
|
|
|
|
- {isDraft ? 'Delete draft' : 'Delete video'}
|
|
|
|
- </ContextMenuItem>
|
|
|
|
- )}
|
|
|
|
- </>
|
|
|
|
- ) : (
|
|
|
|
- onCopyVideoURLClick && (
|
|
|
|
- <ContextMenuItem onClick={onCopyVideoURLClick} icon={<SvgGlyphCopy />}>
|
|
|
|
- Copy video URL
|
|
|
|
- </ContextMenuItem>
|
|
|
|
- )
|
|
|
|
|
|
+ <KebabMenuButtonIcon
|
|
|
|
+ onClick={(event) => openContextMenu(event, 200)}
|
|
|
|
+ variant="tertiary"
|
|
|
|
+ size="small"
|
|
|
|
+ isActive={contextMenuOpts.isActive}
|
|
|
|
+ >
|
|
|
|
+ <SvgGlyphMore />
|
|
|
|
+ </KebabMenuButtonIcon>
|
|
|
|
+ <ContextMenu contextMenuOpts={contextMenuOpts}>
|
|
|
|
+ {publisherMode ? (
|
|
|
|
+ <>
|
|
|
|
+ {onOpenInTabClick && (
|
|
|
|
+ <ContextMenuItem icon={<SvgGlyphPlay />} onClick={onOpenInTabClick}>
|
|
|
|
+ Play in Joystream
|
|
|
|
+ </ContextMenuItem>
|
|
)}
|
|
)}
|
|
- </ContextMenu>
|
|
|
|
- </>
|
|
|
|
- )}
|
|
|
|
|
|
+ {onCopyVideoURLClick && (
|
|
|
|
+ <ContextMenuItem icon={<SvgGlyphCopy />} onClick={onCopyVideoURLClick}>
|
|
|
|
+ Copy video URL
|
|
|
|
+ </ContextMenuItem>
|
|
|
|
+ )}
|
|
|
|
+ {onEditVideoClick && (
|
|
|
|
+ <ContextMenuItem icon={<SvgGlyphEdit />} onClick={onEditVideoClick}>
|
|
|
|
+ {isDraft ? 'Edit draft' : 'Edit video'}
|
|
|
|
+ </ContextMenuItem>
|
|
|
|
+ )}
|
|
|
|
+ {onDeleteVideoClick && (
|
|
|
|
+ <ContextMenuItem icon={<SvgGlyphTrash />} onClick={onDeleteVideoClick}>
|
|
|
|
+ {isDraft ? 'Delete draft' : 'Delete video'}
|
|
|
|
+ </ContextMenuItem>
|
|
|
|
+ )}
|
|
|
|
+ </>
|
|
|
|
+ ) : (
|
|
|
|
+ onCopyVideoURLClick && (
|
|
|
|
+ <ContextMenuItem onClick={onCopyVideoURLClick} icon={<SvgGlyphCopy />}>
|
|
|
|
+ Copy video URL
|
|
|
|
+ </ContextMenuItem>
|
|
|
|
+ )
|
|
|
|
+ )}
|
|
|
|
+ </ContextMenu>
|
|
</InfoContainer>
|
|
</InfoContainer>
|
|
</Container>
|
|
</Container>
|
|
)
|
|
)
|