From f53768612b1e2268697b5444e502032ef9f3fb3c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 23 May 2023 23:45:12 -0400 Subject: copying from old repo --- extension/react-app/src/components/CodeBlock.tsx | 57 +++++ .../react-app/src/components/CodeMultiselect.tsx | 276 +++++++++++++++++++++ .../react-app/src/components/ContinueButton.tsx | 37 +++ extension/react-app/src/components/DebugPanel.tsx | 121 +++++++++ .../src/components/IterationContainer.tsx | 77 ++++++ .../react-app/src/components/StepContainer.tsx | 208 ++++++++++++++++ .../react-app/src/components/SubContainer.tsx | 24 ++ .../react-app/src/components/VSCodeFileLink.tsx | 17 ++ extension/react-app/src/components/index.ts | 136 ++++++++++ 9 files changed, 953 insertions(+) create mode 100644 extension/react-app/src/components/CodeBlock.tsx create mode 100644 extension/react-app/src/components/CodeMultiselect.tsx create mode 100644 extension/react-app/src/components/ContinueButton.tsx create mode 100644 extension/react-app/src/components/DebugPanel.tsx create mode 100644 extension/react-app/src/components/IterationContainer.tsx create mode 100644 extension/react-app/src/components/StepContainer.tsx create mode 100644 extension/react-app/src/components/SubContainer.tsx create mode 100644 extension/react-app/src/components/VSCodeFileLink.tsx create mode 100644 extension/react-app/src/components/index.ts (limited to 'extension/react-app/src/components') diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx new file mode 100644 index 00000000..4c10ab23 --- /dev/null +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -0,0 +1,57 @@ +import hljs from "highlight.js"; +import { useEffect } from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, vscBackground } from "."; + +import { Clipboard } from "@styled-icons/heroicons-outline"; + +const StyledPre = styled.pre` + overflow: scroll; + border: 1px solid gray; + border-radius: ${defaultBorderRadius}; + background-color: ${vscBackground}; +`; + +const StyledCode = styled.code` + background-color: ${vscBackground}; +`; + +const StyledCopyButton = styled.button` + float: right; + border: none; + background-color: ${vscBackground}; + cursor: pointer; + padding: 0; + margin: 4px; + &:hover { + color: #fff; + } +`; + +function CopyButton(props: { textToCopy: string }) { + return ( + <> + { + navigator.clipboard.writeText(props.textToCopy); + }} + > + + + + ); +} + +function CodeBlock(props: { language?: string; children: string }) { + useEffect(() => { + hljs.highlightAll(); + }, [props.children]); + return ( + + + {props.children} + + ); +} + +export default CodeBlock; diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx new file mode 100644 index 00000000..626ae42f --- /dev/null +++ b/extension/react-app/src/components/CodeMultiselect.tsx @@ -0,0 +1,276 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { Button, buttonColor, defaultBorderRadius, secondaryDark } from "."; +import { useSelector } from "react-redux"; +import { + selectDebugContext, + selectAllRangesInFiles, + selectRangesMask, +} from "../redux/selectors/debugContextSelectors"; +import "../highlight/dark.min.css"; +import hljs from "highlight.js"; +import { postVscMessage } from "../vscode"; +import { RootStore } from "../redux/store"; +import { useDispatch } from "react-redux"; +import { + addRangeInFile, + deleteRangeInFileAt, + toggleSelectionAt, + updateFileSystem, +} from "../redux/slices/debugContexSlice"; +import { RangeInFile } from "../../../src/client"; +import { readRangeInVirtualFileSystem } from "../util"; + +//#region Styled Components + +const MultiSelectContainer = styled.div` + border-radius: ${defaultBorderRadius}; + padding: 4px; + display: flex; + flex-direction: column; + gap: 4px; +`; + +const MultiSelectHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: left; + border-bottom: 1px solid gray; + padding-left: 4px; + padding-right: 4px; + & p { + overflow-wrap: break-word; + word-wrap: break-word; + -ms-wrap-flow: break-word; + overflow: hidden; + } +`; + +const MultiSelectOption = styled.div` + border-radius: ${defaultBorderRadius}; + padding-top: 4px; + cursor: pointer; + background-color: ${secondaryDark}; +`; + +const DeleteSelectedRangeButton = styled(Button)` + align-self: right; + padding: 0px; + margin-top: 0; + aspect-ratio: 1/1; + height: 28px; +`; + +const ToggleHighlightButton = styled(Button)` + display: grid; + justify-content: center; + align-items: center; + grid-template-columns: 30px 1fr; + margin-left: 20px; + order: 1; + width: fit-content; +`; + +//#endregion + +//#region Path Formatting + +const filenameToLanguageMap: any = { + py: "python", + js: "javascript", + ts: "typescript", + html: "html", + css: "css", + java: "java", + c: "c", + cpp: "cpp", + cs: "csharp", + go: "go", + rb: "ruby", + rs: "rust", + swift: "swift", + php: "php", + scala: "scala", + kt: "kotlin", + dart: "dart", + hs: "haskell", + lua: "lua", + pl: "perl", + r: "r", + sql: "sql", + vb: "vb", + xml: "xml", + yaml: "yaml", +}; + +function filenameToLanguage(filename: string): string { + const extension = filename.split(".").pop(); + if (extension === undefined) { + return ""; + } + return filenameToLanguageMap[extension] || ""; +} + +function formatPathRelativeToWorkspace( + path: string, + workspacePath: string | undefined +) { + if (workspacePath === undefined) { + return path; + } + if (path.startsWith(workspacePath)) { + return path.substring(workspacePath.length + 1); + } else { + return path; + } +} + +function formatFileRange( + rangeInFile: RangeInFile, + workspacePath: string | undefined +) { + return `${formatPathRelativeToWorkspace( + rangeInFile.filepath, + workspacePath + )} (lines ${rangeInFile.range.start.line + 1}-${ + rangeInFile.range.end.line + 1 + })`; + // +1 because VSCode Ranges are 0-indexed +} + +//#endregion + +function CodeMultiselect(props: {}) { + // State + const [highlightLocked, setHighlightLocked] = useState(true); + + // Redux + const dispatch = useDispatch(); + const workspacePath = useSelector( + (state: RootStore) => state.config.workspacePath + ); + const debugContext = useSelector(selectDebugContext); + const rangesInFiles = useSelector(selectAllRangesInFiles); + const rangesInFilesMask = useSelector(selectRangesMask); + + useEffect(() => { + let eventListener = (event: any) => { + switch (event.data.type) { + case "highlightedCode": + if (!highlightLocked) { + dispatch( + addRangeInFile({ + rangeInFile: event.data.rangeInFile, + canUpdateLast: true, + }) + ); + dispatch(updateFileSystem(event.data.filesystem)); + } + break; + case "findSuspiciousCode": + for (let c of event.data.codeLocations) { + dispatch(addRangeInFile({ rangeInFile: c, canUpdateLast: false })); + } + dispatch(updateFileSystem(event.data.filesystem)); + postVscMessage("listTenThings", { debugContext }); + break; + } + }; + window.addEventListener("message", eventListener); + return () => window.removeEventListener("message", eventListener); + }, [debugContext, highlightLocked]); + + useEffect(() => { + hljs.highlightAll(); + }, [rangesInFiles]); + + return ( + + {rangesInFiles.map((range: RangeInFile, index: number) => { + return ( + { + dispatch(toggleSelectionAt(index)); + }} + > + +

