Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial keyboard shortcuts #32

Merged
merged 20 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea5a840
add useKeySequence for keyboard shortcut handling
keturiosakys Jun 7, 2024
512a7c0
add initial keyboard shortcuts to the overview and detail pages
keturiosakys Jun 7, 2024
c04bad6
remove unused useEffect
keturiosakys Jun 7, 2024
7008b70
update with fixes so that all instances of useKeySequence registered …
keturiosakys Jun 7, 2024
38b7cfd
put the rows data in a ref so that `useKeySequence` hooks pick it up …
keturiosakys Jun 7, 2024
cde502f
formatting
keturiosakys Jun 7, 2024
0abbbcb
update fixes based on linter suggestion except one
keturiosakys Jun 7, 2024
db49c13
fix formatting
keturiosakys Jun 7, 2024
5a4e16d
change the useKeySequence hook to always use the latest reference to …
keturiosakys Jun 10, 2024
2873873
simplify the keysequence + callback logic in the datatable component
keturiosakys Jun 10, 2024
f6dac8a
formatting fix
keturiosakys Jun 10, 2024
fc4d19d
make callback deps more accurate
keturiosakys Jun 10, 2024
8eb8f7a
some more useKeySequence updates
keturiosakys Jun 10, 2024
0893e73
Fix side effect inside state update
brettimus Jun 10, 2024
b024bd0
use muted as opposed to explicit color so it can work in future darkm…
keturiosakys Jun 10, 2024
ca021d4
kill transition animation so the thing feels snappy
keturiosakys Jun 11, 2024
eac5595
basic mouse-over logic
keturiosakys Jun 11, 2024
1ff7132
use useHandler hook so that the callback doesn't get reevaluated ever…
keturiosakys Jun 11, 2024
8b085dd
clean up the table return and use clsx for className resolution
keturiosakys Jun 11, 2024
af4a1ea
nav => navigate
keturiosakys Jun 11, 2024
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
81 changes: 62 additions & 19 deletions frontend/src/components/ui/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from "@tanstack/react-table";
import { clsx } from "clsx";

import { useKeySequence } from "@/hooks";

import {
Table,
TableBody,
Expand All @@ -16,6 +18,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useCallback, useState } from "react";

// Extend the ColumnMeta type to include headerClassName and cellClassName
//
Expand Down Expand Up @@ -67,6 +70,37 @@ export function DataTable<TData, TValue>({
}),
});

const [selectedRowIndex, setSelectedRowIndex] = useState<number | null>(null);
const rows = table.getRowModel().rows;

const handleNextRow = useCallback(() => {
keturiosakys marked this conversation as resolved.
Show resolved Hide resolved
setSelectedRowIndex((prevIndex) => {
if (prevIndex === null) return 0;
if (prevIndex + 1 >= rows.length) return prevIndex;

return prevIndex + 1;
});
}, [rows]);

const handlePrevRow = useCallback(() => {
setSelectedRowIndex((prevIndex) => {
if (prevIndex === null) return 0;
if (prevIndex - 1 < 0) return prevIndex;
return prevIndex - 1;
});
}, []);

const handleRowSelect = useCallback(() => {
if (selectedRowIndex !== null && rows.length > 0) {
const selectedRow = rows[selectedRowIndex];
handleRowClick?.(selectedRow);
}
}, [selectedRowIndex, rows, handleRowClick]);

useKeySequence(["j"], handleNextRow);
useKeySequence(["k"], handlePrevRow);
useKeySequence(["Enter"], handleRowSelect);

