diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3cf99c3..c82fb28 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,6 +6,7 @@ { "label": "watch", "dependsOn": [ + "npm: build:webview", "npm: watch:tsc", "npm: watch:esbuild" ], @@ -17,6 +18,18 @@ "isDefault": true } }, + { + "type": "npm", + "script": "build:webview", + "group": "build", + "problemMatcher": [], + "isBackground": true, + "label": "npm: build:webview", + "presentation": { + "group": "watch", + "reveal": "never" + } + }, { "type": "npm", "script": "watch:esbuild", diff --git a/package.json b/package.json index 9fb4290..66c4b8b 100644 --- a/package.json +++ b/package.json @@ -32,15 +32,25 @@ }, "commands": [ { - "command": "claude-dev.menuButtonTapped", - "title": "Text that will show when hovered", - "icon": "$(clear-all)" + "command": "claude-dev.plusButtonTapped", + "title": "New Task", + "icon": "$(add)" + }, + { + "command": "claude-dev.settingsButtonTapped", + "title": "Settings", + "icon": "$(settings-gear)" } ], "menus": { "view/title": [ { - "command": "claude-dev.menuButtonTapped", + "command": "claude-dev.plusButtonTapped", + "group": "navigation", + "when": "view == claude-dev.SidebarProvider" + }, + { + "command": "claude-dev.settingsButtonTapped", "group": "navigation", "when": "view == claude-dev.SidebarProvider" } diff --git a/src/extension.ts b/src/extension.ts index e1f1c6a..5470ec7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -34,8 +34,15 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.window.registerWebviewViewProvider(SidebarProvider.viewType, provider)) context.subscriptions.push( - vscode.commands.registerCommand("claude-dev.menuButtonTapped", () => { - const message = "claude-dev.menuButtonTapped!" + vscode.commands.registerCommand("claude-dev.plusButtonTapped", () => { + const message = "claude-dev.plusButtonTapped!" + vscode.window.showInformationMessage(message) + }) + ) + + context.subscriptions.push( + vscode.commands.registerCommand("claude-dev.settingsButtonTapped", () => { + const message = "claude-dev.settingsButtonTapped!" vscode.window.showInformationMessage(message) }) ) diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 47eab7d..d1aa14f 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -19,6 +19,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", + "react-textarea-autosize": "^8.5.3", "rewire": "^7.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -16142,6 +16143,23 @@ } } }, + "node_modules/react-textarea-autosize": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -18549,6 +18567,46 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 4859a56..66eddc2 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -14,6 +14,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", + "react-textarea-autosize": "^8.5.3", "rewire": "^7.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index b856788..f81fd30 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,8 +1,7 @@ -import React from "react" +import React, { useState } from "react" import logo from "./logo.svg" import "./App.css" - import { vscode } from "./utilities/vscode" import { VSCodeBadge, @@ -26,8 +25,12 @@ import { VSCodeTextField, } from "@vscode/webview-ui-toolkit/react" import ChatSidebar from "./components/ChatSidebar" +import Demo from "./components/Demo" +import SettingsView from "./components/SettingsView" const App: React.FC = () => { + const [showSettings, setShowSettings] = useState(true) + const handleHowdyClick = () => { vscode.postMessage({ command: "hello", @@ -35,12 +38,7 @@ const App: React.FC = () => { }) } - return ( - // REMOVE COLOR -
- -
- ) + return <>{showSettings ? : } } export default App diff --git a/webview-ui/src/components/ChatSidebar.tsx b/webview-ui/src/components/ChatSidebar.tsx index d3ae701..d60fb0a 100644 --- a/webview-ui/src/components/ChatSidebar.tsx +++ b/webview-ui/src/components/ChatSidebar.tsx @@ -1,7 +1,7 @@ -import React, { useState, useRef, useEffect, useCallback } from "react" +import React, { useState, useRef, useEffect, useCallback, KeyboardEvent } from "react" import { VSCodeButton, VSCodeTextArea, VSCodeDivider, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { vscode } from "../utilities/vscode" -import ResizingTextArea from "./ResizingTextArea" +import DynamicTextArea from "react-textarea-autosize" interface Message { id: number @@ -13,9 +13,12 @@ const ChatSidebar = () => { const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState("") const messagesEndRef = useRef(null) + const textAreaRef = useRef(null) + const [textAreaHeight, setTextAreaHeight] = useState(undefined) const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) + // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move + messagesEndRef.current?.scrollIntoView({ behavior: "smooth", block: 'nearest', inline: 'start' }) } useEffect(scrollToBottom, [messages]) @@ -29,10 +32,6 @@ const ChatSidebar = () => { } setMessages([...messages, newMessage]) setInputValue("") - // if (textAreaRef.current) { - // textAreaRef.current.style.height = "auto" - // } - // Here you would typically send the message to your extension's backend vscode.postMessage({ command: "sendMessage", @@ -40,14 +39,25 @@ const ChatSidebar = () => { }) } } + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault() + handleSendMessage() + } + } + + useEffect(() => { + if (textAreaRef.current && !textAreaHeight) { + setTextAreaHeight(textAreaRef.current.offsetHeight) + } + }, []) return ( -
-
+
+
{messages.map((message) => (
{ ? "var(--vscode-editor-background)" : "var(--vscode-sideBar-background)", }}> - {message.text} + {message.text}
))} -
+
- -
- + setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + onHeightChange={() => scrollToBottom()} placeholder="Type a message..." - style={{ marginBottom: "10px", width: "100%" }} + maxRows={10} + style={{ + width: "100%", + boxSizing: "border-box", + backgroundColor: "var(--vscode-input-background, #3c3c3c)", + color: "var(--vscode-input-foreground, #cccccc)", + border: "1px solid var(--vscode-input-border, #3c3c3c)", + borderRadius: "2px", + fontFamily: + "var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif)", + fontSize: "var(--vscode-editor-font-size, 13px)", + lineHeight: "var(--vscode-editor-line-height, 1.5)", + resize: "none", + overflow: "hidden", + paddingTop: "8px", + paddingBottom: "8px", + paddingLeft: "8px", + paddingRight: "40px", // Make room for button + }} /> - Send - -
- - + {textAreaHeight && ( +
+ + - - - - - - -
-
- - Send +
+ )}
) diff --git a/webview-ui/src/components/ResizingTextArea.tsx b/webview-ui/src/components/ResizingTextArea.tsx deleted file mode 100644 index 183d3b7..0000000 --- a/webview-ui/src/components/ResizingTextArea.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { TextareaHTMLAttributes, CSSProperties, useRef, useEffect } from "react" - -interface ResizingTextAreaProps extends Omit, "onChange"> { - onChange: (value: string) => void -} - -const ResizingTextArea= ({ style, value, onChange, ...props }: ResizingTextAreaProps) => { - const textAreaRef = useRef(null) - - const textareaStyle: CSSProperties = { - width: "100%", - minHeight: "60px", - backgroundColor: "var(--vscode-input-background, #3c3c3c)", - color: "var(--vscode-input-foreground, #cccccc)", - border: "1px solid var(--vscode-input-border, #3c3c3c)", - borderRadius: "2px", - padding: "4px 8px", - outline: "none", - fontFamily: "var(--vscode-editor-font-family)", - fontSize: "var(--vscode-editor-font-size, 13px)", - lineHeight: "var(--vscode-editor-line-height, 1.5)", - resize: "none", - overflow: "hidden", - ...style, - } - - const adjustTextAreaHeight = () => { - if (textAreaRef.current) { - textAreaRef.current.style.height = "auto" - textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px` - } - } - - const handleInputChange = (event: React.ChangeEvent) => { - onChange(event.target.value) - adjustTextAreaHeight() - } - - useEffect(() => { - adjustTextAreaHeight() - }, [value]) - - return