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:
Dylan De Faoite
2025-07-17 23:21:01 +02:00
committed by GitHub
parent 4c49a1daf8
commit 87ad7e3537
26 changed files with 190 additions and 44 deletions

View File

@@ -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="

View File

@@ -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"}
/>

View File

@@ -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
};

View File

@@ -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,
}