- sesstions start by default

- added restart session button
This commit is contained in:
2025-11-19 11:52:37 +00:00
parent 516b43a2f8
commit ea93053dd3
3 changed files with 40 additions and 21 deletions

View File

@@ -4,7 +4,7 @@ import { useIntegrationsContext } from "../../context/IntegrationsContext";
import type { ReducedSightingType } from "../../types/types"; import type { ReducedSightingType } from "../../types/types";
import { toast } from "sonner"; import { toast } from "sonner";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFloppyDisk, faPause, faPlay, faStop } from "@fortawesome/free-solid-svg-icons"; import { faFloppyDisk, faPause, faPlay, faStop, faArrowRotateRight } from "@fortawesome/free-solid-svg-icons";
import VehicleSessionItem from "../UI/VehicleSessionItem"; import VehicleSessionItem from "../UI/VehicleSessionItem";
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard"; import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
@@ -70,6 +70,21 @@ const SessionCard = () => {
if (result.reason === "OK") toast.success("Session saved"); if (result.reason === "OK") toast.success("Session saved");
}; };
const handleRestartClick = async () => {
const result = await mutation.mutateAsync({
operation: "INSERT",
path: "sessionStats",
value: [],
});
await mutation.mutateAsync({
operation: "SAVE",
path: "",
value: null,
});
if (result.reason === "OK") toast.success("Session restarted");
dispatch({ type: "SESSIONRESTART", payload: [] });
};
return ( return (
<Card className="p-4 col-span-3"> <Card className="p-4 col-span-3">
<CardHeader title="Session" /> <CardHeader title="Session" />
@@ -85,6 +100,17 @@ const SessionCard = () => {
<p>{sessionStarted ? "End Session" : "Start Session"}</p> <p>{sessionStarted ? "End Session" : "Start Session"}</p>
</div> </div>
</button> </button>
{sessionStarted && (
<button
className={`bg-none text-white px-4 py-2 rounded transition w-full border border-gray-500 hover:bg-gray-500 hover:text-gray-900`}
onClick={handleRestartClick}
>
<div className="flex flex-row gap-3 items-center justify-self-center">
<FontAwesomeIcon icon={faArrowRotateRight} />
<p>Restart Session</p>
</div>
</button>
)}
<div className="flex flex-col lg:flex-row gap-5"> <div className="flex flex-col lg:flex-row gap-5">
{sessionStarted && ( {sessionStarted && (
<button <button

View File

@@ -1,7 +1,7 @@
import type { NPEDACTION, NPEDSTATE } from "../../types/types"; import type { NPEDACTION, NPEDSTATE } from "../../types/types";
export const initialState = { export const initialState = {
sessionStarted: false, sessionStarted: true,
sessionList: [], sessionList: [],
sessionPaused: false, sessionPaused: false,
savedSightings: [], savedSightings: [],
@@ -36,6 +36,11 @@ export function reducer(state: NPEDSTATE, action: NPEDACTION) {
...state, ...state,
sessionPaused: action.payload, sessionPaused: action.payload,
}; };
case "SESSIONRESTART":
return {
...state,
sessionList: action.payload,
};
case "ADD": case "ADD":
return { return {
...state, ...state,

View File

@@ -15,7 +15,6 @@ async function fetchSnapshot(cameraSide: string): Promise<Blob> {
return response.blob(); return response.blob();
} }
/** Draw an ImageBitmap to canvas with aspect-fill (like object-fit: cover) */
function drawBitmapToCanvas(canvas: HTMLCanvasElement, bitmap: ImageBitmap) { function drawBitmapToCanvas(canvas: HTMLCanvasElement, bitmap: ImageBitmap) {
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
if (!ctx) return; if (!ctx) return;
@@ -42,18 +41,14 @@ function drawBitmapToCanvas(canvas: HTMLCanvasElement, bitmap: ImageBitmap) {
let drawWidth = width; let drawWidth = width;
let drawHeight = height; let drawHeight = height;
// aspect-fit calculation (no cropping)
if (srcAspect > dstAspect) { if (srcAspect > dstAspect) {
// image is wider → fit to canvas width
drawWidth = width; drawWidth = width;
drawHeight = width / srcAspect; drawHeight = width / srcAspect;
} else { } else {
// image is taller → fit to canvas height
drawHeight = height; drawHeight = height;
drawWidth = height * srcAspect; drawWidth = height * srcAspect;
} }
// center image (adds black borders if aspect ratios differ)
const dx = (width - drawWidth) / 50; const dx = (width - drawWidth) / 50;
const dy = (height - drawHeight) / 2; const dy = (height - drawHeight) / 2;
@@ -66,7 +61,6 @@ export function useGetOverviewSnapshot(side: string) {
const canvasRef = useRef<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null);
const latestBitmapRef = useRef<ImageBitmap | null>(null); const latestBitmapRef = useRef<ImageBitmap | null>(null);
// Redraw helper; always draws the current bitmap if available
const draw = useCallback(() => { const draw = useCallback(() => {
const canvas = canvasRef.current; const canvas = canvasRef.current;
const bmp = latestBitmapRef.current; const bmp = latestBitmapRef.current;
@@ -82,16 +76,15 @@ export function useGetOverviewSnapshot(side: string) {
} = useQuery({ } = useQuery({
queryKey: ["overviewSnapshot", side], queryKey: ["overviewSnapshot", side],
queryFn: () => fetchSnapshot(side), queryFn: () => fetchSnapshot(side),
// Poll ~4 fps when visible; pause when tab hidden
refetchInterval: () => (document.visibilityState === "visible" ? 250 : false), refetchInterval: () => (document.visibilityState === "visible" ? 250 : false),
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
// Avoid keeping lots of blobs around in cache
gcTime: 0, // v5 name (cacheTime in v4) gcTime: 0,
staleTime: 0, staleTime: 0,
retry: false, // or a small number if you prefer retries retry: false,
}); });
// Convert Blob -> ImageBitmap and draw
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
if (!snapshotBlob) return; if (!snapshotBlob) return;
@@ -104,13 +97,11 @@ export function useGetOverviewSnapshot(side: string) {
return; return;
} }
// Dispose previous bitmap to free memory
if (latestBitmapRef.current) { if (latestBitmapRef.current) {
latestBitmapRef.current.close(); latestBitmapRef.current.close();
} }
latestBitmapRef.current = bitmap; latestBitmapRef.current = bitmap;
// Draw now (and again on next resize)
draw(); draw();
} catch { } catch {
// noop — fetch handler surfaces the main error path // noop — fetch handler surfaces the main error path
@@ -122,12 +113,11 @@ export function useGetOverviewSnapshot(side: string) {
}; };
}, [snapshotBlob, draw]); }, [snapshotBlob, draw]);
// Redraw on resize & DPR changes
useEffect(() => { useEffect(() => {
const onResize = () => draw(); const onResize = () => draw();
const onDPR = () => draw(); const onDPR = () => draw();
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
// Listen for DPR changes (some browsers support this)
const mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); const mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
mql.addEventListener?.("change", onDPR); mql.addEventListener?.("change", onDPR);
return () => { return () => {
@@ -137,14 +127,13 @@ export function useGetOverviewSnapshot(side: string) {
}, [draw]); }, [draw]);
useEffect(() => { useEffect(() => {
const el = canvasRef.current?.parentElement; // the box const el = canvasRef.current?.parentElement;
if (!el) return; if (!el) return;
const ro = new ResizeObserver(() => draw()); // your draw() calls aspect-fit logic const ro = new ResizeObserver(() => draw());
ro.observe(el); ro.observe(el);
return () => ro.disconnect(); return () => ro.disconnect();
}, [draw]); }, [draw]);
// Cleanup on unmount
useEffect(() => { useEffect(() => {
return () => { return () => {
if (latestBitmapRef.current) { if (latestBitmapRef.current) {
@@ -154,7 +143,6 @@ export function useGetOverviewSnapshot(side: string) {
}; };
}, []); }, []);
// Optional: normalize error type
const typedError = error instanceof Error ? error : undefined; const typedError = error instanceof Error ? error : undefined;
return { canvasRef, isError, error: typedError, isPending }; return { canvasRef, isError, error: typedError, isPending };