Skip to content

Commit 8bddb4f

Browse files
committed
feat: add logger commands to key bindings
1 parent cfbbd60 commit 8bddb4f

9 files changed

Lines changed: 144 additions & 44 deletions

File tree

frontend/testing-view/src/components/leftSidebar/ColorSchemeToggle.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ const ColorSchemeToggle = () => {
77
const toggleColorScheme = useStore((s) => s.toggleColorScheme);
88

99
const isPink = colorScheme === "pink";
10-
const tooltip = isPink ? "Switch to Default" : "Switch to Firmware";
10+
const tooltip = isPink ? "Switch to Default" : "Switch to Claudia";
1111

1212
return (
1313
<SidebarMenuButton tooltip={tooltip} onClick={toggleColorScheme}>
1414
<Palette className="size-4" />
15-
<span>{isPink ? "Firmware" : "Default"}</span>
15+
<span>{isPink ? "Claudia" : "Default"}</span>
1616
<div className="bg-primary ml-auto h-3 w-3 rounded-full" />
1717
</SidebarMenuButton>
1818
);

frontend/testing-view/src/components/settings/SettingsDialog.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ export const SettingsDialog = () => {
4444
"https://api.github.com/repos/hyperloop-upv/adj/branches?per_page=100",
4545
);
4646
const data = await res.json();
47-
setTimeout(() => {
48-
setBranches(data.map((b: { name: string }) => b.name));
49-
}, 3000);
47+
setBranches(data.map((b: { name: string }) => b.name));
5048
} catch (error) {
5149
console.error("Error loading branches:", error);
5250
}

frontend/testing-view/src/features/keyBindings/components/AddKeyBindingDialog.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getDefaultParameterValues } from "../../../lib/commandUtils";
2121
import { useStore } from "../../../store/store";
2222
import type { CommandCatalogItem } from "../../../types/data/commandCatalogItem";
2323
import { CommandParameters } from "../../workspace/components/rightSidebar/tabs/commands/CommandParameters";
24+
import { SPECIAL_COMMANDS } from "../constants/specialCommands";
2425
import { SPECIAL_KEY_BINDINGS } from "../constants/specialKeyBindings";
2526

