Merge pull request 'feature/baywatchPage' (#2) from feature/baywatchPage into develop
Reviewed-on: #2
This commit is contained in:
@@ -19,9 +19,13 @@
|
|||||||
"@tanstack/react-router": "^1.136.18",
|
"@tanstack/react-router": "^1.136.18",
|
||||||
"@tanstack/react-router-devtools": "^1.136.18",
|
"@tanstack/react-router-devtools": "^1.136.18",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"konva": "^10.0.11",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-use-websocket": "3.0.0"
|
"react-konva": "^19.2.0",
|
||||||
|
"react-tabs": "^6.1.0",
|
||||||
|
"react-use-websocket": "3.0.0",
|
||||||
|
"sonner": "^2.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
|||||||
40
src/features/cameras/components/CameraGrid.tsx
Normal file
40
src/features/cameras/components/CameraGrid.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
import VideoFeedGridPainter from "./Video/VideoFeedGridPainter";
|
||||||
|
import CameraSettings from "./CameraSettings/CameraSettings";
|
||||||
|
import type { Region } from "../../../types/types";
|
||||||
|
import PlatePatch from "./PlatePatch/PlatePatch";
|
||||||
|
|
||||||
|
const CameraGrid = () => {
|
||||||
|
const [regions, setRegions] = useState<Region[]>([
|
||||||
|
{ name: "Region 1", brushColour: "#ff0000" },
|
||||||
|
{ name: "Region 2", brushColour: "#00ff00" },
|
||||||
|
{ name: "Region 3", brushColour: "#0400ff" },
|
||||||
|
]);
|
||||||
|
const [selectedRegionIndex, setSelectedRegionIndex] = useState(0);
|
||||||
|
const [mode, setMode] = useState("");
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
|
console.log(tabIndex);
|
||||||
|
const updateRegionColour = (index: number, newColour: string) => {
|
||||||
|
setRegions((prev) => prev.map((r, i) => (i === index ? { ...r, brushColour: newColour } : r)));
|
||||||
|
};
|
||||||
|
console.log(mode);
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-5 grid-rows-2">
|
||||||
|
<VideoFeedGridPainter regions={regions} selectedRegionIndex={selectedRegionIndex} mode={mode} />
|
||||||
|
<CameraSettings
|
||||||
|
regions={regions}
|
||||||
|
selectedRegionIndex={selectedRegionIndex}
|
||||||
|
onSelectRegion={setSelectedRegionIndex}
|
||||||
|
onChangeRegionColour={updateRegionColour}
|
||||||
|
mode={mode}
|
||||||
|
onSelectMode={setMode}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
setTabIndex={setTabIndex}
|
||||||
|
/>
|
||||||
|
<PlatePatch />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CameraGrid;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import Card from "../../../../ui/Card";
|
||||||
|
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
||||||
|
import "react-tabs/style/react-tabs.css";
|
||||||
|
import RegionSelector from "./RegionSelector";
|
||||||
|
import type { Region } from "../../../../types/types";
|
||||||
|
|
||||||
|
type CameraSettingsProps = {
|
||||||
|
regions: Region[];
|
||||||
|
selectedRegionIndex: number;
|
||||||
|
onSelectRegion: (index: number) => void;
|
||||||
|
onChangeRegionColour: (index: number, colour: string) => void;
|
||||||
|
mode: string;
|
||||||
|
onSelectMode: (mode: string) => void;
|
||||||
|
setTabIndex: (tabIndex: number) => void;
|
||||||
|
tabIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CameraSettings = ({
|
||||||
|
regions,
|
||||||
|
selectedRegionIndex,
|
||||||
|
onSelectRegion,
|
||||||
|
onChangeRegionColour,
|
||||||
|
mode,
|
||||||
|
onSelectMode,
|
||||||
|
tabIndex,
|
||||||
|
setTabIndex,
|
||||||
|
}: CameraSettingsProps) => {
|
||||||
|
return (
|
||||||
|
<Card className="p-4 max-h-screen col-span-3">
|
||||||
|
<Tabs
|
||||||
|
selectedTabClassName="bg-gray-300 text-gray-900 font-semibold border-none rounded-sm mb-1"
|
||||||
|
className="react-tabs"
|
||||||
|
onSelect={(index) => setTabIndex(index)}
|
||||||
|
>
|
||||||
|
<TabList>
|
||||||
|
<Tab>Target Detection</Tab>
|
||||||
|
<Tab>Camera 1</Tab>
|
||||||
|
<Tab>Camera 2</Tab>
|
||||||
|
<Tab>Camera 3</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel>
|
||||||
|
<RegionSelector
|
||||||
|
regions={regions}
|
||||||
|
selectedRegionIndex={selectedRegionIndex}
|
||||||
|
onSelectRegion={onSelectRegion}
|
||||||
|
onChangeRegionColour={onChangeRegionColour}
|
||||||
|
mode={mode}
|
||||||
|
onSelectMode={onSelectMode}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<div>Camera details {tabIndex}</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<div>Camera details {tabIndex}</div>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<div>Camera details {tabIndex}</div>
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CameraSettings;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
type ColourPickerProps = {
|
||||||
|
colour: string;
|
||||||
|
setColour: (colour: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ColourPicker = ({ colour, setColour }: ColourPickerProps) => {
|
||||||
|
return <input type="color" name="" id="" value={colour} onChange={(e) => setColour(e.target.value)} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColourPicker;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import ColourPicker from "./ColourPicker";
|
||||||
|
import type { Region } from "../../../../types/types";
|
||||||
|
|
||||||
|
type RegionSelectorProps = {
|
||||||
|
regions: Region[];
|
||||||
|
selectedRegionIndex: number;
|
||||||
|
onSelectRegion: (index: number) => void;
|
||||||
|
onChangeRegionColour: (index: number, colour: string) => void;
|
||||||
|
mode: string;
|
||||||
|
onSelectMode: (mode: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const RegionSelector = ({
|
||||||
|
regions,
|
||||||
|
selectedRegionIndex,
|
||||||
|
onSelectRegion,
|
||||||
|
onChangeRegionColour,
|
||||||
|
mode,
|
||||||
|
onSelectMode,
|
||||||
|
}: RegionSelectorProps) => {
|
||||||
|
const handleChange = (e: { target: { value: string } }) => {
|
||||||
|
onSelectMode(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl">Region Select</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{regions.map((region, idx) => (
|
||||||
|
<div
|
||||||
|
key={region.name}
|
||||||
|
className="items-center p-4 border border-gray-700 bg-slate-700 rounded-xl m-4 w-[40%]"
|
||||||
|
>
|
||||||
|
<label style={{ marginRight: "0.5rem" }}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={selectedRegionIndex === idx}
|
||||||
|
onChange={() => {
|
||||||
|
onSelectMode("painter");
|
||||||
|
onSelectRegion(idx);
|
||||||
|
}}
|
||||||
|
/>{" "}
|
||||||
|
{region.name}
|
||||||
|
</label>
|
||||||
|
<ColourPicker colour={region.brushColour} setColour={(c: string) => onChangeRegionColour(idx, c)} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl">Tools</h2>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<label htmlFor="mode">
|
||||||
|
<input id="mode" type="radio" onChange={handleChange} checked={mode === "painter"} value="painter" />
|
||||||
|
Paint mode
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label htmlFor="erase">
|
||||||
|
<input type="radio" onChange={handleChange} checked={mode === "eraser"} value={"eraser"} />
|
||||||
|
Erase mode
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RegionSelector;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Card from "../../../../ui/Card";
|
||||||
|
|
||||||
|
const PlatePatch = () => {
|
||||||
|
return <Card>PlatePatch</Card>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlatePatch;
|
||||||
153
src/features/cameras/components/Video/VideoFeedGridPainter.tsx
Normal file
153
src/features/cameras/components/Video/VideoFeedGridPainter.tsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { useEffect, useRef, useState, type RefObject } from "react";
|
||||||
|
import { Stage, Layer, Image, Shape } from "react-konva";
|
||||||
|
import type { KonvaEventObject } from "konva/lib/Node";
|
||||||
|
import { useCreateVideoSnapshot } from "../../hooks/useGetvideoSnapshots";
|
||||||
|
import type { Region } from "../../../../types/types";
|
||||||
|
import Card from "../../../../ui/Card";
|
||||||
|
|
||||||
|
const rows = 40;
|
||||||
|
const cols = 40;
|
||||||
|
const size = 20;
|
||||||
|
const gap = 0;
|
||||||
|
|
||||||
|
type VideoFeedGridPainterProps = {
|
||||||
|
regions: Region[];
|
||||||
|
selectedRegionIndex: number;
|
||||||
|
mode: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PaintedCell = {
|
||||||
|
colour: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VideoFeedGridPainter = ({ regions, selectedRegionIndex, mode }: VideoFeedGridPainterProps) => {
|
||||||
|
const { latestBitmapRef, isloading } = useCreateVideoSnapshot();
|
||||||
|
const [stageSize, setStageSize] = useState({ width: 740, height: 460 });
|
||||||
|
const isDrawingRef = useRef(false);
|
||||||
|
const paintedCellsRef = useRef<Map<string, PaintedCell>>(new Map());
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const paintLayerRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const draw = (bmp: RefObject<ImageBitmap | null>): ImageBitmap | null => {
|
||||||
|
if (!bmp || !bmp.current) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const image = bmp.current;
|
||||||
|
return image;
|
||||||
|
};
|
||||||
|
|
||||||
|
const image = draw(latestBitmapRef);
|
||||||
|
|
||||||
|
const paintCell = (x: number, y: number) => {
|
||||||
|
const col = Math.floor(x / (size + gap));
|
||||||
|
const row = Math.floor(y / (size + gap));
|
||||||
|
|
||||||
|
if (row < 0 || row >= rows || col < 0 || col >= cols) return;
|
||||||
|
|
||||||
|
const activeRegion = regions[selectedRegionIndex];
|
||||||
|
if (!activeRegion) return;
|
||||||
|
|
||||||
|
const key = `${row}-${col}`;
|
||||||
|
const currentColour = regions[selectedRegionIndex].brushColour;
|
||||||
|
|
||||||
|
const map = paintedCellsRef.current;
|
||||||
|
const existing = map.get(key);
|
||||||
|
|
||||||
|
if (mode === "eraser") {
|
||||||
|
if (map.has(key)) {
|
||||||
|
map.delete(key);
|
||||||
|
paintLayerRef.current?.batchDraw();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing && existing.colour === currentColour) return;
|
||||||
|
|
||||||
|
map.set(key, { colour: currentColour });
|
||||||
|
|
||||||
|
paintLayerRef.current?.batchDraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStageMouseDown = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
if (!regions[selectedRegionIndex]) return;
|
||||||
|
isDrawingRef.current = true;
|
||||||
|
const pos = e.target.getStage()?.getPointerPosition();
|
||||||
|
if (pos) paintCell(pos.x, pos.y);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStageMouseMove = (e: KonvaEventObject<MouseEvent>) => {
|
||||||
|
if (!isDrawingRef.current) return;
|
||||||
|
if (!regions[selectedRegionIndex]) return;
|
||||||
|
const pos = e.target.getStage()?.getPointerPosition();
|
||||||
|
if (pos) paintCell(pos.x, pos.y);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStageMouseUp = () => {
|
||||||
|
isDrawingRef.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
|
||||||
|
const aspectRatio = 740 / 460;
|
||||||
|
const newWidth = width * 0.36;
|
||||||
|
const newHeight = newWidth / aspectRatio;
|
||||||
|
setStageSize({ width: newWidth, height: newHeight });
|
||||||
|
};
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (image === null || isloading)
|
||||||
|
return (
|
||||||
|
<Card className="row-span-1 col-span-2 rounded-lg p-4 w-full">
|
||||||
|
<span className="text-slate-500">Loading Video feed…</span>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="row-span-1 col-span-2 rounded-lg">
|
||||||
|
<Stage
|
||||||
|
width={stageSize.width}
|
||||||
|
height={stageSize.height}
|
||||||
|
onMouseDown={handleStageMouseDown}
|
||||||
|
onMouseMove={handleStageMouseMove}
|
||||||
|
onMouseUp={handleStageMouseUp}
|
||||||
|
onMouseLeave={handleStageMouseUp}
|
||||||
|
>
|
||||||
|
<Layer>
|
||||||
|
<Image image={image} width={stageSize.width} height={stageSize.height} classname={"rounded-lg"} />
|
||||||
|
</Layer>
|
||||||
|
|
||||||
|
<Layer ref={paintLayerRef} opacity={0.6}>
|
||||||
|
<Shape
|
||||||
|
sceneFunc={(ctx, shape) => {
|
||||||
|
const cells = paintedCellsRef.current;
|
||||||
|
cells.forEach((cell, key) => {
|
||||||
|
const [rowStr, colStr] = key.split("-");
|
||||||
|
const row = Number(rowStr);
|
||||||
|
const col = Number(colStr);
|
||||||
|
|
||||||
|
const x = col * (size + gap);
|
||||||
|
const y = row * (size + gap);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(x, y, size, size);
|
||||||
|
ctx.fillStyle = cell.colour;
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.fillStrokeShape(shape);
|
||||||
|
}}
|
||||||
|
width={stageSize.width}
|
||||||
|
height={stageSize.height}
|
||||||
|
/>
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 };
|
||||||
|
};
|
||||||
27
src/features/cameras/hooks/useGetvideoSnapshots.ts
Normal file
27
src/features/cameras/hooks/useGetvideoSnapshots.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
const isloading = videoQuery.isPending;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function createBitmap() {
|
||||||
|
if (!snapShot) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bitmap = await createImageBitmap(snapShot);
|
||||||
|
if (!bitmap) return;
|
||||||
|
latestBitmapRef.current = bitmap;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createBitmap();
|
||||||
|
}, [snapShot]);
|
||||||
|
|
||||||
|
return { latestBitmapRef, isloading };
|
||||||
|
};
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import CameraGrid from "../features/cameras/components/CameraGrid";
|
||||||
|
import { Toaster } from "sonner";
|
||||||
|
|
||||||
export const Route = createFileRoute('/baywatch')({
|
export const Route = createFileRoute("/baywatch")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/baywatch"!</div>
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold">Cameras</h2>
|
||||||
|
<CameraGrid />
|
||||||
|
<Toaster />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,8 @@ export type InfoBarData = {
|
|||||||
"memory-cpu-status": string;
|
"memory-cpu-status": string;
|
||||||
"thread-count": string;
|
"thread-count": string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Region = {
|
||||||
|
name: string;
|
||||||
|
brushColour: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const Header = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link to="/baywatch" className="[&.active]:font-bold">
|
<Link to="/baywatch" className="[&.active]:font-bold">
|
||||||
Baywatch
|
Cameras
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/output" className="[&.active]:font-bold">
|
<Link to="/output" className="[&.active]:font-bold">
|
||||||
Output
|
Output
|
||||||
|
|||||||
84
yarn.lock
84
yarn.lock
@@ -1011,6 +1011,16 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c"
|
||||||
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
|
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":
|
"@types/react@^19.2.5":
|
||||||
version "19.2.6"
|
version "19.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.6.tgz#d27db1ff45012d53980f5589fda925278e1249ca"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.6.tgz#d27db1ff45012d53980f5589fda925278e1249ca"
|
||||||
@@ -1283,7 +1293,7 @@ chokidar@^3.6.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
clsx@^2.1.1:
|
clsx@^2.0.0, clsx@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||||
@@ -1726,12 +1736,19 @@ isexe@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
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:
|
jiti@^2.6.1:
|
||||||
version "2.6.1"
|
version "2.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92"
|
||||||
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
|
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
|
||||||
|
|
||||||
js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
@@ -1775,6 +1792,11 @@ keyv@^4.5.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer "3.0.1"
|
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:
|
levn@^0.4.1:
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||||
@@ -1869,6 +1891,13 @@ lodash.merge@^4.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||||
|
|
||||||
|
loose-envify@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
|
dependencies:
|
||||||
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
lru-cache@^5.1.1:
|
lru-cache@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||||
@@ -1940,6 +1969,11 @@ normalize-range@^0.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||||
|
|
||||||
|
object-assign@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
|
||||||
optionator@^0.9.3:
|
optionator@^0.9.3:
|
||||||
version "0.9.4"
|
version "0.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
|
||||||
@@ -2027,6 +2061,15 @@ prettier@^3.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
|
||||||
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
|
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
|
||||||
|
|
||||||
|
prop-types@^15.5.0:
|
||||||
|
version "15.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
react-is "^16.13.1"
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||||
@@ -2044,11 +2087,41 @@ react-dom@^19.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
scheduler "^0.27.0"
|
scheduler "^0.27.0"
|
||||||
|
|
||||||
|
react-is@^16.13.1:
|
||||||
|
version "16.13.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
|
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:
|
react-refresh@^0.18.0:
|
||||||
version "0.18.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
|
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.18.0.tgz#2dce97f4fe932a4d8142fa1630e475c1729c8062"
|
||||||
integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==
|
integrity sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==
|
||||||
|
|
||||||
|
react-tabs@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-6.1.0.tgz#a1fc9d9b8db4c6e7bb327a1b6783bc51a1c457a1"
|
||||||
|
integrity sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==
|
||||||
|
dependencies:
|
||||||
|
clsx "^2.0.0"
|
||||||
|
prop-types "^15.5.0"
|
||||||
|
|
||||||
react-use-websocket@3.0.0:
|
react-use-websocket@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-use-websocket/-/react-use-websocket-3.0.0.tgz#754cb8eea76f55d31c5676d4abe3e573bc2cea04"
|
resolved "https://registry.yarnpkg.com/react-use-websocket/-/react-use-websocket-3.0.0.tgz#754cb8eea76f55d31c5676d4abe3e573bc2cea04"
|
||||||
@@ -2130,7 +2203,7 @@ run-parallel@^1.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
scheduler@^0.27.0:
|
scheduler@0.27.0, scheduler@^0.27.0:
|
||||||
version "0.27.0"
|
version "0.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd"
|
||||||
integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
|
integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==
|
||||||
@@ -2167,6 +2240,11 @@ shebang-regex@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||||
|
|
||||||
|
sonner@^2.0.7:
|
||||||
|
version "2.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/sonner/-/sonner-2.0.7.tgz#810c1487a67ec3370126e0f400dfb9edddc3e4f6"
|
||||||
|
integrity sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==
|
||||||
|
|
||||||
source-map-js@^1.2.1:
|
source-map-js@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
|
|||||||
Reference in New Issue
Block a user