+ {formatFileRange(range, workspacePath)} +

+ dispatch(deleteRangeInFileAt(index))} + > + x + +
+
+              
+                {readRangeInVirtualFileSystem(range, debugContext.filesystem)}
+              
+            
+
+ ); + })} + {rangesInFiles.length === 0 && ( + <> +

Highlight relevant code in the editor.

+ + )} + { + setHighlightLocked(!highlightLocked); + }} + > + {highlightLocked ? ( + <> + + + {" "} + Enable Highlight + + ) : ( + <> + + + {" "} + Disable Highlight + + )} + +
+ ); +} + +export default CodeMultiselect; diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx new file mode 100644 index 00000000..11dc7a92 --- /dev/null +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -0,0 +1,37 @@ +import styled, { keyframes } from "styled-components"; +import { Button } from "."; +import { Play } from "@styled-icons/heroicons-outline"; + +let StyledButton = styled(Button)` + margin: auto; + display: grid; + grid-template-columns: 30px 1fr; + align-items: center; + background: linear-gradient( + 95.23deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + + &:hover { + transition-delay: 0.5s; + transition-property: background; + background: linear-gradient( + 45deg, + #be1a55 14.44%, + rgba(203, 27, 90, 0.4) 82.21% + ); + } +`; + +function ContinueButton(props: { onClick?: () => void }) { + return ( + + + {/* */} + Continue + + ); +} + +export default ContinueButton; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx new file mode 100644 index 00000000..ed00571b --- /dev/null +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -0,0 +1,121 @@ +import React, { useEffect, useState } from "react"; +import styled from "styled-components"; +import { postVscMessage } from "../vscode"; +import { useDispatch } from "react-redux"; +import { + setApiUrl, + setVscMachineId, + setSessionId, +} from "../redux/slices/configSlice"; +import { setHighlightedCode } from "../redux/slices/miscSlice"; +import { updateFileSystem } from "../redux/slices/debugContexSlice"; +import { buttonColor, defaultBorderRadius, vscBackground } from "."; +interface DebugPanelProps { + tabs: { + element: React.ReactElement; + title: string; + }[]; +} + +const GradientContainer = styled.div` + // Uncomment to get gradient border + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); + /* padding: 10px; */ + margin: 0; + height: 100%; + /* border: 1px solid white; */ + border-radius: ${defaultBorderRadius}; +`; + +const MainDiv = styled.div` + height: 100%; + border-radius: ${defaultBorderRadius}; + overflow: scroll; + scrollbar-base-color: transparent; + /* background: ${vscBackground}; */ + background-color: #1e1e1ede; +`; + +const TabBar = styled.div<{ numTabs: number }>` + display: grid; + grid-template-columns: repeat(${(props) => props.numTabs}, 1fr); +`; + +const TabsAndBodyDiv = styled.div` + display: grid; + grid-template-rows: auto 1fr; + height: 100%; +`; + +function DebugPanel(props: DebugPanelProps) { + const dispatch = useDispatch(); + useEffect(() => { + const eventListener = (event: any) => { + switch (event.data.type) { + case "onLoad": + dispatch(setApiUrl(event.data.apiUrl)); + dispatch(setVscMachineId(event.data.vscMachineId)); + dispatch(setSessionId(event.data.sessionId)); + break; + case "highlightedCode": + dispatch(setHighlightedCode(event.data.rangeInFile)); + dispatch(updateFileSystem(event.data.filesystem)); + break; + } + }; + window.addEventListener("message", eventListener); + postVscMessage("onLoad", {}); + return () => window.removeEventListener("message", eventListener); + }, []); + + const [currentTab, setCurrentTab] = useState(0); + + return ( + + + + {props.tabs.length > 1 && ( + + {props.tabs.map((tab, index) => { + return ( +
setCurrentTab(index)} + > + {tab.title} +
+ ); + })} +
+ )} + {props.tabs.map((tab, index) => { + return ( + + ); + })} +
+
+
+ ); +} + +export default DebugPanel; diff --git a/extension/react-app/src/components/IterationContainer.tsx b/extension/react-app/src/components/IterationContainer.tsx new file mode 100644 index 00000000..a0053519 --- /dev/null +++ b/extension/react-app/src/components/IterationContainer.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import styled from "styled-components"; +import { + defaultBorderRadius, + MainContainerWithBorder, + secondaryDark, + vscBackground, +} from "."; +import { RangeInFile, FileEdit } from "../../../src/client"; +import CodeBlock from "./CodeBlock"; +import SubContainer from "./SubContainer"; + +import { ChevronDown, ChevronRight } from "@styled-icons/heroicons-outline"; + +export interface IterationContext { + codeSelections: RangeInFile[]; + instruction: string; + suggestedChanges: FileEdit[]; + status: "waiting" | "accepted" | "rejected"; + summary?: string; + action: string; + error?: string; +} + +interface IterationContainerProps { + iterationContext: IterationContext; +} + +const IterationContainerDiv = styled.div<{ open: boolean }>` + background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; + border-radius: ${defaultBorderRadius}; + padding: ${(props) => (props.open ? "2px" : "8px")}; +`; + +function IterationContainer(props: IterationContainerProps) { + const [open, setOpen] = useState(false); + + return ( + + +

setOpen((prev) => !prev)} + > + {open ? : } + {props.iterationContext.summary || + props.iterationContext.codeSelections + .map((cs) => cs.filepath) + .join("\n")} +

+ + {open && ( + <> + + {props.iterationContext.action} + + {props.iterationContext.error && ( + + {props.iterationContext.error} + + )} + {props.iterationContext.suggestedChanges.map((sc) => { + return ( + + {sc.filepath} + {sc.replacement} + + ); + })} + + )} +
+
+ ); +} + +export default IterationContainer; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx new file mode 100644 index 00000000..03649b66 --- /dev/null +++ b/extension/react-app/src/components/StepContainer.tsx @@ -0,0 +1,208 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import styled, { keyframes } from "styled-components"; +import { + appear, + buttonColor, + defaultBorderRadius, + MainContainerWithBorder, + MainTextInput, + secondaryDark, + vscBackground, + GradientBorder, +} from "."; +import { RangeInFile, FileEdit } from "../../../src/client"; +import CodeBlock from "./CodeBlock"; +import SubContainer from "./SubContainer"; + +import { + ChevronDown, + ChevronRight, + Backward, +} from "@styled-icons/heroicons-outline"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import ReactMarkdown from "react-markdown"; +import ContinueButton from "./ContinueButton"; + +interface StepContainerProps { + historyNode: HistoryNode; + onReverse: () => void; + inFuture: boolean; + onRefinement: (input: string) => void; + onUserInput: (input: string) => void; +} + +const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` + opacity: ${(props) => (props.inFuture ? 0.3 : 1)}; + animation: ${appear} 0.3s ease-in-out; + /* padding-left: ${(props) => props.stepDepth * 20}px; */ + overflow: hidden; +`; + +const StepContainerDiv = styled.div<{ open: boolean }>` + background-color: ${(props) => (props.open ? vscBackground : secondaryDark)}; + border-radius: ${defaultBorderRadius}; + padding: 8px; +`; + +const HeaderDiv = styled.div` + display: grid; + grid-template-columns: 1fr auto; + align-items: center; +`; + +const HeaderButton = styled.button` + background-color: transparent; + border: 1px solid white; + border-radius: ${defaultBorderRadius}; + padding: 2px; + cursor: pointer; + color: white; + + &:hover { + background-color: white; + color: black; + } +`; + +const OnHoverDiv = styled.div` + text-align: center; + padding: 10px; + animation: ${appear} 0.3s ease-in-out; +`; + +const NaturalLanguageInput = styled(MainTextInput)` + width: 80%; +`; + +function StepContainer(props: StepContainerProps) { + const [open, setOpen] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const naturalLanguageInputRef = useRef(null); + + useEffect(() => { + if (isHovered) { + naturalLanguageInputRef.current?.focus(); + } + }, [isHovered]); + + const onTextInput = useCallback(() => { + if (naturalLanguageInputRef.current) { + props.onRefinement(naturalLanguageInputRef.current.value); + naturalLanguageInputRef.current.value = ""; + } + }, [naturalLanguageInputRef]); + + return ( + { + setIsHovered(true); + }} + onMouseLeave={() => { + setIsHovered(false); + }} + hidden={props.historyNode.step.hide as any} + > + setOpen((prev) => !prev)} + > + + +