return (
<div className="rounded-md border">
<Table>
Expand Down Expand Up @@ -95,25 +129,34 @@ export function DataTable<TData, TValue>({
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
onClick={() => handleRowClick?.(row)}
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={clsx(
"py-1",
cell.column.columnDef.meta?.cellClassName,
)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
table.getRowModel().rows.map((row, rowIdx) => {
// console.log("row", row.getIsSelected(), row.id)
keturiosakys marked this conversation as resolved.
Show resolved Hide resolved
return (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
onClick={() => handleRowClick?.(row)}
onMouseEnter={() => setSelectedRowIndex(rowIdx)}
onMouseLeave={() => setSelectedRowIndex(null)}
className={`${rowIdx === selectedRowIndex ? "bg-muted/50" : ""} transition-none`}
keturiosakys marked this conversation as resolved.
Show resolved Hide resolved
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={clsx(
"py-1",
cell.column.columnDef.meta?.cellClassName,
)}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
);
})
) : (
<TableRow>
<TableCell
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useKeySequence } from "./keyboard.ts";
77 changes: 77 additions & 0 deletions frontend/src/hooks/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useCallback, useEffect, useRef, useState } from "react";

/**
* Define a sequence of keys to be pressed in order to trigger a callback. Note that it should be used only for sequences, not chords. TODO: create a hook for chords.
*
* @param sequence - An array of keys to be pressed in order to trigger the callback.
* @param callback - The callback to be executed when the sequence of keys is pressed.
* @param timeout - The timeout in milliseconds to wait before executing the callback. Default is 2000.
* @returns keySequence - An array of the keys that were pressed in order to trigger the callback. This is useful for debugging purposes.
*
*/
export function useKeySequence(
sequence: string[],
callback: () => void,
timeout: number = 2000,
) {
const [keySequence, setKeySequence] = useState<string[]>([]);
const timeoutRef = useRef<number | undefined>(undefined);
const callbackRef = useRef(callback);
const sequenceRef = useRef(sequence);

// ensure that the callback and sequence are updated when they change
useEffect(() => {
sequenceRef.current = sequence;
}, [sequence]);

useEffect(() => {
callbackRef.current = callback;
}, [callback]);

// create and persist the callback across re-renders
const handleKeyPress = useCallback((event: KeyboardEvent) => {
setKeySequence((prevKeySequence) => {
const updatedSequence = [...prevKeySequence, event.key].slice(
-sequenceRef.current.length,
);

return updatedSequence;
});
}, []);

useEffect(() => {
brettimus marked this conversation as resolved.
Show resolved Hide resolved
if (keySequence.join("") === sequenceRef.current.join("")) {
callbackRef.current();
setKeySequence([]);
}
}, [keySequence]);

useEffect(() => {
const resetSequence = () => setKeySequence([]);

if (keySequence.length > 0) {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(
resetSequence,
timeout,
) as unknown as number;
}

return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [keySequence, timeout]);

useEffect(() => {
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [handleKeyPress]);

return keySequence;
}
2 changes: 0 additions & 2 deletions frontend/src/pages/RequestDetailsPage/RequestDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ const FetchRequestLog = ({ log }: { log: MizuLog }) => {
? log?.message?.url
: "UNKNOWN_URL";
const description = `Fetch Request: ${url}`;
console.log("FETCH REQUEST", log.args);

return (
<LogCard>
Expand All @@ -202,7 +201,6 @@ const FetchResponseLog = ({ log }: { log: MizuLog }) => {
? log?.message?.url
: "UNKNOWN_URL";
const description = `Fetch Response: ${url}`;
console.log("FETCH RESPONSE", url, log.args);

return (
<LogCard>
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/pages/RequestDetailsPage/RequestDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useMizuTraces } from "@/queries";
import { isError } from "react-query";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";

import { useKeySequence } from "@/hooks";
import { TraceDetails } from "./RequestDetails";

function useRequestDetails(traceId?: string) {
Expand All @@ -30,6 +31,12 @@ export function RequestDetailsPage() {
const { traceId } = useParams<{ traceId: string }>();
const { trace } = useRequestDetails(traceId);

const nav = useNavigate();
keturiosakys marked this conversation as resolved.
Show resolved Hide resolved

useKeySequence(["Escape"], () => {
nav("/requests");
});

return (
<Tabs defaultValue="all">
<div className="flex items-center">
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/pages/RequestsPage/Requests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
TrashIcon,
// ListBulletIcon as ListFilter, // FIXME
} from "@radix-ui/react-icons";
import { useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo } from "react";
import { useQueryClient } from "react-query";
import { useNavigate } from "react-router-dom";

Expand All @@ -29,6 +29,7 @@ import { DataTable } from "@/components/ui/DataTable";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { type MizuTrace, useMizuTraces } from "@/queries";
import { Row } from "@tanstack/react-table";
import { columns } from "./columns";

type LevelFilter = "all" | "error" | "warning" | "info" | "debug";
Expand All @@ -48,11 +49,18 @@ const RequestsTable = ({
);
}, [traces, filter]);

const handleRowClick = useCallback(
(row: Row<MizuTrace>) => {
navigate(`/requests/${row.id}`);
},
[navigate],
);

return (
<DataTable
columns={columns}
data={filteredTraces ?? []}
handleRowClick={(row) => navigate(`/requests/${row.id}`)}
handleRowClick={handleRowClick}
/>
);
};
Expand Down
Loading