Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions frontend/src/components/console/nav/nav-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { useCommonData } from "../data-provider"
import { IconChevronDown, IconChevronRight, IconDots, IconFolder, IconFolderOpen, IconFolderPlus, IconLoader, IconPlus, IconPointFilled } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
Expand Down Expand Up @@ -104,6 +105,8 @@ export default function NavProject() {
const [deleting, setDeleting] = useState(false)
const [taskToStop, setTaskToStop] = useState<DomainProjectTask | null>(null)
const [stopping, setStopping] = useState(false)
const deleteTaskDialogNavigation = useDialogActionNavigation()
const stopTaskDialogNavigation = useDialogActionNavigation()
const [historyExpanded, setHistoryExpanded] = useState(false)

const { projects, reloadProjects, unlinkedTasks, reloadUnlinkedTasks, historicalTasks, reloadHistoricalTasks } = useCommonData()
Expand Down Expand Up @@ -407,7 +410,7 @@ export default function NavProject() {
</SidebarMenu>
)}
<AlertDialog open={!!taskToDelete} onOpenChange={(open) => !open && setTaskToDelete(null)}>
<AlertDialogContent className="max-h-[calc(100dvh-2rem)] grid-rows-[minmax(0,1fr)_auto] overflow-hidden">
<AlertDialogContent onKeyDown={deleteTaskDialogNavigation.onKeyDown} className="max-h-[calc(100dvh-2rem)] grid-rows-[minmax(0,1fr)_auto] overflow-hidden">
<AlertDialogHeader
role="region"
tabIndex={0}
Expand All @@ -420,8 +423,9 @@ export default function NavProject() {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleting}>{t("navProject.common.cancel")}</AlertDialogCancel>
<AlertDialogCancel ref={deleteTaskDialogNavigation.cancelRef} disabled={deleting}>{t("navProject.common.cancel")}</AlertDialogCancel>
<AlertDialogAction
ref={deleteTaskDialogNavigation.confirmRef}
onClick={(e) => {
e.preventDefault()
handleConfirmDeleteTask()
Expand All @@ -435,7 +439,7 @@ export default function NavProject() {
</AlertDialogContent>
</AlertDialog>
<AlertDialog open={!!taskToStop} onOpenChange={(open) => !open && setTaskToStop(null)}>
<AlertDialogContent className="max-h-[calc(100dvh-2rem)] grid-rows-[minmax(0,1fr)_auto] overflow-hidden">
<AlertDialogContent onKeyDown={stopTaskDialogNavigation.onKeyDown} className="max-h-[calc(100dvh-2rem)] grid-rows-[minmax(0,1fr)_auto] overflow-hidden">
<AlertDialogHeader
role="region"
tabIndex={0}
Expand All @@ -448,8 +452,9 @@ export default function NavProject() {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={stopping}>{t("navProject.common.cancel")}</AlertDialogCancel>
<AlertDialogCancel ref={stopTaskDialogNavigation.cancelRef} disabled={stopping}>{t("navProject.common.cancel")}</AlertDialogCancel>
<AlertDialogAction
ref={stopTaskDialogNavigation.confirmRef}
onClick={(e) => {
e.preventDefault()
handleConfirmStopTask()
Expand Down
23 changes: 5 additions & 18 deletions frontend/src/components/console/task/chat-inputbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { AvailableCommand, AvailableCommands, TaskStreamStatus, TaskUserInp
import { Button } from "@/components/ui/button"
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
import { isCompressibleImageFile, MAX_TASK_UPLOAD_FILE_SIZE_BYTES, MAX_TASK_UPLOAD_FILE_SIZE_LABEL, TaskFileUploadDialog, TaskUploadedFileItem, TaskUploadFileTooLargeError, uploadTaskFile, type TaskUploadedFile } from "./task-file-upload"
Expand Down Expand Up @@ -140,8 +141,7 @@ export const TaskChatInputBox = React.forwardRef<TaskChatInputBoxHandle, TaskCha
const fileInputRef = useRef<HTMLInputElement>(null)
const quickInputContainerRef = useRef<HTMLDivElement>(null)
const quickInputMeasureRef = useRef<HTMLDivElement>(null)
const slashCommandCancelRef = useRef<HTMLButtonElement>(null)
const slashCommandConfirmRef = useRef<HTMLButtonElement>(null)
const slashCommandDialogNavigation = useDialogActionNavigation()
const dragDepthRef = useRef(0)
const nextAttachmentFileIndexRef = useRef(1)
const autoSendingQueuedInputRef = useRef(false)
Expand Down Expand Up @@ -472,19 +472,6 @@ export const TaskChatInputBox = React.forwardRef<TaskChatInputBoxHandle, TaskCha
void sendCurrentInput()
}

const handleSlashCommandDialogKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
slashCommandCancelRef.current?.focus()
return
}

if (event.key === "ArrowRight") {
event.preventDefault()
slashCommandConfirmRef.current?.focus()
}
}

const handleContentChange = (nextContent: string) => {
if (
selectedQuickInputRef.current
Expand Down Expand Up @@ -1330,16 +1317,16 @@ export const TaskChatInputBox = React.forwardRef<TaskChatInputBoxHandle, TaskCha
}}
/>
<AlertDialog open={slashCommandConfirmOpen} onOpenChange={setSlashCommandConfirmOpen}>
<AlertDialogContent onKeyDown={handleSlashCommandDialogKeyDown}>
<AlertDialogContent onKeyDown={slashCommandDialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("taskDetail.chat.slashCommand.title")}</AlertDialogTitle>
<AlertDialogDescription>
{t("taskDetail.chat.slashCommand.description")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel ref={slashCommandCancelRef}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction ref={slashCommandConfirmRef} onClick={handleConfirmSlashCommand}>
<AlertDialogCancel ref={slashCommandDialogNavigation.cancelRef}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction ref={slashCommandDialogNavigation.confirmRef} onClick={handleConfirmSlashCommand}>
{t("taskDetail.chat.slashCommand.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { IconCopy, IconDotsVertical, IconDownload, IconFile, IconFolder, IconReload, IconTrash, IconTransfer, IconUpload } from "@tabler/icons-react"
import { nativeDownloadFile, normalizePath } from "@/utils/common"
import { apiRequest } from "@/utils/requestUtils"
Expand Down Expand Up @@ -59,6 +60,7 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
const [moveFileDialogOpen, setMoveFileDialogOpen] = useState(false)
const [downloadDialogOpen, setDownloadDialogOpen] = useState(false)
const [downloadFileHandle, setDownloadFileHandle] = useState<FileSystemFileHandle | null>(null)
const deleteDialogNavigation = useDialogActionNavigation()

const isDirectory = file.entry_mode === RepoFileEntryMode.RepoEntryModeTree
const filePath = normalizePath('/workspace/' + file.path)
Expand Down Expand Up @@ -181,16 +183,16 @@ export function FileActionsDropdown({ file, envid, onRefresh, onSuccess, alwaysV
</DropdownMenu>

<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent onClick={(e) => e.stopPropagation()}>
<AlertDialogContent onClick={(e) => e.stopPropagation()} onKeyDown={deleteDialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("taskDetail.fileActions.deleteTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{t("taskDetail.fileActions.deleteDescription", { fileType, displayPath })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete}>{t("taskDetail.fileActions.confirmDelete")}</AlertDialogAction>
<AlertDialogCancel ref={deleteDialogNavigation.cancelRef}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction ref={deleteDialogNavigation.confirmRef} onClick={handleDelete}>{t("taskDetail.fileActions.confirmDelete")}</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { MAX_TASK_UPLOAD_FILE_SIZE_LABEL } from "./task-file-upload"

interface TaskLongContentDialogProps {
Expand All @@ -29,6 +30,7 @@ export function TaskLongContentDialog({
onConfirm,
}: TaskLongContentDialogProps) {
const { t } = useTranslation()
const dialogNavigation = useDialogActionNavigation()

return (
<AlertDialog
Expand All @@ -38,7 +40,7 @@ export function TaskLongContentDialog({
onOpenChange(nextOpen)
}}
>
<AlertDialogContent>
<AlertDialogContent onKeyDown={dialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("taskDetail.chat.longContent.title")}</AlertDialogTitle>
<AlertDialogDescription>
Expand All @@ -53,6 +55,7 @@ export function TaskLongContentDialog({
</div>
<AlertDialogFooter>
<Button
ref={dialogNavigation.cancelRef}
type="button"
variant="outline"
autoFocus
Expand All @@ -62,6 +65,7 @@ export function TaskLongContentDialog({
{t("taskDetail.common.cancel")}
</Button>
<Button
ref={dialogNavigation.confirmRef}
type="button"
disabled={converting}
onClick={() => void onConfirm()}
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/console/task/task-terminal-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { apiRequest } from "@/utils/requestUtils"
import { IconAlertCircle, IconCloudOff, IconPlus, IconReload, IconTerminal2, IconX } from "@tabler/icons-react"
import { useCallback, useEffect, useState } from "react"
Expand Down Expand Up @@ -39,6 +40,7 @@ export function TaskTerminalPanel({ envid, disabled, onClosePanel }: TaskTermina
const [titles, setTitles] = useState<Record<string, string>>({})
const [closeDialogOpen, setCloseDialogOpen] = useState(false)
const [sessionToClose, setSessionToClose] = useState<string | null>(null)
const closeDialogNavigation = useDialogActionNavigation()

const fetchSessions = useCallback(async (): Promise<DomainTerminal[]> => {
if (!envid) return []
Expand Down Expand Up @@ -260,14 +262,14 @@ export function TaskTerminalPanel({ envid, disabled, onClosePanel }: TaskTermina
</div>

<AlertDialog open={closeDialogOpen} onOpenChange={setCloseDialogOpen}>
<AlertDialogContent>
<AlertDialogContent onKeyDown={closeDialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("taskDetail.terminal.closeTitle")}</AlertDialogTitle>
<AlertDialogDescription>{t("taskDetail.terminal.closeDescription")}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setCloseDialogOpen(false)}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction onClick={() => sessionToClose && handleDeleteSession(sessionToClose)}>
<AlertDialogCancel ref={closeDialogNavigation.cancelRef} onClick={() => setCloseDialogOpen(false)}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogAction ref={closeDialogNavigation.confirmRef} onClick={() => sessionToClose && handleDeleteSession(sessionToClose)}>
{t("taskDetail.terminal.confirmClose")}
</AlertDialogAction>
</AlertDialogFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { Dialog, DialogClose, DialogContent, DialogFooter } from "@/components/ui/dialog"
import { Spinner } from "@/components/ui/spinner"
import React from "react"
Expand Down Expand Up @@ -40,6 +41,7 @@ export function TaskWhiteboardDialog({
const [submitting, setSubmitting] = React.useState(false)
const [resetDialogOpen, setResetDialogOpen] = React.useState(false)
const editorRef = React.useRef<TldrawEditor | null>(null)
const resetDialogNavigation = useDialogActionNavigation()

const handleSubmit = React.useCallback(async () => {
if (submitting) return
Expand Down Expand Up @@ -159,16 +161,17 @@ export function TaskWhiteboardDialog({
setResetDialogOpen(nextOpen)
}}
>
<AlertDialogContent>
<AlertDialogContent onKeyDown={resetDialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("taskDetail.whiteboard.clearTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{t("taskDetail.whiteboard.clearDescription")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={submitting}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<AlertDialogCancel ref={resetDialogNavigation.cancelRef} disabled={submitting}>{t("taskDetail.common.cancel")}</AlertDialogCancel>
<Button
ref={resetDialogNavigation.confirmRef}
type="button"
variant="destructive"
disabled={submitting}
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/components/ui/dialog-action-navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from "react"

function useDialogActionNavigation() {
const cancelRef = React.useRef<HTMLButtonElement>(null)
const confirmRef = React.useRef<HTMLButtonElement>(null)
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
cancelRef.current?.focus()
} else if (event.key === "ArrowRight") {
event.preventDefault()
confirmRef.current?.focus()
}
}

return { cancelRef, confirmRef, onKeyDown }
}

export { useDialogActionNavigation }
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Badge } from "@/components/ui/badge"
import { useDialogActionNavigation } from "@/components/ui/dialog-action-navigation"
import { HoverCard, HoverCardTrigger } from "@/components/ui/hover-card"
import { Item, ItemContent, ItemDescription, ItemFooter, ItemHeader, ItemTitle } from "@/components/ui/item"
import { Spinner } from "@/components/ui/spinner"
Expand Down Expand Up @@ -52,6 +53,7 @@ export default function ProjectOverviewTasksTab({ projectId, refreshKey }: Proje
const [tasksInitialLoading, setTasksInitialLoading] = useState(true)
const [taskToDelete, setTaskToDelete] = useState<DomainProjectTask | null>(null)
const [deleting, setDeleting] = useState(false)
const deleteTaskDialogNavigation = useDialogActionNavigation()
const loadMoreRef = useRef<HTMLDivElement>(null)
const tasksLoadingRef = useRef(false)

Expand Down Expand Up @@ -286,16 +288,17 @@ export default function ProjectOverviewTasksTab({ projectId, refreshKey }: Proje
{tasksLoading && <Spinner className="size-6" />}
</div>
<AlertDialog open={!!taskToDelete} onOpenChange={(open) => !open && setTaskToDelete(null)}>
<AlertDialogContent>
<AlertDialogContent onKeyDown={deleteTaskDialogNavigation.onKeyDown}>
<AlertDialogHeader>
<AlertDialogTitle>{t("projectOverview.tasks.delete.title")}</AlertDialogTitle>
<AlertDialogDescription>
{t("projectOverview.tasks.delete.description", { task: getTaskDisplayName(taskToDelete) })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deleting}>{t("projectOverview.common.cancel")}</AlertDialogCancel>
<AlertDialogCancel ref={deleteTaskDialogNavigation.cancelRef} disabled={deleting}>{t("projectOverview.common.cancel")}</AlertDialogCancel>
<AlertDialogAction
ref={deleteTaskDialogNavigation.confirmRef}
onClick={(e) => {
e.preventDefault()
handleConfirmDeleteTask()
Expand Down
Loading
Loading