Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d0be8e4
Yoni - basic shift tab
YoniKiriaty Jan 22, 2026
63a7c5f
Yoni - fixed image scaling issue
YoniKiriaty Jan 22, 2026
1d25cfe
Yoni - better styling
YoniKiriaty Jan 22, 2026
e99b400
Merge remote-tracking branch 'origin/master' into scouting/feat/shift…
YoniKiriaty Jan 22, 2026
f87ef2c
Yoni - tweaked stopwatch
YoniKiriaty Jan 22, 2026
1667b72
Yoni - changed props of stopwatch
YoniKiriaty Jan 22, 2026
3db316f
Yoni - added disable to stopwatch
YoniKiriaty Jan 22, 2026
f836592
Yoni - got correct times on form
YoniKiriaty Jan 23, 2026
d3f89ce
Yoni - truncated position
YoniKiriaty Jan 23, 2026
939c033
Yoni - truly standardized the allmap position
YoniKiriaty Jan 23, 2026
233b76e
Yoni - simplified logic
YoniKiriaty Jan 23, 2026
7bc901a
Yoni - added side button and changed basic css (! changed button css !)
YoniKiriaty Jan 23, 2026
e2f21cb
Yoni - added hidden color divs
YoniKiriaty Jan 23, 2026
8d24af5
Yoni -started working on movement
YoniKiriaty Jan 23, 2026
9c7683a
Yoni - added movement
YoniKiriaty Jan 25, 2026
fa9247d
Yoni - merged master
YoniKiriaty Jan 25, 2026
7f4db14
Yoni - finished basic design
YoniKiriaty Jan 25, 2026
79b6b78
Yoni - redesigned movement and changed direction of teleop scouting
YoniKiriaty Jan 25, 2026
18eec65
Yoni - changed color to rose
YoniKiriaty Jan 25, 2026
a9ef772
Yoni - merged master
YoniKiriaty Jan 25, 2026
b2bce1f
Yoni - fixed bad task
YoniKiriaty Jan 25, 2026
fe8c8a6
Yoni - fixed according to CR
YoniKiriaty Jan 25, 2026
294c303
Yoni - removed loose mode
YoniKiriaty Jan 25, 2026
9760918
Yoni - target touche
YoniKiriaty Jan 25, 2026
d4c435f
Yoni - improved pipe
YoniKiriaty Jan 25, 2026
140b0ea
Yoni - default point
YoniKiriaty Jan 25, 2026
4abad17
Yoni - fixed conflicts
YoniKiriaty Jan 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{
"label": "dev",
"type": "shell",
"command": "./node_modules/.bin/dotenv -e .public.env -e .secret.env -- 'turbo run dev --filter=${input:project}-frontend --filter=${input:project}-backend'",
"command": "./node_modules/.bin/dotenv -e .dev.env -e .public.env -e .secret.env -- 'turbo run dev --filter=${input:project}-frontend --filter=${input:project}-backend'",
"problemMatcher": []
},
{
Expand All @@ -24,10 +24,7 @@
{
"label": "deploy",
"type": "shell",
"dependsOn": [
"build",
"serve"
],
"dependsOn": ["build", "serve"],
"dependsOrder": "sequence"
},
{
Expand All @@ -48,7 +45,7 @@
"type": "pickString",
"options": ["template", "test", "scouting"]
},

{
"id": "workspace",
"description": "Workspaces within projects",
Expand Down
4 changes: 0 additions & 4 deletions apps/scouting/backend/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ import { StatusCodes } from "http-status-codes";
import { tbaRouter } from "./tba";
import { gameRouter } from "./game-router";



export const apiRouter = Router();


apiRouter.use("/tba", tbaRouter);
apiRouter.use("/game", gameRouter);
apiRouter.get("/health", (req, res) => {
res.status(StatusCodes.OK).send({ message: "Healthy!" });
});

83 changes: 32 additions & 51 deletions apps/scouting/frontend/src/components/stopwatch.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
// בס"ד
import type React from "react";
import { useEffect, useRef, useState } from "react";

interface CycleStopwatchCounter {
startCycleTime: number;
endCycleTimer: number;
}
import { useEffect, useRef, useState, type Dispatch } from "react";
import type { Interval } from "@repo/scouting_types";

const MILLLISECONDS_IN_A_SECOND = 1000;
const SECOND_IN_A_MINUTE = 60;
const INITIAL_TIME_MILLISECONDS = 0;
const CYCLE_TIME_MILLISECONDS = 10;
const DECIMAL_PLACES = 2
const DECIMAL_PLACES = 2;
const DECIMAL_PLACES_MILLISECONDS = 3;

interface StopwatchProps {
addCycleTimeSeconds: Dispatch<Interval>;
originTime: number;
disabled: boolean;
}

const Stopwatch: React.FC = () => {
const Stopwatch: React.FC<StopwatchProps> = ({
addCycleTimeSeconds,
originTime,
disabled,
}) => {
const [isRunning, setIsRunning] = useState(false);
const [elapsedTime, setElapsedTime] = useState(INITIAL_TIME_MILLISECONDS);
const [cycleTimesInMilliseconds, setCycleTimesInMilliseconds] = useState<
CycleStopwatchCounter[]
>([]);

const startTimeRef = useRef(INITIAL_TIME_MILLISECONDS);
const originRef = useRef<number | null>(null);

const startCurrentCycleTime = useRef<number>(INITIAL_TIME_MILLISECONDS);

Expand All @@ -30,28 +33,18 @@ const Stopwatch: React.FC = () => {
setIsRunning(false);
};

const calculateMinutes = () => {
return Math.floor(
(elapsedTime / (MILLLISECONDS_IN_A_SECOND * SECOND_IN_A_MINUTE)) %
SECOND_IN_A_MINUTE,
);
};

const calculateSeconds = () => {
return Math.floor(
(elapsedTime / MILLLISECONDS_IN_A_SECOND) % SECOND_IN_A_MINUTE,
);
};

const calculateMilliSeconds = () => {
return Math.floor(
(elapsedTime % MILLLISECONDS_IN_A_SECOND) / SECOND_IN_A_MINUTE,
);
return Math.floor(elapsedTime % MILLLISECONDS_IN_A_SECOND);
};

const getCurrentRelativeTime = () => {
originRef.current ??= Date.now();
return Date.now() - originRef.current;
return Date.now() - originTime;
};

useEffect(() => {
Expand All @@ -68,7 +61,7 @@ const Stopwatch: React.FC = () => {
}, [isRunning]);

const start = () => {
if (isRunning) {
if (isRunning || disabled) {
return;
}
const relativeTime = getCurrentRelativeTime();
Expand All @@ -83,34 +76,33 @@ const Stopwatch: React.FC = () => {
return;
}

const cycleStopwatchCounter: CycleStopwatchCounter = {
startCycleTime: startCurrentCycleTime.current,
endCycleTimer: getCurrentRelativeTime(),
const cycleStopwatchCounter: Interval = {
start: startCurrentCycleTime.current,
end: getCurrentRelativeTime(),
};
setCycleTimesInMilliseconds((prev) => [...prev, cycleStopwatchCounter]);

addCycleTimeSeconds(cycleStopwatchCounter);

setIsRunning(false);
reset();
};

useEffect(() => {
console.log(cycleTimesInMilliseconds);
}, [cycleTimesInMilliseconds]);

const formatTime = () => {
const minutes = String(calculateMinutes()).padStart(DECIMAL_PLACES, "0");
const seconds = String(calculateSeconds()).padStart(DECIMAL_PLACES, "0");
const milliseconds = String(calculateMilliSeconds()).padStart(DECIMAL_PLACES, "0");
return `${minutes}:${seconds}:${milliseconds}`;
const milliseconds = String(calculateMilliSeconds()).padStart(
DECIMAL_PLACES_MILLISECONDS,
"0",
);
return `${seconds}:${milliseconds}`;
};

return (
<div className="flex flex-col items-center gap-6 p-6">
<div className="flex flex-col items-center py-6 px-5">
<div
className={`
select-none cursor-pointer rounded-2xl px-10 py-6
text-4xl font-mono font-semibold shadow-lg transition-all duration-150
${isRunning ? "bg-emerald-500 text-white scale-95" : "bg-slate-800 text-emerald-400 hover:bg-slate-700"}
select-none cursor-pointer rounded-2xl px-4 py-4
text-3xl font-mono font-semibold shadow-lg transition-all duration-150
${disabled ? "bg-slate-800 text-slate-900" : isRunning ? "bg-emerald-500 text-white scale-95" : "bg-slate-800 text-green-400 hover:bg-slate-700"}
`}
onMouseDown={start}
onMouseUp={stop}
Expand All @@ -120,17 +112,6 @@ const Stopwatch: React.FC = () => {
>
{formatTime()}
</div>

<button
onClick={reset}
className="
rounded-lg px-4 py-1.5 text-sm font-medium text-slate-600
border border-slate-300 hover:bg-slate-100 hover:text-slate-800
active:scale-95 transition
"
>
Reset
</button>
</div>
);
};
Expand Down
6 changes: 3 additions & 3 deletions apps/scouting/frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@import "tailwindcss";
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
Expand Down Expand Up @@ -70,11 +69,9 @@ h1 {
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
Expand Down Expand Up @@ -121,6 +118,7 @@ button:focus-visible {
.unhighlighted-tab {
@apply text-emerald-400 hover:bg-green-950 hover:text-emerald-300
border border-emerald-500/20 hover:border-emerald-500/50
bg-[#1a1a1a]
hover:shadow-[0_0_10px_rgba(34,197,94,0.3)];
}
}
Expand All @@ -129,3 +127,5 @@ button:focus-visible {
--color-greenBlitz: #00ff00;
--color-blackBlitz: #001100;
}

@import "tailwindcss";
33 changes: 33 additions & 0 deletions apps/scouting/frontend/src/scouter/components/MovementForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// בס"ד

import type { Dispatch, FC } from "react";
import type { ScoutingForm } from "@repo/scouting_types";

type Movement = ScoutingForm["tele"]["movement"];

interface MovementFormProps {
setMovement: Dispatch<Movement>;
currentMovement: Movement;
}

export const MovementForm: FC<MovementFormProps> = ({
setMovement,
currentMovement,
}) => {
return (
<div className="flex flex-col items-center">
<div className="bg-rose-500" />
<button
className={`bg-${currentMovement.bumpStuck ? "rose-500" : "slate-800"} m-1 w-32 h-16 px-2`}
onClick={() => {
setMovement({
...currentMovement,
bumpStuck: !currentMovement.bumpStuck,
});
}}
>
Stuck Bump
</button>
</div>
);
};
78 changes: 58 additions & 20 deletions apps/scouting/frontend/src/scouter/components/ScoreMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,42 @@ import {
type TouchEvent,
type Touch,
} from "react";
import type { Point } from "@repo/scouting_types";
import type { Alliance, Point } from "@repo/scouting_types";
import { pipe } from "fp-ts/lib/function";

interface ScoreMapProps {
currentPoint?: Point;
setPosition: Dispatch<SetStateAction<Point | undefined>>;
alliance: "red" | "blue";
mapZone: "red" | "blue";
alliance: Alliance;
mapZone: Alliance;
}

const ALLIANCE_ZONE_WIDTH_PIXELS = 395;
const TWO_THIRDS_WIDTH_PIXELS = 1010;
const HEIGHT_PIXELS = 652;
const alliancizePosition = (alliance: Alliance, position: Point): Point => {
if (alliance === "red") {
return position;
}

return {
x: TWO_THIRDS_WIDTH_PIXELS - position.x,
y: HEIGHT_PIXELS - position.y,
};
};

const switchZone = (point: Point) => {
return {
...point,
x: point.x + ALLIANCE_ZONE_WIDTH_PIXELS,
};
};

const normalizePosition = (point: Point, bounds: Point) => ({
x: (point.x * TWO_THIRDS_WIDTH_PIXELS) / bounds.x,
y: (point.y * HEIGHT_PIXELS) / bounds.y,
});

const dotRadius = 10;
const radiusToDiameterRatio = 2;
const dotDiameter = dotRadius * radiusToDiameterRatio;
Expand All @@ -36,6 +63,7 @@ const getRobotPosition = (touch: Touch, bound: DOMRect) => {
bound.bottom - dotRadius - bound.top,
Math.max(y, dotRadius),
);

return { x: boundedX, y: boundedY };
};

Expand All @@ -46,42 +74,52 @@ export const ScoreMap: FC<ScoreMapProps> = ({
mapZone,
}) => {
const [isHolding, setHolding] = useState(false);

const [mapPoint, setMapPoint] = useState(currentPoint);

const handleMapClick = (event: TouchEvent<HTMLDivElement>) => {
if (!isHolding) {
return;
}
const rect = event.currentTarget.getBoundingClientRect();
const touch = event.targetTouches[firstTouchIndex];

const dotPoint = getRobotPosition(touch, rect);

const touch = event.touches[firstTouchIndex];
setMapPoint(dotPoint);

setPosition(getRobotPosition(touch, rect));
pipe(
dotPoint,
(point) => normalizePosition(point, { x: rect.width, y: rect.height }),
(point) => alliancizePosition(alliance, point),
(point) => (alliance === mapZone ? point : switchZone(point)),
(point) => ({ x: Math.round(point.x), y: Math.round(point.y) }),
setPosition,
);
};

return (
<div
draggable={false}
className="max-h-screen relative mx-auto touch-none"
onTouchMove={handleMapClick}
onTouchStart={() => {
setHolding(true);
}}
onTouchEnd={() => {
setHolding(false);
}}
>
<div draggable={false} className="h-full relative touch-none">
<img
src={`/${mapZone}-field-4418.png`}
className="max-h-screen block select-none"
onTouchMove={handleMapClick}
onTouchStart={() => {
setHolding(true);
}}
onTouchEnd={() => {
setHolding(false);
}}
className="h-full block select-none"
alt="Game Map"
draggable={false}
/>

{currentPoint && (
{mapPoint && (
<div
className={`absolute border-2 border-black shadow-[0_0_10px_#ccff00] -translate-x-1/2 -translate-y-1/2 pointer-events-none`}
style={{
left: currentPoint.x,
top: currentPoint.y,
left: mapPoint.x,
top: mapPoint.y,
width: dotDiameter,
height: dotDiameter,
backgroundColor: alliance === "blue" ? "darkcyan" : "crimson",
Expand Down
Loading
Loading