From 59fb65d37722292349ce355c1d3104643b7ad414 Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Wed, 28 May 2025 12:17:45 +0200 Subject: [PATCH] MOVED frontend out of Vaadin/Spring --- .../components/ClipConfig.tsx | 26 +++-- .../components/ClipRangeSlider.tsx | 13 ++- .../components/PlaybackSlider.tsx | 0 .../components/Playbar.tsx | 0 frontend/components/Sidebar.tsx | 9 ++ frontend/layouts/MainLayout.tsx | 14 +++ .../{id}.tsx => frontend/pages/ClipEdit.tsx | 107 ++++++------------ .../pages/ClipUpload.tsx | 26 ++--- frontend/utils/Endpoints.ts | 96 ++++++++++++++++ package-lock.json | 2 +- pom.xml | 59 +--------- src/main/frontend/index.css | 28 ----- .../vodsystem/services/DownloadService.java | 4 - .../ddf/vodsystem/services/EditService.java | 4 - .../vodsystem/services/MetadataService.java | 4 - .../ddf/vodsystem/services/UploadService.java | 4 - src/main/resources/application.properties | 1 - .../vodsystem/VodSystemApplicationTests.java | 13 --- 18 files changed, 194 insertions(+), 216 deletions(-) rename {src/main/frontend => frontend}/components/ClipConfig.tsx (80%) rename {src/main/frontend => frontend}/components/ClipRangeSlider.tsx (81%) rename {src/main/frontend => frontend}/components/PlaybackSlider.tsx (100%) rename {src/main/frontend => frontend}/components/Playbar.tsx (100%) create mode 100644 frontend/components/Sidebar.tsx create mode 100644 frontend/layouts/MainLayout.tsx rename src/main/frontend/views/video/{id}.tsx => frontend/pages/ClipEdit.tsx (58%) rename src/main/frontend/views/@index.tsx => frontend/pages/ClipUpload.tsx (75%) create mode 100644 frontend/utils/Endpoints.ts delete mode 100644 src/main/frontend/index.css delete mode 100644 src/test/java/com/ddf/vodsystem/VodSystemApplicationTests.java diff --git a/src/main/frontend/components/ClipConfig.tsx b/frontend/components/ClipConfig.tsx similarity index 80% rename from src/main/frontend/components/ClipConfig.tsx rename to frontend/components/ClipConfig.tsx index e94b471..b13770f 100644 --- a/src/main/frontend/components/ClipConfig.tsx +++ b/frontend/components/ClipConfig.tsx @@ -1,23 +1,31 @@ +import { VideoMetadata } from "Frontend/utils/Endpoints"; + type prop = { - setWidth: Function; - setHeight: Function; - setFps: Function; - setFileSize: Function; + setMetadata: Function; } -export default function ClipConfig({setWidth, setHeight, setFps, setFileSize}: prop) { +export default function ClipConfig({setMetadata}: prop) { const updateRes = (e: React.ChangeEvent) => { var vals = e.target.value.split(","); - setWidth(parseInt(vals[0])) - setHeight(parseInt(vals[1])) + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + width: parseInt(vals[0]), + height: parseInt(vals[1]) + })) } const updateFps = (e: React.ChangeEvent) => { - setFps(parseInt(e.target.value)) + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + fps: parseInt(e.target.value) + })) } const updateFileSize = (e: React.ChangeEvent) => { - setFileSize(parseInt(e.target.value)) + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + fileSize: parseInt(e.target.value) * 1000 + })) } return ( diff --git a/src/main/frontend/components/ClipRangeSlider.tsx b/frontend/components/ClipRangeSlider.tsx similarity index 81% rename from src/main/frontend/components/ClipRangeSlider.tsx rename to frontend/components/ClipRangeSlider.tsx index ef29727..2a1e8de 100644 --- a/src/main/frontend/components/ClipRangeSlider.tsx +++ b/frontend/components/ClipRangeSlider.tsx @@ -2,20 +2,20 @@ import RangeSlider from 'react-range-slider-input'; import 'react-range-slider-input/dist/style.css'; import {useRef} from "react"; import clsx from 'clsx'; -import VideoMetadata from "Frontend/generated/com/ddf/vodsystem/entities/VideoMetadata"; +import { VideoMetadata } from "../utils/Endpoints" type Props = { videoRef: HTMLVideoElement | null; videoMetadata: VideoMetadata; setSliderValue: Function; - setClipRangeValue: Function; + setMetadata: Function; className?: string; }; export default function ClipRangeSlider({videoRef, videoMetadata, setSliderValue, - setClipRangeValue, + setMetadata, className}: Props) { const previousRangeSliderInput = useRef<[number, number]>([0, 0]); @@ -30,7 +30,12 @@ export default function ClipRangeSlider({videoRef, setSliderValue(val[1]); } - setClipRangeValue(val); + setMetadata((prevState: VideoMetadata) => ({ + ...prevState, + startPoint: val[0], + endPoint: val[1] + } + )) previousRangeSliderInput.current = val; }; diff --git a/src/main/frontend/components/PlaybackSlider.tsx b/frontend/components/PlaybackSlider.tsx similarity index 100% rename from src/main/frontend/components/PlaybackSlider.tsx rename to frontend/components/PlaybackSlider.tsx diff --git a/src/main/frontend/components/Playbar.tsx b/frontend/components/Playbar.tsx similarity index 100% rename from src/main/frontend/components/Playbar.tsx rename to frontend/components/Playbar.tsx diff --git a/frontend/components/Sidebar.tsx b/frontend/components/Sidebar.tsx new file mode 100644 index 0000000..fa62779 --- /dev/null +++ b/frontend/components/Sidebar.tsx @@ -0,0 +1,9 @@ +export default function Sidebar() { + return ( +
+
    +
  • Create Clip
  • +
+
+ ) +} \ No newline at end of file diff --git a/frontend/layouts/MainLayout.tsx b/frontend/layouts/MainLayout.tsx new file mode 100644 index 0000000..747b6d9 --- /dev/null +++ b/frontend/layouts/MainLayout.tsx @@ -0,0 +1,14 @@ +// layout/MainLayout.jsx +import Sidebar from 'Frontend/components/Sidebar'; +import { Outlet } from 'react-router-dom'; + +const MainLayout = () => ( +
+ +
+ {/* This renders the nested route content */} +
+
+); + +export default MainLayout; \ No newline at end of file diff --git a/src/main/frontend/views/video/{id}.tsx b/frontend/pages/ClipEdit.tsx similarity index 58% rename from src/main/frontend/views/video/{id}.tsx rename to frontend/pages/ClipEdit.tsx index 373217d..7b13d8a 100644 --- a/src/main/frontend/views/video/{id}.tsx +++ b/frontend/pages/ClipEdit.tsx @@ -1,90 +1,42 @@ import { useParams } from 'react-router-dom'; import { useEffect, useRef, useState } from "react"; -import Playbar from "./../../components/Playbar"; -import PlaybackSlider from "./../../components/PlaybackSlider"; -import ClipRangeSlider from "./../../components/ClipRangeSlider"; -import ClipConfig from "./../../components/ClipConfig"; -import * as editService from "../../generated/EditService"; -import * as metadataService from "../../generated/MetadataService" -import VideoMetadata from "Frontend/generated/com/ddf/vodsystem/entities/VideoMetadata"; +import Playbar from "./../components/Playbar"; +import PlaybackSlider from "./../components/PlaybackSlider"; +import ClipRangeSlider from "./../components/ClipRangeSlider"; +import ClipConfig from "./../components/ClipConfig"; +import { VideoMetadata, editFile, getMetadata } from "../utils/Endpoints" -function exportFile(uuid: string, - startPoint: number, - endPoint: number, - width: number, - height: number, - fps: number, - fileSize: number, - setProgress: Function, - setDownloadable: Function) { - - setDownloadable(false); - const metadata: VideoMetadata = { - startPoint: startPoint, - endPoint: endPoint, - width: width, - height: height, - fps: fps, - fileSize: fileSize*1000 - } - - editService.edit(uuid, metadata) - .then(r => { - editService.process(uuid); - }); - - // get progress updates - const interval = setInterval(async () => { - try { - const result = await editService.getProgress(uuid); - setProgress(result); - - if (result >= 1) { - clearInterval(interval); - setDownloadable(true); - console.log('Progress complete'); - } else { - setDownloadable(false); - } - } catch (err) { - console.error('Failed to fetch progress', err); - clearInterval(interval); - } - }, 200); // 0.5 seconds -} - -export default function VideoId() { +const ClipEdit = () => { const { id } = useParams(); const videoRef = useRef(null); const videoUrl = `api/v1/download/input/${id}` const [metadata, setMetadata] = useState(null); const [playbackValue, setPlaybackValue] = useState(0); - const [clipRangeValue, setClipRangeValue] = useState([0, 1]); - const [width, setWidth] = useState(1280); - const [height, setHeight] = useState(720); - const [fps, setFps] = useState(30); - const [fileSize, setFileSize] = useState(10); + + const [outputMetadata, setOutputMetadata] = useState({ + // default values + startPoint: 0, + endPoint: 5, + width: 1280, + height: 720, + fps: 30, + fileSize: 10000 + }); const [progress, setProgress] = useState(0); const [downloadable, setDownloadable] = useState(false); - useEffect(() => { - if (!id) return; - - metadataService.getInputFileMetadata(id) - .then((data) => setMetadata(data ?? null)) // 👈 Normalize undefined to null - .catch((err) => console.error("Metadata fetch failed:", err)); - }, [id]); - const sendData = () => { - if (!id) return - exportFile(id,clipRangeValue[0], clipRangeValue[1], width, height, fps, fileSize, setProgress, setDownloadable); + if (!id) return; + editFile(id, outputMetadata); } const handleDownload = async (filename: string | undefined) => { if (!filename) return; + const response = await fetch(`/api/v1/download/output/${id}`); + if (!response.ok) { console.error('Download failed'); return; @@ -93,6 +45,7 @@ export default function VideoId() { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); + a.href = url; a.download = filename; document.body.appendChild(a); @@ -101,6 +54,13 @@ export default function VideoId() { 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 (
@@ -115,10 +75,7 @@ export default function VideoId() { {metadata && @@ -141,7 +98,7 @@ export default function VideoId() { videoRef={videoRef.current} videoMetadata={metadata} setSliderValue={setPlaybackValue} - setClipRangeValue={setClipRangeValue} + setMetadata={setOutputMetadata} className={"w-full mb-10 bg-primary"} />
} @@ -168,4 +125,6 @@ export default function VideoId() { ); -} \ No newline at end of file +} + +export default ClipEdit; \ No newline at end of file diff --git a/src/main/frontend/views/@index.tsx b/frontend/pages/ClipUpload.tsx similarity index 75% rename from src/main/frontend/views/@index.tsx rename to frontend/pages/ClipUpload.tsx index 2b9eab2..17d2de2 100644 --- a/src/main/frontend/views/@index.tsx +++ b/frontend/pages/ClipUpload.tsx @@ -1,23 +1,21 @@ import {useState} from "react"; -import {UploadService} from "Frontend/generated/endpoints"; -import { useNavigate } from 'react-router-dom'; -import "./../index.css"; +import {useNavigate} from "react-router-dom"; +import { uploadFile } from "../utils/Endpoints" -export default function main() { +const clipUpload = () => { const [file, setFile] = useState(null); const navigate = useNavigate(); - const [error, setError] = useState(false); - - function press() { + const [noFileError, setNoFileError] = useState(false); + const press = (() => { if (file) { - UploadService.upload(file) + uploadFile(file) .then(uuid => navigate(`video/${uuid}`)) .catch(e => console.error(e)); } else { - setError(true); + setNoFileError(true); } - } + }); return (
@@ -30,14 +28,16 @@ export default function main() { className={"block w-full cursor-pointer rounded-lg border border-dashed border-gray-400 bg-white p-4 text-center hover:bg-gray-50 transition"} /> - {error && + {noFileError && }
) -} \ No newline at end of file +}; + +export default clipUpload; diff --git a/frontend/utils/Endpoints.ts b/frontend/utils/Endpoints.ts new file mode 100644 index 0000000..0fea300 --- /dev/null +++ b/frontend/utils/Endpoints.ts @@ -0,0 +1,96 @@ +type VideoMetadata = { + startPoint: number, + endPoint: number, + fps: number, + width: number, + height: number, + fileSize: number +} + +const uploadFile = async (file: File): Promise => { + const formData = new FormData(); + formData.append('file', file); + + try { + const response = await fetch('api/v1/upload', { + method: 'POST', + body: formData + }); + + if (response.ok) { + console.log("File uploaded successfully"); + return await response.text(); + } else { + console.log("File upload failed"); + return ""; + } + + } catch (error) { + console.error('Error uploading file:', error); + return ""; + } +}; + +const editFile = async (uuid: string, videoMetadata: VideoMetadata): Promise => { + const formData = new URLSearchParams(); + Object.entries(videoMetadata).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + formData.append(key, value.toString()); + } + }); + + try { + const response = await fetch(`/api/v1/edit/${uuid}`, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: formData.toString() + }); + + return response.ok; + } catch (error){ + console.error('Error editing file:', error); + return false; + } +} + +const processFile = async (uuid: string): Promise => { + const response = await fetch(`/api/v1/process/${uuid}`); + return response.ok; +} + +const getProgress = async (uuid: string): Promise => { + const response = await fetch(`/api/v1/progress/${uuid}`); + + if (response.ok) { + return response.json(); + } else { + return 0; + } +} + +const getMetadata = async (uuid: string): Promise => { + try { + const response = await fetch(`/api/v1/metadata/${uuid}`); + return response.json(); + } catch (error) { + return { + startPoint: 0, + endPoint: 0, + fps: 0, + width: 0, + height: 0, + fileSize: 0 + } + } +} + +export { + VideoMetadata, + uploadFile, + editFile, + processFile, + getProgress, + getMetadata +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 494b59b..189be9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "vodSystem", + "name": "VoD-System", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/pom.xml b/pom.xml index df253f0..e2e5696 100644 --- a/pom.xml +++ b/pom.xml @@ -27,14 +27,9 @@ - 24 - 24.7.3 + 21 - - com.vaadin - vaadin-spring-boot-starter - org.springframework.boot spring-boot-devtools @@ -48,8 +43,7 @@ org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter-web org.postgresql @@ -66,17 +60,6 @@ true - - - - com.vaadin - vaadin-bom - ${vaadin.version} - pom - import - - - @@ -108,42 +91,4 @@ - - - production - - - com.vaadin - vaadin-core - - - com.vaadin - vaadin-dev - - - - - - - - - com.vaadin - vaadin-maven-plugin - ${vaadin.version} - - - frontend - compile - - prepare-frontend - build-frontend - - - - - - - - - diff --git a/src/main/frontend/index.css b/src/main/frontend/index.css deleted file mode 100644 index cd52442..0000000 --- a/src/main/frontend/index.css +++ /dev/null @@ -1,28 +0,0 @@ -@import "tailwindcss"; - -@theme { - --font-display: "Satoshi", "sans-serif"; - - /* Breakpoints */ - --breakpoint-3xl: 1920px; - - --color-primary: oklch(0.55 0.21 254); /* Modern Blue (#2563EB) */ - --color-secondary: oklch(0.94 0.01 250); /* Light Gray (#E5E7EB) */ - --color-accent: oklch(0.68 0.20 288); /* Indigo Accent (#6366F1) */ - --color-text: oklch(0.17 0.01 270); /* Dark Slate (#111827) */ - --color-background: oklch(0.98 0.005 250);/* Soft off-white (#F9FAFB) */ - - --color-primary-pressed: oklch(0.55 0.21 254 / 0.5); - - /* Easing */ - --ease-fluid: cubic-bezier(0.3, 0, 0, 1); - --ease-snappy: cubic-bezier(0.2, 0, 0, 1); -} - -#range-slider .range-slider__range{ - background: var(--color-primary); -} - -#range-slider .range-slider__thumb{ - background: var(--color-primary); -} \ No newline at end of file diff --git a/src/main/java/com/ddf/vodsystem/services/DownloadService.java b/src/main/java/com/ddf/vodsystem/services/DownloadService.java index 008c409..8d35875 100644 --- a/src/main/java/com/ddf/vodsystem/services/DownloadService.java +++ b/src/main/java/com/ddf/vodsystem/services/DownloadService.java @@ -4,8 +4,6 @@ import com.ddf.vodsystem.entities.JobStatus; import com.ddf.vodsystem.exceptions.JobNotFinished; import com.ddf.vodsystem.exceptions.JobNotFound; import com.ddf.vodsystem.entities.Job; -import com.vaadin.flow.server.auth.AnonymousAllowed; -import com.vaadin.hilla.Endpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; @@ -14,8 +12,6 @@ import org.springframework.stereotype.Service; import java.io.File; @Service -@Endpoint -@AnonymousAllowed public class DownloadService { private final JobService jobService; diff --git a/src/main/java/com/ddf/vodsystem/services/EditService.java b/src/main/java/com/ddf/vodsystem/services/EditService.java index 3ea1c84..d702463 100644 --- a/src/main/java/com/ddf/vodsystem/services/EditService.java +++ b/src/main/java/com/ddf/vodsystem/services/EditService.java @@ -3,13 +3,9 @@ package com.ddf.vodsystem.services; import com.ddf.vodsystem.entities.VideoMetadata; import com.ddf.vodsystem.entities.Job; import com.ddf.vodsystem.entities.JobStatus; -import com.vaadin.flow.server.auth.AnonymousAllowed; -import com.vaadin.hilla.Endpoint; import org.springframework.stereotype.Service; @Service -@Endpoint -@AnonymousAllowed public class EditService { private final JobService jobService; diff --git a/src/main/java/com/ddf/vodsystem/services/MetadataService.java b/src/main/java/com/ddf/vodsystem/services/MetadataService.java index 6431899..9006c15 100644 --- a/src/main/java/com/ddf/vodsystem/services/MetadataService.java +++ b/src/main/java/com/ddf/vodsystem/services/MetadataService.java @@ -5,8 +5,6 @@ import com.ddf.vodsystem.entities.VideoMetadata; import com.ddf.vodsystem.exceptions.FFMPEGException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.vaadin.flow.server.auth.AnonymousAllowed; -import com.vaadin.hilla.Endpoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -17,8 +15,6 @@ import java.io.IOException; import java.io.InputStreamReader; @Service -@Endpoint -@AnonymousAllowed public class MetadataService { private static Logger logger = LoggerFactory.getLogger(MetadataService.class); diff --git a/src/main/java/com/ddf/vodsystem/services/UploadService.java b/src/main/java/com/ddf/vodsystem/services/UploadService.java index e19291c..18c81e7 100644 --- a/src/main/java/com/ddf/vodsystem/services/UploadService.java +++ b/src/main/java/com/ddf/vodsystem/services/UploadService.java @@ -2,8 +2,6 @@ package com.ddf.vodsystem.services; import com.ddf.vodsystem.entities.Job; import com.ddf.vodsystem.entities.VideoMetadata; -import com.vaadin.flow.server.auth.AnonymousAllowed; -import com.vaadin.hilla.Endpoint; import jakarta.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -24,8 +22,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Service -@Endpoint -@AnonymousAllowed public class UploadService { private static final Logger logger = LoggerFactory.getLogger(UploadService.class); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b51ccc4..c798028 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,3 @@ -vaadin.launch-browser=true spring.application.name=vodSystem # VODs diff --git a/src/test/java/com/ddf/vodsystem/VodSystemApplicationTests.java b/src/test/java/com/ddf/vodsystem/VodSystemApplicationTests.java deleted file mode 100644 index 9a9c1ba..0000000 --- a/src/test/java/com/ddf/vodsystem/VodSystemApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ddf.vodsystem; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class VodSystemApplicationTests { - - @Test - void contextLoads() { - } - -}