mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
big UI improvements
This commit is contained in:
parent
ff8c188d3a
commit
d926a771bc
3 changed files with 245 additions and 23 deletions
|
|
@ -156,11 +156,6 @@ export const ChatRowContent = ({
|
|||
})
|
||||
}, [message.ts, editedContent])
|
||||
|
||||
// Handle sending edited message (when Enter is pressed in ChatTextArea)
|
||||
const handleSendEdit = useCallback(() => {
|
||||
handleSaveEdit()
|
||||
}, [handleSaveEdit])
|
||||
|
||||
// Handle image selection for editing
|
||||
const handleSelectImages = useCallback(() => {
|
||||
vscode.postMessage({ type: "selectImages" })
|
||||
|
|
@ -1059,21 +1054,15 @@ export const ChatRowContent = ({
|
|||
placeholderText={t("chat:editMessage.placeholder")}
|
||||
selectedImages={editImages}
|
||||
setSelectedImages={setEditImages}
|
||||
onSend={handleSendEdit}
|
||||
onSend={handleSaveEdit}
|
||||
onSelectImages={handleSelectImages}
|
||||
shouldDisableImages={false}
|
||||
mode={editMode}
|
||||
setMode={setEditMode}
|
||||
modeShortcutText=""
|
||||
isEditMode={true}
|
||||
onCancel={handleCancelEdit}
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={handleCancelEdit}>
|
||||
{t("chat:cancel.title")}
|
||||
</Button>
|
||||
<Button variant="default" size="sm" onClick={handleSaveEdit}>
|
||||
{t("chat:save.title")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between">
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ interface ChatTextAreaProps {
|
|||
mode: Mode
|
||||
setMode: (value: Mode) => void
|
||||
modeShortcutText: string
|
||||
// Edit mode props
|
||||
isEditMode?: boolean
|
||||
onCancel?: () => void
|
||||
}
|
||||
|
||||
const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
|
|
@ -65,6 +68,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
mode,
|
||||
setMode,
|
||||
modeShortcutText,
|
||||
isEditMode = false,
|
||||
onCancel,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
|
|
@ -972,15 +977,27 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<IconButton
|
||||
iconClass="codicon-send"
|
||||
title={t("chat:sendMessage")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={onSend}
|
||||
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
|
||||
/>
|
||||
</div>
|
||||
{isEditMode ? (
|
||||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<IconButton
|
||||
iconClass="codicon-edit"
|
||||
title={t("chat:save")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={onSend}
|
||||
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute bottom-1 right-1 z-30">
|
||||
<IconButton
|
||||
iconClass="codicon-send"
|
||||
title={t("chat:sendMessage")}
|
||||
disabled={sendingDisabled}
|
||||
onClick={onSend}
|
||||
className="opacity-60 hover:opacity-100 text-vscode-descriptionForeground hover:text-vscode-foreground"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!inputValue && (
|
||||
<div
|
||||
|
|
@ -1145,6 +1162,16 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
</div>
|
||||
|
||||
<div className={cn("flex", "items-center", "gap-0.5", "shrink-0")}>
|
||||
{isEditMode && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={onCancel}
|
||||
disabled={sendingDisabled}
|
||||
className="mr-2 text-xs">
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<IndexingStatusDot />
|
||||
<IconButton
|
||||
iconClass="codicon-device-camera"
|
||||
|
|
|
|||
|
|
@ -856,4 +856,210 @@ describe("ChatTextArea", () => {
|
|||
expect(apiConfigDropdown).toHaveAttribute("disabled")
|
||||
})
|
||||
})
|
||||
describe("edit mode functionality", () => {
|
||||
it("should show edit button instead of send button when isEditMode is true", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
|
||||
|
||||
// Should show edit button (codicon-edit)
|
||||
const editButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-edit") !== null
|
||||
},
|
||||
})
|
||||
expect(editButton).toBeInTheDocument()
|
||||
|
||||
// Should not show send button (codicon-send)
|
||||
const sendButton = screen.queryByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-send") !== null
|
||||
},
|
||||
})
|
||||
expect(sendButton).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show send button when isEditMode is false", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={false} />)
|
||||
|
||||
// Should show send button (codicon-send)
|
||||
const sendButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-send") !== null
|
||||
},
|
||||
})
|
||||
expect(sendButton).toBeInTheDocument()
|
||||
|
||||
// Should not show edit button (codicon-edit)
|
||||
const editButton = screen.queryByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-edit") !== null
|
||||
},
|
||||
})
|
||||
expect(editButton).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should show cancel button in edit mode", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
|
||||
|
||||
// Should show cancel button with text "Cancel"
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i })
|
||||
expect(cancelButton).toBeInTheDocument()
|
||||
expect(cancelButton).toHaveTextContent("Cancel")
|
||||
})
|
||||
|
||||
it("should not show cancel button when not in edit mode", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={false} />)
|
||||
|
||||
// Should not show cancel button
|
||||
const cancelButton = screen.queryByRole("button", { name: /cancel/i })
|
||||
expect(cancelButton).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("should call onSend when edit button is clicked", () => {
|
||||
const onSend = vi.fn()
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} onSend={onSend} />)
|
||||
|
||||
const editButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-edit") !== null
|
||||
},
|
||||
})
|
||||
|
||||
fireEvent.click(editButton)
|
||||
expect(onSend).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should call onCancel when cancel button is clicked", () => {
|
||||
const onCancel = vi.fn()
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} onCancel={onCancel} />)
|
||||
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i })
|
||||
|
||||
fireEvent.click(cancelButton)
|
||||
expect(onCancel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should disable edit button when sendingDisabled is true", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} sendingDisabled={true} />)
|
||||
|
||||
const editButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-edit") !== null
|
||||
},
|
||||
})
|
||||
|
||||
expect(editButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it("should disable cancel button when sendingDisabled is true", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} sendingDisabled={true} />)
|
||||
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i })
|
||||
expect(cancelButton).toBeDisabled()
|
||||
})
|
||||
|
||||
it("should have correct tooltip for edit button", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
render(<ChatTextArea {...defaultProps} isEditMode={true} />)
|
||||
|
||||
const editButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-edit") !== null
|
||||
},
|
||||
})
|
||||
|
||||
// Check that the button has the correct aria-label (which is used for tooltip)
|
||||
expect(editButton).toHaveAttribute("aria-label", expect.stringMatching(/save/i))
|
||||
})
|
||||
|
||||
it("should position cancel button correctly relative to camera button", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
})
|
||||
|
||||
const { container } = render(<ChatTextArea {...defaultProps} isEditMode={true} />)
|
||||
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i })
|
||||
const cameraButton = screen.getByRole("button", {
|
||||
name: (_, element) => {
|
||||
return element.querySelector(".codicon-device-camera") !== null
|
||||
},
|
||||
})
|
||||
|
||||
// Both buttons should be in the same container (bottom toolbar)
|
||||
const bottomToolbar = container.querySelector(".flex.items-center.gap-0\\.5.shrink-0")
|
||||
expect(bottomToolbar).toContainElement(cancelButton)
|
||||
expect(bottomToolbar).toContainElement(cameraButton)
|
||||
|
||||
// Cancel button should come before camera button in DOM order
|
||||
const buttons = bottomToolbar?.querySelectorAll("button")
|
||||
const cancelIndex = Array.from(buttons || []).indexOf(cancelButton as HTMLButtonElement)
|
||||
const cameraIndex = Array.from(buttons || []).indexOf(cameraButton as HTMLButtonElement)
|
||||
expect(cancelIndex).toBeLessThan(cameraIndex)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue