import { EventEnvelope, EventType } from "@/api_fsb";
import { selectLatestEvent, selectLatestEvents } from "@/models/grlEventsSlice";
import { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
// ─── Config ───────────────────────────────────────────────────────────────────
const BASE_SPEED = 60; // px/sec at idle
const MAX_SPEED = 380; // px/sec at surge
const RATE_WINDOW = 6_000; // ms window to measure message frequency
const SPEED_EASING = 0.025; // how quickly speed transitions (per frame)
const RATE_TO_SPEED = 55; // px/sec added per msg/sec
interface EventItemProps {
event: EventEnvelope;
}
const EventTaskEnter = ({ event }: EventItemProps) => {
return (
<>
{event.product_user_id} Entered
>
)
}
const EventTaskFinish = ({ event }: EventItemProps) => {
return (
<>
{event.product_user_id} Finished
>
)
}
const EventUserCreated = ({ event }: EventItemProps) => {
return (
<>
{event.product_user_id} Created
>
)
}
const EventComponent = ({ event }: EventItemProps) => {
const renderContent = () => {
switch (event.event_type) {
case EventType.TaskEnter:
return
case EventType.TaskFinish:
return
case EventType.UserCreated:
return
default:
return <>Unknown event
>
}
}
return (
{renderContent()}
◆
)
}
// ─── Main Component ───────────────────────────────────────────────────────────
export default function NewsTicker() {
const latestEvent = useSelector(selectLatestEvent);
const latestEvents = useSelector(selectLatestEvents(10));
const itemsRef = useRef(latestEvents);
const [speed, setSpeed] = useState(BASE_SPEED);
const stripRef = useRef(null);
const containerRef = useRef(null);
const translateX = useRef(0);
const currentSpeed = useRef(BASE_SPEED);
const targetSpeed = useRef(BASE_SPEED);
const rafRef = useRef(null);
const lastTime = useRef(null);
const nextId = useRef(1);
const pendingQueue = useRef([]);
const messageTimes = useRef([]);
// keep itemsRef in sync
useEffect(() => {
console.log("Adding message to ticker:", latestEvent);
itemsRef.current = latestEvents;
}, [latestEvents]);
// const addMessage = useCallback(() => {
// console.log("Adding message to ticker:", latestEvent);
// // calc rate over window
// messageTimes.current = messageTimes.current.filter(
// (t) => now - t < RATE_WINDOW
// );
// // const rate = messageTimes.current.length / (RATE_WINDOW / 1000); // msgs/sec
// // targetSpeed.current = Math.min(BASE_SPEED + rate * RATE_TO_SPEED, MAX_SPEED);
// // pendingQueue.current.push({ id: nextId.current++, text });
// }, [latestEvent]);
// RAF animation loop
// useEffect(() => {
// const loop = (timestamp) => {
// if (!lastTime.current) lastTime.current = timestamp;
// const delta = (timestamp - lastTime.current) / 1000; // seconds
// lastTime.current = timestamp;
// // ease speed toward target
// currentSpeed.current +=
// (targetSpeed.current - currentSpeed.current) * SPEED_EASING;
// // consume pending queue
// if (pendingQueue.current.length > 0) {
// const newItems = pendingQueue.current.splice(0);
// setItems((prev) => [...prev, ...newItems]);
// }
// // advance strip
// translateX.current -= currentSpeed.current * delta;
// // apply transform directly — no re-render
// if (stripRef.current) {
// stripRef.current.style.transform = `translateX(${translateX.current}px)`;
// }
// // update speed display periodically (for the indicator)
// setSpeed(Math.round(currentSpeed.current));
// // prune items that have scrolled off-screen
// if (stripRef.current && containerRef.current) {
// const children = Array.from(stripRef.current.children);
// const containerLeft = containerRef.current.getBoundingClientRect().left;
// let pruneCount = 0;
// for (const child of children) {
// const rect = child.getBoundingClientRect();
// if (rect.right < containerLeft - 20) {
// pruneCount++;
// } else {
// break;
// }
// }
// if (pruneCount > 0) {
// // measure how much width we're removing so we can adjust translateX
// let removedWidth = 0;
// for (let i = 0; i < pruneCount; i++) {
// removedWidth += children[i].getBoundingClientRect().width;
// }
// translateX.current += removedWidth;
// setItems((prev) => prev.slice(pruneCount));
// }
// }
// rafRef.current = requestAnimationFrame(loop);
// };
// rafRef.current = requestAnimationFrame(loop);
// return () => cancelAnimationFrame(rafRef.current);
// }, []);
// speed bucket for visual indicator
// const speedLevel =
// currentSpeed.current < 100 ? 0 :
// currentSpeed.current < 180 ? 1 :
// currentSpeed.current < 260 ? 2 : 3;
// const speedLabels = ["NORMAL", "ELEVATED", "HIGH", "SURGE"];
// const speedColors = ["#22d3ee", "#f59e0b", "#f97316", "#ef4444"];
return (
{/* Ticker wrapper */}
{/* The ticker band */}
{/* Scrolling strip */}
{latestEvents.map((item, i) => (
))}
{/* Trailing spacer so new content doesn't immediately snap in */}
);
}