- added Konvas lib
- added feed from proof of concept
This commit is contained in:
@@ -19,8 +19,10 @@
|
||||
"@tanstack/react-router": "^1.136.18",
|
||||
"@tanstack/react-router-devtools": "^1.136.18",
|
||||
"clsx": "^2.1.1",
|
||||
"konva": "^10.0.11",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react-dom": "^19.2.0",
|
||||
"react-konva": "^19.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
|
||||
11
src/features/cameras/components/CameraGrid.tsx
Normal file
11
src/features/cameras/components/CameraGrid.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import VideoFeedCard from "./VideoFeedCard";
|
||||
|
||||
const CameraGrid = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2">
|
||||
<VideoFeedCard />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CameraGrid;
|
||||
13
src/features/cameras/components/VideoFeedCard.tsx
Normal file
13
src/features/cameras/components/VideoFeedCard.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import Card from "../../../ui/Card";
|
||||
|
||||
import VideoFeedGridPainter from "./VideoFeedGridPainter";
|
||||
|
||||
const VideoFeedCard = () => {
|
||||
return (
|
||||
<Card className="w-full md:w-[70%]">
|
||||
<VideoFeedGridPainter />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoFeedCard;
|
||||
75
src/features/cameras/components/VideoFeedGridPainter.tsx
Normal file
75
src/features/cameras/components/VideoFeedGridPainter.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useRef, type RefObject } from "react";
|
||||
import { Stage, Layer, Image, Rect } from "react-konva";
|
||||
import { useCreateVideoSnapshot } from "../hooks/useGetvideoSnapshots";
|
||||
|
||||
const VideoFeedGridPainter = () => {
|
||||
const { latestBitmapRef } = useCreateVideoSnapshot();
|
||||
const isDrawing = useRef(false);
|
||||
|
||||
const rows = 100;
|
||||
const cols = 100;
|
||||
const size = 10;
|
||||
const gap = 0;
|
||||
|
||||
const squares = [];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
squares.push(
|
||||
<Rect
|
||||
key={`${row}-${col}`}
|
||||
x={col * (size + gap)}
|
||||
y={row * (size + gap)}
|
||||
width={size}
|
||||
height={size}
|
||||
fill="#ddd"
|
||||
stroke="black"
|
||||
strokeWidth={0.5}
|
||||
opacity={0.5}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getCoords = (e) => {
|
||||
isDrawing.current = true;
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (!isDrawing.current) {
|
||||
return;
|
||||
}
|
||||
const pos = e.target.getStage().getPointerPosition();
|
||||
console.log(pos);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDrawing.current = false;
|
||||
};
|
||||
|
||||
const draw = (bmp: RefObject<ImageBitmap | null>) => {
|
||||
if (!bmp || !bmp.current) {
|
||||
return;
|
||||
} else {
|
||||
const frame = bmp.current;
|
||||
return frame;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stage
|
||||
width={640}
|
||||
height={360}
|
||||
onMouseDown={getCoords}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
<Layer>
|
||||
<Image image={draw(latestBitmapRef)} width={640} height={360} />
|
||||
</Layer>
|
||||
<Layer opacity={0.3}>{squares}</Layer>
|
||||
</Stage>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoFeedGridPainter;
|
||||
22
src/features/cameras/hooks/useGetVideoFeed.ts
Normal file
22
src/features/cameras/hooks/useGetVideoFeed.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
const getfeed = async () => {
|
||||
const response = await fetch(`http://100.115.148.59/TargetDetectionColour-preview`, {
|
||||
signal: AbortSignal.timeout(300000),
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Cannot reach endpoint (${response.status})`);
|
||||
}
|
||||
return response.blob();
|
||||
};
|
||||
|
||||
export const useGetVideoFeed = () => {
|
||||
const videoQuery = useQuery({
|
||||
queryKey: ["getfeed"],
|
||||
queryFn: getfeed,
|
||||
refetchInterval: 500,
|
||||
});
|
||||
|
||||
return { videoQuery };
|
||||
};
|
||||
25
src/features/cameras/hooks/useGetvideoSnapshots.ts
Normal file
25
src/features/cameras/hooks/useGetvideoSnapshots.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useGetVideoFeed } from "./useGetVideoFeed";
|
||||
|
||||
export const useCreateVideoSnapshot = () => {
|
||||
const latestBitmapRef = useRef<ImageBitmap | null>(null);
|
||||
const { videoQuery } = useGetVideoFeed();
|
||||
|
||||
const snapShot = videoQuery?.data;
|
||||
|
||||
useEffect(() => {
|
||||
async function createBitmap() {
|
||||
if (!snapShot) return;
|
||||
|
||||
try {
|
||||
const bitmap = await createImageBitmap(snapShot);
|
||||
latestBitmapRef.current = bitmap;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
createBitmap();
|
||||
}, [snapShot]);
|
||||
|
||||
return { latestBitmapRef };
|
||||
};
|
||||
@@ -1,9 +1,15 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import CameraGrid from "../features/cameras/components/CameraGrid";
|
||||
|
||||
export const Route = createFileRoute('/baywatch')({
|
||||
export const Route = createFileRoute("/baywatch")({
|
||||
component: RouteComponent,
|
||||
})
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/baywatch"!</div>
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">Cameras</h2>
|
||||
<CameraGrid />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const Header = () => {
|
||||
</Link>
|
||||
|
||||
<Link to="/baywatch" className="[&.active]:font-bold">
|
||||
Baywatch
|
||||
Cameras
|
||||
</Link>
|
||||
<Link to="/output" className="[&.active]:font-bold">
|
||||
Output
|
||||
|
||||
41
yarn.lock
41
yarn.lock
@@ -1011,6 +1011,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
||||
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
|
||||
|
||||
"@types/react-reconciler@^0.28.9":
|
||||
version "0.28.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.9.tgz#d24b4864c384e770c83275b3fe73fba00269c83b"
|
||||
integrity sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==
|
||||
|
||||
"@types/react-reconciler@^0.32.2":
|
||||
version "0.32.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.32.3.tgz#eb4b346f367f29f07628032934d30a4f3f9eaba7"
|
||||
integrity sha512-cMi5ZrLG7UtbL7LTK6hq9w/EZIRk4Mf1Z5qHoI+qBh7/WkYkFXQ7gOto2yfUvPzF5ERMAhaXS5eTQ2SAnHjLzA==
|
||||
|
||||
"@types/react@^19.2.5":
|
||||
version "19.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.6.tgz#d27db1ff45012d53980f5589fda925278e1249ca"
|
||||
@@ -1726,6 +1736,13 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
its-fine@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-2.0.0.tgz#a90b18a3ee4c211a1fb6faac2abcc2b682ce1f21"
|
||||
integrity sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==
|
||||
dependencies:
|
||||
"@types/react-reconciler" "^0.28.9"
|
||||
|
||||
jiti@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92"
|
||||
@@ -1775,6 +1792,11 @@ keyv@^4.5.4:
|
||||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
konva@^10.0.11:
|
||||
version "10.0.11"
|
||||
resolved "https://registry.yarnpkg.com/konva/-/konva-10.0.11.tgz#f63d3422625d13513094b646a2f09359d644542a"
|
||||
integrity sha512-h0O6YNrwdgfb76kzkiMIqkyufUxE6GPcNwJZhYalnZ5qnYBEuxSRk62fQwtJqygGP5hdZKzJs2ea1ivVP+zetw==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
@@ -2044,6 +2066,23 @@ react-dom@^19.2.0:
|
||||
dependencies:
|
||||
scheduler "^0.27.0"
|
||||
|
||||
react-konva@^19.2.0:
|
||||
version "19.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-19.2.0.tgz#b4cc5d73cd6d642569e4df36a0139996c3dcf8e6"
|
||||
integrity sha512-Ofifq/rdNvff50+Lj8x86WSfoeQDvdysOlsXMMrpD2uWmDxUPrEYSRLt27iCfdovQZL6xinKRpX9VaL9xDwXDQ==
|
||||
dependencies:
|
||||
"@types/react-reconciler" "^0.32.2"
|
||||
its-fine "^2.0.0"
|
||||
react-reconciler "0.33.0"
|
||||
scheduler "0.27.0"
|
||||
|
||||
react-reconciler@0.33.0:
|
||||
version "0.33.0"
|
||||
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.33.0.tgz#9dd20208d45baa5b0b4701781f858236657f15e1"
|
||||
integrity sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==
|
||||
dependencies:
|
||||
scheduler "^0.27.0"
|
||||
|
||||
react-refresh@^0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
|
||||
@@ -2125,7 +2164,7 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
scheduler@^0.27.0:
|
||||
scheduler@0.27.0, scheduler@^0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
||||
integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
|
||||
|
||||
Reference in New Issue
Block a user