2627
interface AddKeyBindingDialogProps {
@@ -139,6 +140,19 @@ export const AddKeyBindingDialog = ({
139140
<SelectValue placeholder="Select command..." />
140141
</SelectTrigger>
141142
<SelectContent className="px-2 py-2">
143+
<SelectGroup key="Logger">
144+
<SelectLabel>Logger</SelectLabel>
145+
{Object.entries(SPECIAL_COMMANDS).map(([id, label]) => (
146+
<SelectItem key={id} value={id}>
147+
<div className="flex flex-col">
148+
<span className="font-medium">{label}</span>
149+
<span className="text-muted-foreground text-xs">
150+
Logger
151+
</span>
152+
</div>
153+
</SelectItem>
154+
))}
155+
</SelectGroup>
142156
{Object.entries(commandsCatalog).map(
143157
([boardName, commands]) => (
144158
<SelectGroup key={boardName}>

frontend/testing-view/src/features/keyBindings/components/KeyBindingsDialog.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Plus } from "@workspace/ui/icons";
1010
import { useState } from "react";
1111
import { useShallow } from "zustand/shallow";
1212
import { useStore } from "../../../store/store";
13+
import { SPECIAL_COMMANDS } from "../constants/specialCommands";
1314
import type { KeyBinding } from "../types/keyBinding";
1415
import { AddKeyBindingDialog } from "./AddKeyBindingDialog";
1516
import { KeyBindingCard } from "./KeyBindingCard";
@@ -33,6 +34,17 @@ export const KeyBindingsDialog = ({
3334

3435
// Get command details for display
3536
const getCommandDetails = (commandId: number) => {
37+
if (commandId in SPECIAL_COMMANDS) {
38+
return {
39+
command: {
40+
id: commandId,
41+
name: String(commandId),
42+
label: SPECIAL_COMMANDS[commandId],
43+
fields: {},
44+
},
45+
boardName: "Logger",
46+
};
47+
}
3648
for (const [boardName, commands] of Object.entries(commandsCatalog)) {
3749
const command = commands.find((c) => c.id === commandId);
3850
if (command) {
@@ -59,7 +71,7 @@ export const KeyBindingsDialog = ({
5971
<p className="mb-2 text-sm">No key bindings yet</p>
6072
</div>
6173
) : (
62-
<div className="max-h-[400px] space-y-2 overflow-y-auto">
74+
<div className="max-h-100 space-y-2 overflow-y-auto">
6375
{keyBindings.map((binding) => {
6476
const details = getCommandDetails(binding.commandId);
6577
if (!details)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/** Special id for starting logger command */
2+
export const START_LOGGER_COMMAND_ID = -1;
3+
4+
/** Special id for stopping logger command */
5+
export const STOP_LOGGER_COMMAND_ID = -2;
6+
7+
/** Special id for toggling logger command */
8+
export const TOGGLE_LOGGER_COMMAND_ID = -3;
9+
10+
export const SPECIAL_COMMANDS: Record<number, string> = {
11+
[START_LOGGER_COMMAND_ID]: "Start Logger",
12+
[STOP_LOGGER_COMMAND_ID]: "Stop Logger",
13+
[TOGGLE_LOGGER_COMMAND_ID]: "Toggle Logger",
14+
};

frontend/testing-view/src/features/keyBindings/hooks/useGlobalKeyBindings.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { logger, socketService } from "@workspace/core";
22
import { useEffect } from "react";
3+
import { useLogger } from "../../../hooks/useLogger";
34
import { getDefaultParameterValues } from "../../../lib/commandUtils";
45
import { useStore } from "../../../store/store";
56
import type { CommandCatalogItem } from "../../../types/data/commandCatalogItem";
7+
import {
8+
START_LOGGER_COMMAND_ID,
9+
STOP_LOGGER_COMMAND_ID,
10+
TOGGLE_LOGGER_COMMAND_ID,
11+
} from "../constants/specialCommands";
612
import { SPECIAL_KEY_BINDINGS } from "../constants/specialKeyBindings";
713

814
export const useGlobalKeyBindings = () => {
915
const getKeyBindings = useStore((s) => s.getKeyBindings);
1016
const commandsCatalog = useStore((s) => s.commandsCatalog);
17+
const { startLogging, stopLogging, toggleLogging } = useLogger();
1118

1219
useEffect(() => {
1320
const handleKeyPress = (e: KeyboardEvent) => {
@@ -37,6 +44,31 @@ export const useGlobalKeyBindings = () => {
3744

3845
// Execute each binding
3946
bindings.forEach((binding) => {
47+
// Handle special built-in commands
48+
if (binding.commandId === START_LOGGER_COMMAND_ID) {
49+
logger.testingView.log(
50+
`Executing Start Logger via key binding [${key}]`,
51+
);
52+
startLogging();
53+
return;
54+
}
55+
56+
if (binding.commandId === STOP_LOGGER_COMMAND_ID) {
57+
logger.testingView.log(
58+
`Executing Stop Logger via key binding [${key}]`,
59+
);
60+
stopLogging();
61+
return;
62+
}
63+
64+
if (binding.commandId === TOGGLE_LOGGER_COMMAND_ID) {
65+
logger.testingView.log(
66+
`Executing Toggle Logger via key binding [${key}]`,
67+
);
68+
toggleLogging();
69+
return;
70+
}
71+
4072
// Find the command in the catalog
4173
let commandToExecute: CommandCatalogItem | null = null;
4274
for (const commands of Object.values(commandsCatalog)) {
@@ -103,5 +135,11 @@ export const useGlobalKeyBindings = () => {
103135

104136
window.addEventListener("keydown", handleKeyPress);
105137
return () => window.removeEventListener("keydown", handleKeyPress);
106-
}, [getKeyBindings, commandsCatalog]);
138+
}, [
139+
getKeyBindings,
140+
commandsCatalog,
141+
startLogging,
142+
stopLogging,
143+
toggleLogging,
144+
]);
107145
};

frontend/testing-view/src/features/workspace/components/LoggerControl.tsx

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { cn } from "@workspace/ui/lib";
44
import { LOGGER_CONTROL_CONFIG } from "../../../constants/loggerControlConfig";
55
import { useLogger } from "../../../hooks/useLogger";
66
import { useStore } from "../../../store/store";
7-
import type { BoardName } from "../../../types/data/board";
8-
import type { TelemetryCatalogItem } from "../../../types/data/telemetryCatalogItem";
97

108
interface LoggerControlProps {
119
disabled: boolean;
@@ -22,25 +20,7 @@ export const LoggerControl = ({ disabled }: LoggerControlProps) => {
2220
if (status === "recording") {
2321
stopLogging();
2422
} else {
25-
const catalog = useStore.getState().getCatalog("logs");
26-
const filters = useStore.getState().getActiveFilters("logs");
27-
28-
if (!catalog || !filters) return;
29-
30-
const variableNames = Object.entries(catalog).flatMap(
31-
([boardName, items]) => {
32-
const selectedIds = filters[boardName as BoardName] || [];
33-
const selectedPackets = items.filter((item) =>
34-
selectedIds.includes(item.id),
35-
) as TelemetryCatalogItem[];
36-
37-
return selectedPackets.flatMap((p) =>
38-
p.measurements.map((m) => `${boardName}/${m.id}`),
39-
);
40-
},
41-
);
42-
43-
startLogging(variableNames);
23+
startLogging();
4424
}
4525
};
4626

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,84 @@
11
import { socketService } from "@workspace/core";
22
import { useTopic } from "@workspace/ui/hooks";
3-
import { useRef, useState } from "react";
3+
import { useEffect, useState } from "react";
44
import { config } from "../../config";
5+
import { useStore } from "../store/store";
6+
import type { BoardName } from "../types/data/board";
57
import type { LoggerStatus } from "../types/common/logger";
8+
import type { TelemetryCatalogItem } from "../types/data/telemetryCatalogItem";
9+
10+
// Shared singleton state across all useLogger instances
11+
let sharedStatus: LoggerStatus = "standby";
12+
let sharedTimeout: ReturnType<typeof setTimeout> | null = null;
13+
const listeners = new Set<(status: LoggerStatus) => void>();
14+
15+
const updateStatus = (status: LoggerStatus) => {
16+
sharedStatus = status;
17+
listeners.forEach((l) => l(status));
18+
};
19+
20+
export const getLoggerStatus = () => sharedStatus;
621

722
export function useLogger() {
8-
const [status, setStatus] = useState<LoggerStatus>("standby");
9-
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
23+
const [status, setStatus] = useState<LoggerStatus>(sharedStatus);
24+
25+
useEffect(() => {
26+
listeners.add(setStatus);
27+
return () => { listeners.delete(setStatus); };
28+
}, []);
1029

1130
const log = (enable: boolean) => {
12-
if (timeoutRef.current) clearTimeout(timeoutRef.current);
31+
if (sharedTimeout) clearTimeout(sharedTimeout);
1332

14-
setStatus("loading");
33+
updateStatus("loading");
1534

1635
socketService.post("logger/enable", enable);
1736

18-
timeoutRef.current = setTimeout(() => {
19-
setStatus("error");
20-
timeoutRef.current = null;
37+
sharedTimeout = setTimeout(() => {
38+
updateStatus("error");
39+
sharedTimeout = null;
2140
}, config.LOGGER_RESPONSE_TIMEOUT);
2241
};
2342

2443
useTopic<boolean>("logger/response", (isLogging) => {
25-
if (timeoutRef.current) {
26-
clearTimeout(timeoutRef.current);
27-
timeoutRef.current = null;
44+
if (sharedTimeout) {
45+
clearTimeout(sharedTimeout);
46+
sharedTimeout = null;
2847
}
29-
setStatus(isLogging ? "recording" : "standby");
48+
updateStatus(isLogging ? "recording" : "standby");
3049
});
3150

32-
const startLogging = (vars: string[]) => {
51+
const getVariables = (): string[] => {
52+
const catalog = useStore.getState().getCatalog("logs");
53+
const filters = useStore.getState().getActiveFilters("logs");
54+
55+
if (!catalog || !filters) return [];
56+
57+
return Object.entries(catalog).flatMap(([boardName, items]) => {
58+
const selectedIds = filters[boardName as BoardName] || [];
59+
const selectedPackets = items.filter((item) =>
60+
selectedIds.includes(item.id),
61+
) as TelemetryCatalogItem[];
62+
return selectedPackets.flatMap((p) =>
63+
p.measurements.map((m) => `${boardName}/${m.id}`),
64+
);
65+
});
66+
};
67+
68+
const startLogging = () => {
69+
if (sharedStatus === "recording" || sharedStatus === "loading") return;
70+
const vars = getVariables();
3371
socketService.post("logger/variables", vars);
3472
log(true);
3573
};
3674

37-
const stopLogging = () => log(false);
75+
const stopLogging = () => {
76+
if (sharedStatus !== "recording") return;
77+
log(false);
78+
};
79+
80+
const toggleLogging = () =>
81+
sharedStatus === "recording" ? stopLogging() : startLogging();
3882

39-
return { status, startLogging, stopLogging };
83+
return { status, startLogging, stopLogging, toggleLogging };
4084
}

frontend/testing-view/src/pages/Testing.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const Testing = () => {
4646
</div>
4747
<div>
4848
<h2 className="text-2xl font-bold">No Active Workspace</h2>
49-
<p className="text-muted-foreground mt-2 max-w-[500px]">
49+
<p className="text-muted-foreground max-w-125 mt-2">
5050
Create your first workspace to start organizing your commands,
5151
telemetry, and charts.
5252
</p>

0 commit comments

Comments
 (0)