ADD thumbnail to database (#12)
* ADD thumbnail to database * ADD thumbnail generation and retrieval functionality * ADD thumbnail availability check in VideoCard component * ADD ClipDTO to reduce exposed internals * REFactor move APIResponse and VideoMetadata to dto package * REMOVE unused props from VideoCard * ADD isThumbnailAvailable function
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
import clsx from "clsx";
|
||||
import { formatTime, stringToDate, dateToTimeAgo } from "../../utils/utils.ts";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import { isThumbnailAvailable } from "../../utils/endpoints.ts";
|
||||
|
||||
type VideoCardProps = {
|
||||
id: number,
|
||||
title: string,
|
||||
duration: number,
|
||||
thumbnailPath: string | null,
|
||||
videoPath: string,
|
||||
createdAt: string,
|
||||
className?: string
|
||||
}
|
||||
@@ -19,31 +18,35 @@ const VideoCard = ({
|
||||
id,
|
||||
title,
|
||||
duration,
|
||||
thumbnailPath,
|
||||
createdAt,
|
||||
className
|
||||
}: VideoCardProps) => {
|
||||
|
||||
const initialSrc = thumbnailPath && thumbnailPath.trim() !== "" ? thumbnailPath : fallbackThumbnail;
|
||||
const [imgSrc, setImgSrc] = useState(initialSrc);
|
||||
const [timeAgo, setTimeAgo] = useState(dateToTimeAgo(stringToDate(createdAt)));
|
||||
const [thumbnailAvailable, setThumbnailAvailable] = useState(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeAgo(dateToTimeAgo(stringToDate(createdAt)))
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
isThumbnailAvailable(id)
|
||||
.then((available) => {
|
||||
setThumbnailAvailable(available);
|
||||
})
|
||||
.catch(() => {
|
||||
setThumbnailAvailable(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<Link to={"/video/" + id}>
|
||||
<div className={clsx("flex flex-col", className)}>
|
||||
<div className={"relative inline-block"}>
|
||||
<img
|
||||
src={imgSrc}
|
||||
src={thumbnailAvailable ? `/api/v1/download/thumbnail/${id}` : fallbackThumbnail}
|
||||
alt="Video Thumbnail"
|
||||
onError={() => {
|
||||
if (imgSrc !== fallbackThumbnail) {
|
||||
setImgSrc(fallbackThumbnail);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<p className="
|
||||
|
||||
@@ -18,11 +18,8 @@ const MyClips = () => {
|
||||
{clips.map((clip) => (
|
||||
<VideoCard
|
||||
id={clip.id}
|
||||
key={clip.videoPath}
|
||||
title={clip.title}
|
||||
duration={clip.duration}
|
||||
thumbnailPath={clip.thumbnailPath}
|
||||
videoPath={clip.videoPath}
|
||||
createdAt={clip.createdAt}
|
||||
className={"w-40 m-5"}
|
||||
/>
|
||||
|
||||
@@ -176,6 +176,15 @@ const getClipById = async (id: string): Promise<Clip | null> => {
|
||||
}
|
||||
};
|
||||
|
||||
const isThumbnailAvailable = async (id: number): Promise<boolean> => {
|
||||
const response = await fetch(`/api/v1/download/thumbnail/${id}`);
|
||||
if (!response.ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export {
|
||||
uploadFile,
|
||||
editFile,
|
||||
@@ -184,5 +193,6 @@ export {
|
||||
getMetadata,
|
||||
getUser,
|
||||
getClips,
|
||||
getClipById
|
||||
getClipById,
|
||||
isThumbnailAvailable
|
||||
};
|
||||
@@ -23,14 +23,10 @@ type User = {
|
||||
|
||||
type Clip = {
|
||||
id: number,
|
||||
userId: number,
|
||||
title: string,
|
||||
description: string,
|
||||
duration: number,
|
||||
thumbnailPath: string,
|
||||
videoPath: string,
|
||||
fps: number,
|
||||
width: number,
|
||||
height: number,
|
||||
createdAt: string,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user