"use client"; import React, { useState, useEffect, useRef, useCallback } from "react"; import EmbedTimeline from "./components/EmbedTimeline"; import VideoPlayer from "./components/VideoPlayer"; // import ModernDateRangeSelector from './components/ModernDateRangeSelector'; import CompactDateRangePicker from "./components/CompactDateRangePicker"; import CustomDateRangePicker from "./components/DateRangePicker"; import "./App.css"; import StatusesDisplayHUD from "./components/StatusDisplay"; function App() { const original_data = useRef(null); const chartRef = useRef(null); const [dataResults, setDataResults] = useState(null); const [statusMessages, setStatusMessages] = useState([]); const [markerTime, setMarkerTime] = useState(0); const playerRef = useRef(null); const playerInstanceRef = useRef(null); // State for the values window.chartRef = chartRef; window.playerRef = playerRef; window.playerInstanceRef = playerInstanceRef; // Slider states const [sliderMin, setSliderMin] = useState(0.0); const [sliderMax, setSliderMax] = useState(1.0); // Date range states // const [startRange, setStartRange] = useState( new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000) ); const [endRange, setEndRange] = useState(new Date()); // const [endRange, setEndRange] = useState(new Date(new Date().getTime() - 6 * 24 * 60 * 60 * 1000)); const [queryText, setQueryText] = useState("A clouded leopard and a human"); const [sliderValue, setSliderValue] = useState(0); // State to track last submitted values const [lastSubmitted, setLastSubmitted] = useState({ startRange, endRange, sliderValue, queryText, }); // Check if any value has changed const hasChanged = startRange !== lastSubmitted.startRange || endRange !== lastSubmitted.endRange || sliderValue !== lastSubmitted.sliderValue || queryText !== lastSubmitted.queryText; const streamComputeStatus = () => { fetch("api/return_status") .then((response) => { const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; // Accumulate partial text function read() { reader.read().then(({ done, value }) => { if (done) { if (buffer) { } setStatusMessages([]); return; } // Decode only the new chunk buffer += decoder.decode(value, { stream: true }); // If your server sends lines, split and log only complete lines: let lines = buffer.split("\n"); buffer = lines.pop(); // Save incomplete line for next chunk for (const line of lines) { if (line.trim()) { let c_line = JSON.parse(line); if (c_line["task"] !== "DONE_QUIT") { console.log(c_line); setStatusMessages((msgs) => [ ...msgs, c_line, ]); } } } read(); }); } read(); }) .catch((error) => { console.error("Error while streaming status:", error); }); }; // Function to resubmit fetch const handleResubmit = (doTestMode = false) => { console.log("startRange, endRange:", startRange, endRange); console.log("test mode:", doTestMode); let startRangeUse; let endRangeUse; if (doTestMode == true) { startRangeUse = new Date( new Date().getTime() - 2 * 24 * 60 * 60 * 1000 ); endRangeUse = new Date( new Date().getTime() - 1 * 24 * 60 * 60 * 1000 ); } else { startRangeUse = startRange; endRangeUse = endRange; } console.log("Using date range:", startRangeUse, endRangeUse); setStartRange(startRangeUse); setEndRange(endRangeUse); const params = new URLSearchParams(); params.append("startRange", startRangeUse.toISOString()); params.append("endRange", endRangeUse.toISOString()); params.append("threshold", 0.0); params.append("query", queryText); setDataResults({ videos: [], breaks: [] }); fetch("api/videos.json?" + params.toString()) .then((res) => res.json()) .then((data) => { streamComputeStatus(); // Don't setDataResults here, since it's just {"status": ...} pollForResult(); // Start polling for the real result }); setLastSubmitted({ startRangeUse, endRangeUse, sliderValue, queryText, }); }; function updateDataAndValue(newValue) { const floatValue = parseFloat(newValue); setSliderValue(floatValue); var newData = JSON.parse(JSON.stringify(original_data.current)); newData["videos"] = newData["videos"].filter( (vid) => vid["embed_scores"]["score"][1] >= floatValue ); setDataResults(newData); } function setMarkerValueNonReactive(inputValue) { let chart = chartRef.current.getEchartsInstance(); let options = chart.getOption(); let mappers = options["mappers"]; let vv = { xAxis: mappers["real_to_virtual"](new Date(inputValue)), lineStyle: { type: "solid", color: "#FF0000", width: 2 }, label: { show: false, formatter: "Break", position: "bottom", color: "#888", fontSize: 10, }, }; let markLine = { symbol: ["none", "none"], data: [vv], lineStyle: { type: "dashed", color: "#FF0000", width: 2 }, silent: true, animation: false, }; // if ("markLine" in options["series"][1]) { if (false) { let vv_new = { xAxis: mappers["real_to_virtual"](new Date(inputValue)), }; let markLine_new = { data: [vv_new], }; chart.setOption( { series: [{}, { markLine: { data: [vv_new] } }], }, false, ["series.markLine"] ); } else { chart.setOption( { series: [{}, { markLine: markLine }], }, false, ["series.markLine"] ); } } // Memoize the timeline click handler const handleTimelineClick = useCallback( (path, timeoffset) => { if (playerRef.current && playerInstanceRef.current) { playerInstanceRef.current.src({ src: "api/" + path, type: "video/mp4", }); playerInstanceRef.current.on("loadedmetadata", () => { playerInstanceRef.current.currentTime(timeoffset); }); } }, [] // Empty dependency array since it only uses playerRef ); useEffect(() => { const params = new URLSearchParams(window.location.search); handleResubmit(params.get("test_mode") === "true"); }, []); // useEffect(() => { // if (startRange && endRange) { // handleResubmit(); // } // }, [startRange, endRange]); function pollForResult() { const poll = () => { fetch("api/videos_result.json") .then((res) => res.json()) .then((data) => { if (data.status === "processing") { setTimeout(poll, 250); // Try again in 1 second } else { const max_value = Math.max( ...data["videos"].map( (vid) => vid["embed_scores"]["score"][1] ) ); setSliderMax(max_value); original_data.current = data; setDataResults(data); } }); }; poll(); } return (
setQueryText(e.target.value)} style={{ marginLeft: "16px", marginRight: "16px", padding: "8px", borderRadius: "4px", border: "1px solid #343a40", color: "#fff", // Text white backgroundColor: "#23272f", // Optional: dark background for contrast }} />
updateDataAndValue(e.target.value)} style={{ width: "120px", color: "#fff", // Text white backgroundColor: "#23272f", // Optional: dark background for contrast }} />
{sliderValue.toFixed(2)}
); } export default App;