common code
This commit is contained in:
@@ -1,14 +1,289 @@
|
||||
import React from 'react';
|
||||
import EmbedTimeline from './components/EmbedTimeline';
|
||||
import './App.css';
|
||||
import data_results from "./util/embed_results_web.json"
|
||||
"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;
|
||||
|
||||
// Function to resubmit fetch
|
||||
const handleResubmit = () => {
|
||||
// Start streaming status updates
|
||||
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) {
|
||||
// console.log("Status:", buffer); // Log any remaining text
|
||||
}
|
||||
setStatusMessages([]);
|
||||
// console.log("Status stream finished");
|
||||
|
||||
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()) {
|
||||
// console.log("Status:", line);
|
||||
console.log(line)
|
||||
setStatusMessages((msgs) => [...msgs, JSON.parse(line)]);
|
||||
}
|
||||
}
|
||||
|
||||
read();
|
||||
});
|
||||
}
|
||||
read();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error while streaming status:", error);
|
||||
});
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append("startRange", startRange.toISOString());
|
||||
params.append("endRange", endRange.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) => {
|
||||
const max_value = Math.max(
|
||||
...data["videos"].map((vid) => vid["embed_scores"]["score"][1])
|
||||
);
|
||||
setSliderMax(max_value);
|
||||
original_data.current = data;
|
||||
window.original_data = original_data;
|
||||
setDataResults(data);
|
||||
});
|
||||
|
||||
setLastSubmitted({ startRange, endRange, 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) => {
|
||||
console.log("Timeline clicked:", path, timeoffset);
|
||||
|
||||
if (playerRef.current && playerInstanceRef.current) {
|
||||
console.log("Seeking video player to:", path, timeoffset);
|
||||
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); // id=123
|
||||
|
||||
if (params.get("test_mode") == "true") {
|
||||
setStartRange(new Date(new Date().getTime() - 2 * 24 * 60 * 60 * 1000));
|
||||
setEndRange(new Date(new Date().getTime() - 1 * 24 * 60 * 60 * 1000));
|
||||
}
|
||||
handleResubmit();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Embed Timeline Visualization</h1>
|
||||
<EmbedTimeline data_in={data_results}/>
|
||||
<div className="app-container">
|
||||
<div className="section-box-horiz">
|
||||
<div className="flex-group">
|
||||
<CustomDateRangePicker
|
||||
startDate={startRange}
|
||||
endDate={endRange}
|
||||
setStartRange={setStartRange}
|
||||
setEndRange={setEndRange}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-group">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter query"
|
||||
value={queryText}
|
||||
onChange={(e) => 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
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-group">
|
||||
<label
|
||||
style={{ marginLeft: "8px", marginRight: "8px", color: "#fff" }}
|
||||
>
|
||||
Threshold:
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex-group">
|
||||
<input
|
||||
type="range"
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={0.001}
|
||||
value={sliderValue}
|
||||
onChange={(e) => updateDataAndValue(e.target.value)}
|
||||
style={{
|
||||
width: "120px",
|
||||
color: "#fff", // Text white
|
||||
backgroundColor: "#23272f", // Optional: dark background for contrast
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-group">
|
||||
<span style={{ marginLeft: "8px", color: "#fff" }}>
|
||||
{sliderValue.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-group">
|
||||
<button onClick={handleResubmit}>Resubmit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<StatusesDisplayHUD statusMessages={statusMessages} />
|
||||
</div>
|
||||
|
||||
<div className="timeline-container">
|
||||
<EmbedTimeline
|
||||
chartRef={chartRef}
|
||||
data_in={dataResults}
|
||||
onTimelineClick={handleTimelineClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="section-box">
|
||||
<VideoPlayer
|
||||
videoRef={playerRef}
|
||||
playerInstanceRef={playerInstanceRef}
|
||||
setMarkerTimeFunc={setMarkerValueNonReactive}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user