From 9185a41147a69e338669f83084cdcb8a6611d0f7 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Sat, 20 Jun 2026 22:04:42 +0100 Subject: [PATCH] feat(frontend): remove server-side upload process and switch to browser process --- frontend/src/pages/ClipEdit.tsx | 104 +++++++++++++++++------------- frontend/src/pages/ClipUpload.tsx | 58 +---------------- 2 files changed, 63 insertions(+), 99 deletions(-) diff --git a/frontend/src/pages/ClipEdit.tsx b/frontend/src/pages/ClipEdit.tsx index 64d6c4d..21bd7c8 100644 --- a/frontend/src/pages/ClipEdit.tsx +++ b/frontend/src/pages/ClipEdit.tsx @@ -1,11 +1,11 @@ -import { useParams } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { useEffect, useRef, useState } from "react"; import Playbar from "./../components/video/Playbar"; import PlaybackSlider from "./../components/video/PlaybackSlider"; import ClipRangeSlider from "./../components/video/ClipRangeSlider"; import ConfigBox from "../components/video/ConfigBox.tsx"; import ExportWidget from "../components/video/ExportWidget.tsx"; -import {editFile, getMetadata, processFile, getProgress} from "../utils/endpoints" +import { editFile, processFile, getProgress, uploadFile } from "../utils/endpoints" import type { VideoMetadata } from "../utils/types.ts"; import Box from "../components/Box.tsx"; import MetadataBox from "../components/video/MetadataBox.tsx"; @@ -13,13 +13,16 @@ import MetadataBox from "../components/video/MetadataBox.tsx"; const API_URL = import.meta.env.VITE_API_URL; const ClipEdit = () => { - const { id } = useParams(); + const location = useLocation(); + const localFile: File = location.state?.file; + const videoRef = useRef(null); - const videoUrl = API_URL + `/api/v1/download/input/${id}` + const [videoUrl, setVideoUrl] = useState(''); + const [uploadedId, setUploadedId] = useState(null); + const [metadata, setMetadata] = useState(null); const [playbackValue, setPlaybackValue] = useState(0); const [outputMetadata, setOutputMetadata] = useState({ - // default values title: "", description: "", startPoint: 0, @@ -33,20 +36,47 @@ const ClipEdit = () => { const [downloadable, setDownloadable] = useState(false); const [error, setError] = useState(null); - const sendData = async() => { - if (!id) return; + useEffect(() => { + if (!localFile) return; + const url = URL.createObjectURL(localFile); + setVideoUrl(url); + return () => URL.revokeObjectURL(url); + }, [localFile]); - editFile(id, outputMetadata) - .then(() => { + const handleVideoMetadataLoaded = () => { + const video = videoRef.current; + if (!video) return; + setMetadata({ + title: '', + description: '', + startPoint: 0, + duration: video.duration, + width: video.videoWidth, + height: video.videoHeight, + fps: 30, + fileSize: localFile?.size ?? 0, + }); + }; - processFile(id) - .catch((err: Error) => setError(`Failed to process file: ${err.message}`)); + const sendData = async () => { + if (!localFile) return; - }) + let fileId: string; + try { + fileId = await uploadFile(localFile); + setUploadedId(fileId); + } catch (err: unknown) { + setError(`Failed to upload file: ${err instanceof Error ? err.message : 'Unknown error'}`); + return; + } + + editFile(fileId, outputMetadata) + .then(() => processFile(fileId) + .catch((err: Error) => setError(`Failed to process file: ${err.message}`))) .catch((err: Error) => setError(`Failed to edit file: ${err.message}`)); - const interval = setInterval(async() => await pollProgress(id, interval), 500); - } + const interval = setInterval(async () => await pollProgress(fileId, interval), 500); + }; const pollProgress = async (id: string, intervalId: number) => { getProgress(id) @@ -57,17 +87,19 @@ const ClipEdit = () => { clearInterval(intervalId); setDownloadable(true); } else { - setDownloadable(false) + setDownloadable(false); } - }) + }) .catch((err: Error) => { setError(`Failed to fetch progress: ${err.message}`); clearInterval(intervalId); }); - } + }; const handleDownload = async () => { - const response = await fetch(API_URL + `/api/v1/download/output/${id}`); + if (!uploadedId) return; + + const response = await fetch(API_URL + `/api/v1/download/output/${uploadedId}`); if (!response.ok) { console.error('Download failed'); @@ -79,43 +111,27 @@ const ClipEdit = () => { const a = document.createElement('a'); a.href = url; - a.download = `${id}.mp4`; + a.download = `${uploadedId}.mp4`; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); }; - useEffect(() => { - if (!id) return; - - getMetadata(id) - .then((data) => setMetadata(data ?? null)) - .catch((err) => console.error("Metadata fetch failed:", err)); - }, [id]); - return (
- + ref={videoRef} + src={videoUrl} + className={"w-full rounded-lg shadow-sm bg-black"} + onLoadedMetadata={handleVideoMetadataLoaded} + /> - - + + - {metadata && {
); -} +}; -export default ClipEdit; \ No newline at end of file +export default ClipEdit; diff --git a/frontend/src/pages/ClipUpload.tsx b/frontend/src/pages/ClipUpload.tsx index c373e89..cf4cbb4 100644 --- a/frontend/src/pages/ClipUpload.tsx +++ b/frontend/src/pages/ClipUpload.tsx @@ -1,6 +1,5 @@ import {useState} from "react"; import {useNavigate} from "react-router-dom"; -import {uploadFile, convertFile, getProgress} from "../utils/endpoints" import BlueButton from "../components/buttons/BlueButton.tsx"; import Box from "../components/Box.tsx"; @@ -8,63 +7,14 @@ const ClipUpload = () => { const [file, setFile] = useState(null); const navigate = useNavigate(); const [error, setError] = useState(null); - const [progress, setProgress] = useState(0); - const isVideoFileSupported = (file: File): boolean => { - const video = document.createElement("video"); - - if (file.type && video.canPlayType(file.type) !== "") { - return true; - } - - const extension = file.name.split(".").pop()?.toLowerCase(); - const extensionToMime: Record = { - mp4: "video/mp4", - webm: "video/webm", - ogg: "video/ogg", - mov: "video/quicktime", - }; - - if (extension && extensionToMime[extension]) { - return video.canPlayType(extensionToMime[extension]) !== ""; - } - - return false; - }; - - const press = (() => { + const press = () => { if (!file) { setError("Please choose a file"); return; } - - uploadFile(file) - .then(uuid => { - if (isVideoFileSupported(file)) { - navigate(`/create/${uuid}`) - } else { - convertFile(uuid); - const interval = setInterval(async() => await pollProgress(uuid, interval), 500); - } - }) - .catch((e: Error) => setError(`Failed to upload file: ${e.message}`)); - }); - - const pollProgress = async (id: string, intervalId: number) => { - getProgress(id) - .then((progress) => { - setProgress(progress.conversion.progress); - - if (progress.conversion.complete) { - clearInterval(intervalId); - navigate(`/create/${id}`) - } - }) - .catch((err: Error) => { - setError(`Failed to fetch progress: ${err.message}`); - clearInterval(intervalId); - }); - } + navigate('/create/new', { state: { file } }); + }; return (
@@ -84,8 +34,6 @@ const ClipUpload = () => { Upload {error &&

{error}

} - -
)