- sesstions start by default
- added restart session button
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user