feat(ui): add review keyboard shortcuts
This commit is contained in:
parent
116c0c982b
commit
d63fdd1def
@ -18,6 +18,15 @@ import { approveReviewItem, batchReviewItems, listAgentTimeline, listApprovalQue
|
|||||||
import { toast } from "@/lib/toast";
|
import { toast } from "@/lib/toast";
|
||||||
import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types";
|
import type { AgentTimelineItem, ApprovalQueueItem } from "@/lib/types";
|
||||||
|
|
||||||
|
function isKeyboardShortcutSafe(target: EventTarget | null) {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagName = target.tagName.toLowerCase();
|
||||||
|
return tagName !== "input" && tagName !== "textarea" && tagName !== "select" && !target.isContentEditable;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ReviewsPage() {
|
export default function ReviewsPage() {
|
||||||
const [approvalQueue, setApprovalQueue] = useState<ApprovalQueueItem[]>([]);
|
const [approvalQueue, setApprovalQueue] = useState<ApprovalQueueItem[]>([]);
|
||||||
const [timeline, setTimeline] = useState<AgentTimelineItem[]>([]);
|
const [timeline, setTimeline] = useState<AgentTimelineItem[]>([]);
|
||||||
@ -172,6 +181,88 @@ export default function ReviewsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function selectRelativeQueueItem(offset: number) {
|
||||||
|
if (approvalQueue.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentIndex = Math.max(
|
||||||
|
0,
|
||||||
|
approvalQueue.findIndex((item) => item.id === featuredProposal?.id),
|
||||||
|
);
|
||||||
|
const nextIndex = Math.min(Math.max(currentIndex + offset, 0), approvalQueue.length - 1);
|
||||||
|
setSelectedApprovalId(approvalQueue[nextIndex]?.id ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
|
if (
|
||||||
|
event.defaultPrevented ||
|
||||||
|
event.metaKey ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
event.altKey ||
|
||||||
|
!isKeyboardShortcutSafe(event.target)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = event.key.toLowerCase();
|
||||||
|
if (key === "arrowdown" || key === "j") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectRelativeQueueItem(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "arrowup" || key === "k") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectRelativeQueueItem(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "x") {
|
||||||
|
event.preventDefault();
|
||||||
|
selectAllForBatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((key === "escape" || key === "c") && batchMode) {
|
||||||
|
event.preventDefault();
|
||||||
|
clearBatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "a") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (batchMode) {
|
||||||
|
void handleBatchDecision("approved");
|
||||||
|
} else {
|
||||||
|
void handleDecision("approved");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "r") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (batchMode) {
|
||||||
|
void handleBatchDecision("rejected");
|
||||||
|
} else {
|
||||||
|
void handleDecision("rejected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [
|
||||||
|
approvalQueue,
|
||||||
|
batchMode,
|
||||||
|
clearBatch,
|
||||||
|
featuredProposal?.id,
|
||||||
|
handleBatchDecision,
|
||||||
|
handleDecision,
|
||||||
|
selectAllForBatch,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
title="Agent review"
|
title="Agent review"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user