+ {open ? ( + + ) : ( + + )} + {props.historyNode.step.name as any}: +

+ { + e.stopPropagation(); + props.onReverse(); + }} + > + + +
+ + + {props.historyNode.step.description as any} + + + {props.historyNode.step.name === "Waiting for user input" && ( + { + if (e.key === "Enter") { + props.onUserInput(e.currentTarget.value); + } + }} + type="text" + onSubmit={(ev) => { + props.onUserInput(ev.currentTarget.value); + }} + /> + )} + {props.historyNode.step.name === "Waiting for user confirmation" && ( + <> + + { + props.onUserInput("ok"); + e.preventDefault(); + e.stopPropagation(); + }} + type="button" + value="Confirm" + /> + + )} + + {open && ( + <> + {/* {props.historyNode.observation && ( + + Error Here + + )} */} + {/* {props.iterationContext.suggestedChanges.map((sc) => { + return ( + + {sc.filepath} + {sc.replacement} + + ); + })} */} + + )} +
+
+ + +
+ ); +} + +export default StepContainer; diff --git a/extension/react-app/src/components/SubContainer.tsx b/extension/react-app/src/components/SubContainer.tsx new file mode 100644 index 00000000..87e2094c --- /dev/null +++ b/extension/react-app/src/components/SubContainer.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import styled from "styled-components"; +import { defaultBorderRadius, secondaryDark, appear } from "."; + +const SubContainerDiv = styled.div` + margin: 4px; + padding: 8px; + border-radius: ${defaultBorderRadius}; + background-color: ${secondaryDark}; + + animation: ${appear} 0.3s ease-in-out; +`; + +function SubContainer(props: { children: React.ReactNode; title: string }) { + return ( + + {props.title} +

+ {props.children} +
+ ); +} + +export default SubContainer; diff --git a/extension/react-app/src/components/VSCodeFileLink.tsx b/extension/react-app/src/components/VSCodeFileLink.tsx new file mode 100644 index 00000000..6219654d --- /dev/null +++ b/extension/react-app/src/components/VSCodeFileLink.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { postVscMessage } from "../vscode"; + +function VSCodeFileLink(props: { path: string; text?: string }) { + return ( + { + postVscMessage("openFile", { path: props.path }); + }} + > + {props.text || props.path} + + ); +} + +export default VSCodeFileLink; diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts new file mode 100644 index 00000000..e37c97f3 --- /dev/null +++ b/extension/react-app/src/components/index.ts @@ -0,0 +1,136 @@ +import styled, { keyframes } from "styled-components"; + +export const defaultBorderRadius = "5px"; +export const secondaryDark = "rgb(37 37 38)"; +export const vscBackground = "rgb(30 30 30)"; +export const buttonColor = "rgb(113 28 59)"; +export const buttonColorHover = "rgb(113 28 59 0.67)"; + +export const Button = styled.button` + padding: 10px 12px; + margin: 8px 0; + border-radius: ${defaultBorderRadius}; + cursor: pointer; + + border: none; + color: white; + background-color: ${buttonColor}; + + &:disabled { + color: gray; + } + + &:hover:enabled { + background-color: ${buttonColorHover}; + } +`; + +export const TextArea = styled.textarea` + width: 100%; + border-radius: ${defaultBorderRadius}; + border: none; + background-color: ${secondaryDark}; + resize: vertical; + + padding: 4px; + caret-color: white; + color: white; + + &:focus { + outline: 1px solid ${buttonColor}; + } +`; + +export const Pre = styled.pre` + border-radius: ${defaultBorderRadius}; + padding: 8px; + max-height: 150px; + overflow: scroll; + margin: 0; + background-color: ${secondaryDark}; + border: none; + + /* text wrapping */ + white-space: pre-wrap; /* Since CSS 2.1 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +`; + +export const H3 = styled.h3` + background-color: ${secondaryDark}; + border-radius: ${defaultBorderRadius}; + padding: 4px 8px; + width: fit-content; +`; + +export const TextInput = styled.input.attrs({ type: "text" })` + width: 100%; + padding: 12px 20px; + margin: 8px 0; + box-sizing: border-box; + border-radius: ${defaultBorderRadius}; + border: 2px solid gray; +`; + +const spin = keyframes` + from { + -webkit-transform: rotate(0deg); + } + to { + -webkit-transform: rotate(360deg); + } +`; + +export const Loader = styled.div` + border: 4px solid transparent; + border-radius: 50%; + border-top: 4px solid white; + width: 36px; + height: 36px; + -webkit-animation: ${spin} 1s ease-in-out infinite; + animation: ${spin} 1s ease-in-out infinite; + margin: auto; +`; + +export const GradientBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background: linear-gradient( + 101.79deg, + #12887a 0%, + #87245c 37.64%, + #e12637 65.98%, + #ffb215 110.45% + ); +`; + +export const MainContainerWithBorder = styled.div<{ borderWidth?: string }>` + border-radius: ${defaultBorderRadius}; + padding: ${(props) => props.borderWidth || "1px"}; + background-color: white; +`; + +export const MainTextInput = styled.textarea` + padding: 8px; + font-size: 16px; + border-radius: ${defaultBorderRadius}; + border: 1px solid #ccc; + margin: 8px 8px; + background-color: ${vscBackground}; + color: white; + outline: 1px solid orange; + resize: none; +`; + +export const appear = keyframes` + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0px); + } +`; -- cgit v1.2.3-70-g09d2