diff options
author | Nate Sesti <sestinj@gmail.com> | 2023-07-19 00:33:50 -0700 |
---|---|---|
committer | Nate Sesti <sestinj@gmail.com> | 2023-07-19 00:33:50 -0700 |
commit | 1b92180d4b7720bf1cf36dd63142760d421dabf8 (patch) | |
tree | 26e25e005b06526267c2a140c1fbf1cbf822f066 /extension/react-app/src/components | |
parent | 924a0c09259d25a4dfe62c0a626a9204df45daa9 (diff) | |
parent | a7c57e1d1e4a0eff3e4b598f8bf0448ea6068353 (diff) | |
download | sncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.tar.gz sncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.tar.bz2 sncontinue-1b92180d4b7720bf1cf36dd63142760d421dabf8.zip |
Merge branch 'main' into config-py
Diffstat (limited to 'extension/react-app/src/components')
-rw-r--r-- | extension/react-app/src/components/CodeMultiselect.tsx | 276 | ||||
-rw-r--r-- | extension/react-app/src/components/ComboBox.tsx | 135 | ||||
-rw-r--r-- | extension/react-app/src/components/InputAndButton.tsx | 10 | ||||
-rw-r--r-- | extension/react-app/src/components/LoadingCover.tsx | 50 | ||||
-rw-r--r-- | extension/react-app/src/components/Onboarding.tsx | 136 | ||||
-rw-r--r-- | extension/react-app/src/components/PillButton.tsx | 170 | ||||
-rw-r--r-- | extension/react-app/src/components/StepContainer.tsx | 116 | ||||
-rw-r--r-- | extension/react-app/src/components/TextDialog.tsx | 21 | ||||
-rw-r--r-- | extension/react-app/src/components/index.ts | 23 |
9 files changed, 405 insertions, 532 deletions
diff --git a/extension/react-app/src/components/CodeMultiselect.tsx b/extension/react-app/src/components/CodeMultiselect.tsx deleted file mode 100644 index c0ab9400..00000000 --- a/extension/react-app/src/components/CodeMultiselect.tsx +++ /dev/null @@ -1,276 +0,0 @@ -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 VS Code 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 ( - <MultiSelectContainer> - {rangesInFiles.map((range: RangeInFile, index: number) => { - return ( - <MultiSelectOption - key={index} - style={{ - border: `1px solid ${ - rangesInFilesMask[index] ? buttonColor : "gray" - }`, - }} - onClick={() => { - dispatch(toggleSelectionAt(index)); - }} - > - <MultiSelectHeader> - <p style={{ margin: "4px" }}> - {formatFileRange(range, workspacePath)} - </p> - <DeleteSelectedRangeButton - onClick={() => dispatch(deleteRangeInFileAt(index))} - > - x - </DeleteSelectedRangeButton> - </MultiSelectHeader> - <pre> - <code - className={"language-" + filenameToLanguage(range.filepath)} - > - {readRangeInVirtualFileSystem(range, debugContext.filesystem)} - </code> - </pre> - </MultiSelectOption> - ); - })} - {rangesInFiles.length === 0 && ( - <> - <p>Highlight relevant code in the editor.</p> - </> - )} - <ToggleHighlightButton - onClick={() => { - setHighlightLocked(!highlightLocked); - }} - > - {highlightLocked ? ( - <> - <svg - xmlns="http://www.w3.org/2000/svg" - width="20px" - fill="none" - viewBox="0 0 24 24" - strokeWidth="1.5" - stroke="currentColor" - className="w-6 h-6" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" - /> - </svg>{" "} - Enable Highlight - </> - ) : ( - <> - <svg - xmlns="http://www.w3.org/2000/svg" - width="20px" - fill="none" - viewBox="0 0 24 24" - strokeWidth="1.5" - stroke="currentColor" - className="w-6 h-6" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M13.5 10.5V6.75a4.5 4.5 0 119 0v3.75M3.75 21.75h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H3.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" - /> - </svg>{" "} - Disable Highlight - </> - )} - </ToggleHighlightButton> - </MultiSelectContainer> - ); -} - -export default CodeMultiselect; diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index e6632360..f327e3a3 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,30 +1,20 @@ -import React, { - useCallback, - useEffect, - useImperativeHandle, - useState, -} from "react"; +import React, { useEffect, useImperativeHandle, useState } from "react"; import { useCombobox } from "downshift"; import styled from "styled-components"; import { - buttonColor, defaultBorderRadius, lightGray, secondaryDark, vscBackground, + vscForeground, } from "."; import CodeBlock from "./CodeBlock"; -import { RangeInFile } from "../../../src/client"; import PillButton from "./PillButton"; import HeaderButtonWithText from "./HeaderButtonWithText"; -import { - Trash, - LockClosed, - LockOpen, - Plus, - DocumentPlus, -} from "@styled-icons/heroicons-outline"; +import { DocumentPlus } from "@styled-icons/heroicons-outline"; import { HighlightedRangeContext } from "../../../schema/FullState"; +import { postVscMessage } from "../vscode"; +import { getMetaKeyLabel } from "../util"; // #region styled components const mainInputFontSize = 13; @@ -48,21 +38,6 @@ const EmptyPillDiv = styled.div` } `; -const ContextDropdown = styled.div` - position: absolute; - padding: 4px; - width: calc(100% - 16px - 8px); - background-color: ${secondaryDark}; - color: white; - border-bottom-right-radius: ${defaultBorderRadius}; - border-bottom-left-radius: ${defaultBorderRadius}; - /* border: 1px solid white; */ - border-top: none; - margin: 8px; - outline: 1px solid orange; - z-index: 5; -`; - const MainTextInput = styled.textarea` resize: none; @@ -74,7 +49,7 @@ const MainTextInput = styled.textarea` height: auto; width: 100%; background-color: ${secondaryDark}; - color: white; + color: ${vscForeground}; z-index: 1; border: 1px solid transparent; @@ -97,14 +72,15 @@ const Ul = styled.ul<{ position: absolute; background: ${vscBackground}; background-color: ${secondaryDark}; - color: white; + color: ${vscForeground}; max-height: ${UlMaxHeight}px; + width: calc(100% - 16px); overflow-y: scroll; overflow-x: hidden; padding: 0; ${({ hidden }) => hidden && "display: none;"} border-radius: ${defaultBorderRadius}; - border: 0.5px solid gray; + outline: 0.5px solid gray; z-index: 2; // Get rid of scrollbar and its padding scrollbar-width: none; @@ -120,6 +96,7 @@ const Li = styled.li<{ selected: boolean; isLastItem: boolean; }>` + background-color: ${secondaryDark}; ${({ highlighted }) => highlighted && "background: #ff000066;"} ${({ selected }) => selected && "font-weight: bold;"} padding: 0.5rem 0.75rem; @@ -149,10 +126,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { // The position of the current command you are typing now, so the one that will be appended to history once you press enter const [positionInHistory, setPositionInHistory] = React.useState<number>(0); const [items, setItems] = React.useState(props.items); - const [hoveringButton, setHoveringButton] = React.useState(false); - const [hoveringContextDropdown, setHoveringContextDropdown] = - React.useState(false); - const [pinned, setPinned] = useState(false); const [highlightedCodeSections, setHighlightedCodeSections] = React.useState( props.highlightedCodeSections || [] ); @@ -180,6 +153,27 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { useImperativeHandle(ref, () => downshiftProps, [downshiftProps]); + const [metaKeyPressed, setMetaKeyPressed] = useState(false); + const [focused, setFocused] = useState(false); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(true); + } + }; + const handleKeyUp = (e: KeyboardEvent) => { + if (e.key === "Meta") { + setMetaKeyPressed(false); + } + }; + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }); + useEffect(() => { if (!inputRef.current) { return; @@ -221,11 +215,19 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { )} */} {highlightedCodeSections.map((section, idx) => ( <PillButton + warning={ + section.range.contents.length > 4000 && section.editing + ? "Editing such a large range may be slow" + : undefined + } + onlyShowDelete={ + highlightedCodeSections.length <= 1 || section.editing + } editing={section.editing} pinned={section.pinned} index={idx} - key={`${section.filepath}${idx}`} - title={`${section.range.filepath} (${ + key={`${section.display_name}${idx}`} + title={`${section.display_name} (${ section.range.range.start.line + 1 }-${section.range.range.end.line + 1})`} onDelete={() => { @@ -238,15 +240,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { return newSections; }); }} - onHover={(val: boolean) => { - if (val) { - setHoveringButton(val); - } else { - setTimeout(() => { - setHoveringButton(val); - }, 100); - } - }} /> ))} {props.highlightedCodeSections.length > 0 && @@ -256,11 +249,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { props.onToggleAddContext(); }} > - Highlight to Add Context + Highlight code section </EmptyPillDiv> ) : ( <HeaderButtonWithText - text="Add to Context" + text="Add more code to context" onClick={() => { props.onToggleAddContext(); }} @@ -272,7 +265,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { <div className="flex px-2" ref={divRef} hidden={!downshiftProps.isOpen}> <MainTextInput disabled={props.disabled} - placeholder="Ask a question, give instructions, or type '/' to see slash commands" + placeholder={`Ask a question, give instructions, or type '/' to see slash commands`} {...getInputProps({ onChange: (e) => { const target = e.target as HTMLTextAreaElement; @@ -285,6 +278,13 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { // setShowContextDropdown(target.value.endsWith("@")); }, + onFocus: (e) => { + setFocused(true); + }, + onBlur: (e) => { + setFocused(false); + postVscMessage("blurContinueInput", {}); + }, onKeyDown: (event) => { if (event.key === "Enter" && event.shiftKey) { // Prevent Downshift's default 'Enter' behavior. @@ -311,7 +311,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { ) { (event.nativeEvent as any).preventDownshiftDefault = true; } else if (event.key === "ArrowUp") { - console.log("OWJFOIJO"); if (positionInHistory == 0) return; else if ( positionInHistory == history.length && @@ -340,6 +339,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { })} showAbove={showAbove()} ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0} + hidden={!downshiftProps.isOpen || items.length === 0} > {downshiftProps.isOpen && items.map((item, index) => ( @@ -357,28 +357,15 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { ))} </Ul> </div> - {/* <span className="text-trueGray-400 ml-auto m-auto text-xs text-right"> - Highlight code to include as context. Currently open file included by - default. {highlightedCodeSections.length === 0 && ""} - </span> */} - <ContextDropdown - onMouseEnter={() => { - setHoveringContextDropdown(true); - }} - onMouseLeave={() => { - setHoveringContextDropdown(false); - }} - hidden={true || (!hoveringContextDropdown && !hoveringButton)} - > - {highlightedCodeSections.map((section, idx) => ( - <> - <p>{section.range.filepath}</p> - <CodeBlock showCopy={false} key={idx}> - {section.range.contents} - </CodeBlock> - </> - ))} - </ContextDropdown> + {highlightedCodeSections.length === 0 && + (downshiftProps.inputValue?.startsWith("/edit") || + (focused && + metaKeyPressed && + downshiftProps.inputValue?.length > 0)) && ( + <div className="text-trueGray-400 pr-4 text-xs text-right"> + Inserting at cursor + </div> + )} </> ); }); diff --git a/extension/react-app/src/components/InputAndButton.tsx b/extension/react-app/src/components/InputAndButton.tsx index 0a8592f2..8019d014 100644 --- a/extension/react-app/src/components/InputAndButton.tsx +++ b/extension/react-app/src/components/InputAndButton.tsx @@ -1,6 +1,6 @@ import React, { useRef } from "react"; import styled from "styled-components"; -import { vscBackground } from "."; +import { vscBackground, vscForeground } from "."; interface InputAndButtonProps { onUserInput: (input: string) => void; @@ -16,7 +16,7 @@ const Input = styled.input` padding: 0.5rem; border: 1px solid white; background-color: ${vscBackground}; - color: white; + color: ${vscForeground}; border-radius: 4px; border-top-right-radius: 0; border-bottom-right-radius: 0; @@ -27,7 +27,7 @@ const Button = styled.button` padding: 0.5rem; border: 1px solid white; background-color: ${vscBackground}; - color: white; + color: ${vscForeground}; border-radius: 4px; border-top-left-radius: 0; border-bottom-left-radius: 0; @@ -35,8 +35,8 @@ const Button = styled.button` cursor: pointer; &:hover { - background-color: white; - color: black; + background-color: ${vscForeground}; + color: ${vscBackground}; } `; diff --git a/extension/react-app/src/components/LoadingCover.tsx b/extension/react-app/src/components/LoadingCover.tsx deleted file mode 100644 index a0f8f7a2..00000000 --- a/extension/react-app/src/components/LoadingCover.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const StyledDiv = styled.div` - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100vh; - background: linear-gradient( - 101.79deg, - #12887a 0%, - #87245c 32%, - #e12637 63%, - #ffb215 100% - ); - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - z-index: 10; -`; - -const StyledImg = styled.img` - /* add your styles here */ -`; - -const StyledDiv2 = styled.div` - width: 50%; - height: 5px; - background: white; - margin-top: 20px; -`; - -interface LoadingCoverProps { - message: string; - hidden?: boolean; -} - -const LoadingCover = (props: LoadingCoverProps) => { - return ( - <StyledDiv style={{ display: props.hidden ? "none" : "inherit" }}> - <StyledImg src="continue.gif" alt="centered image" width="50%" /> - <StyledDiv2></StyledDiv2> - <p>{props.message}</p> - </StyledDiv> - ); -}; - -export default LoadingCover; diff --git a/extension/react-app/src/components/Onboarding.tsx b/extension/react-app/src/components/Onboarding.tsx new file mode 100644 index 00000000..231c1e93 --- /dev/null +++ b/extension/react-app/src/components/Onboarding.tsx @@ -0,0 +1,136 @@ +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; +import React, { useState, useEffect } from "react"; +import styled from "styled-components"; +import { ArrowLeft, ArrowRight } from "@styled-icons/heroicons-outline"; +import { defaultBorderRadius } from "."; +import Loader from "./Loader"; + +const StyledDiv = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #1e1e1e; + z-index: 200; +`; + +const StyledSpan = styled.span` + padding: 8px; + border-radius: ${defaultBorderRadius}; + &:hover { + background-color: #ffffff33; + } + white-space: nowrap; +`; + +const Onboarding = () => { + const [counter, setCounter] = useState(4); + const gifs = ["intro", "highlight", "question", "help"]; + const topMessages = [ + "Welcome!", + "Highlight code", + "Ask a question", + "Use /help to learn more", + ]; + + useEffect(() => { + const hasVisited = localStorage.getItem("hasVisited"); + if (hasVisited) { + setCounter(4); + } else { + setCounter(0); + localStorage.setItem("hasVisited", "true"); + } + }, []); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + setLoading(true); + }, [counter]); + + return ( + <StyledDiv hidden={counter >= 4}> + <div + style={{ + display: "grid", + justifyContent: "center", + alignItems: "center", + height: "100%", + textAlign: "center", + background: `linear-gradient( + 101.79deg, + #12887a66 0%, + #87245c66 32%, + #e1263766 63%, + #ffb21566 100% + )`, + paddingLeft: "16px", + paddingRight: "16px", + }} + > + <h1>{topMessages[counter]}</h1> + <div style={{ display: "flex", justifyContent: "center" }}> + {loading && ( + <div style={{ margin: "auto", position: "absolute", zIndex: 0 }}> + <Loader /> + </div> + )} + {counter % 2 === 0 ? ( + <img + src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`} + width="100%" + key={"even-gif"} + alt={topMessages[counter]} + onLoad={() => { + setLoading(false); + }} + style={{ zIndex: 1 }} + /> + ) : ( + <img + src={`https://github.com/continuedev/continue/blob/main/media/${gifs[counter]}.gif?raw=true`} + width="100%" + key={"odd-gif"} + alt={topMessages[counter]} + onLoad={() => { + setLoading(false); + }} + style={{ zIndex: 1 }} + /> + )} + </div> + <p + style={{ + paddingLeft: "50px", + paddingRight: "50px", + paddingBottom: "50px", + textAlign: "center", + cursor: "pointer", + whiteSpace: "nowrap", + }} + > + <StyledSpan + hidden={counter === 0} + onClick={() => setCounter((prev) => Math.max(prev - 1, 0))} + > + <ArrowLeft width="18px" strokeWidth="2px" /> Previous + </StyledSpan> + <span hidden={counter === 0}>{" | "}</span> + <StyledSpan onClick={() => setCounter((prev) => prev + 1)}> + {counter === 0 + ? "Click to learn how to use Continue" + : counter === 3 + ? "Get Started" + : "Next"}{" "} + <ArrowRight width="18px" strokeWidth="2px" /> + </StyledSpan> + </p> + </div> + </StyledDiv> + ); +}; + +export default Onboarding; diff --git a/extension/react-app/src/components/PillButton.tsx b/extension/react-app/src/components/PillButton.tsx index 31d98c0f..c24dba83 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -3,15 +3,19 @@ import styled from "styled-components"; import { StyledTooltip, defaultBorderRadius, - lightGray, secondaryDark, + vscForeground, } from "."; -import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline"; +import { + Trash, + PaintBrush, + ExclamationTriangle, +} from "@styled-icons/heroicons-outline"; import { GUIClientContext } from "../App"; const Button = styled.button` border: none; - color: white; + color: ${vscForeground}; background-color: ${secondaryDark}; border-radius: ${defaultBorderRadius}; padding: 8px; @@ -28,10 +32,8 @@ const GridDiv = styled.div` height: 100%; display: grid; grid-gap: 0; - grid-template-columns: 1fr 1fr; align-items: center; border-radius: ${defaultBorderRadius}; - overflow: hidden; background-color: ${secondaryDark}; `; @@ -48,6 +50,21 @@ const ButtonDiv = styled.div<{ backgroundColor: string }>` } `; +const CircleDiv = styled.div` + position: absolute; + top: -10px; + right: -10px; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: red; + color: white; + display: flex; + align-items: center; + justify-content: center; + padding: 2px; +`; + interface PillButtonProps { onHover?: (arg0: boolean) => void; onDelete?: () => void; @@ -55,6 +72,8 @@ interface PillButtonProps { index: number; editing: boolean; pinned: boolean; + warning?: string; + onlyShowDelete?: boolean; } const PillButton = (props: PillButtonProps) => { @@ -63,75 +82,102 @@ const PillButton = (props: PillButtonProps) => { return ( <> - <Button - style={{ - position: "relative", - borderColor: props.editing - ? "#8800aa" - : props.pinned - ? "#ffff0099" - : "transparent", - borderWidth: "1px", - borderStyle: "solid", - }} - onMouseEnter={() => { - setIsHovered(true); - if (props.onHover) { - props.onHover(true); - } - }} - onMouseLeave={() => { - setIsHovered(false); - if (props.onHover) { - props.onHover(false); - } - }} - > - {isHovered && ( - <GridDiv> - <ButtonDiv - data-tooltip-id={`edit-${props.index}`} - backgroundColor={"#8800aa55"} - onClick={() => { - client?.setEditingAtIndices([props.index]); + <div style={{ position: "relative" }}> + <Button + style={{ + position: "relative", + borderColor: props.warning + ? "red" + : props.editing + ? "#8800aa" + : props.pinned + ? "#ffff0099" + : "transparent", + borderWidth: "1px", + borderStyle: "solid", + }} + onMouseEnter={() => { + setIsHovered(true); + if (props.onHover) { + props.onHover(true); + } + }} + onMouseLeave={() => { + setIsHovered(false); + if (props.onHover) { + props.onHover(false); + } + }} + > + {isHovered && ( + <GridDiv + style={{ + gridTemplateColumns: props.onlyShowDelete ? "1fr" : "1fr 1fr", }} > - <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush> - </ButtonDiv> + {props.onlyShowDelete || ( + <ButtonDiv + data-tooltip-id={`edit-${props.index}`} + backgroundColor={"#8800aa55"} + onClick={() => { + client?.setEditingAtIndices([props.index]); + }} + > + <PaintBrush + style={{ margin: "auto" }} + width="1.6em" + ></PaintBrush> + </ButtonDiv> + )} - {/* <ButtonDiv + {/* <ButtonDiv data-tooltip-id={`pin-${props.index}`} backgroundColor={"#ffff0055"} onClick={() => { client?.setPinnedAtIndices([props.index]); }} - > + > <MapPin style={{ margin: "auto" }} width="1.6em"></MapPin> </ButtonDiv> */} - <StyledTooltip id={`pin-${props.index}`}> - Edit this range + <StyledTooltip id={`pin-${props.index}`}> + Edit this range + </StyledTooltip> + <ButtonDiv + data-tooltip-id={`delete-${props.index}`} + backgroundColor={"#cc000055"} + onClick={() => { + if (props.onDelete) { + props.onDelete(); + } + }} + > + <Trash style={{ margin: "auto" }} width="1.6em"></Trash> + </ButtonDiv> + </GridDiv> + )} + {props.title} + </Button> + <StyledTooltip id={`edit-${props.index}`}> + {props.editing + ? "Editing this section (with entire file as context)" + : "Edit this section"} + </StyledTooltip> + <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip> + {props.warning && ( + <> + <CircleDiv data-tooltip-id={`circle-div-${props.title}`}> + <ExclamationTriangle + style={{ margin: "auto" }} + width="1.0em" + strokeWidth={2} + /> + </CircleDiv> + <StyledTooltip id={`circle-div-${props.title}`}> + {props.warning} </StyledTooltip> - <ButtonDiv - data-tooltip-id={`delete-${props.index}`} - backgroundColor={"#cc000055"} - onClick={() => { - if (props.onDelete) { - props.onDelete(); - } - }} - > - <Trash style={{ margin: "auto" }} width="1.6em"></Trash> - </ButtonDiv> - </GridDiv> + </> )} - {props.title} - </Button> - <StyledTooltip id={`edit-${props.index}`}> - {props.editing - ? "Editing this range (with rest of file as context)" - : "Edit this range"} - </StyledTooltip> - <StyledTooltip id={`delete-${props.index}`}>Delete</StyledTooltip> + </div> </> ); }; diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index d480c565..bc8665fd 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import styled, { keyframes } from "styled-components"; import { appear, @@ -6,18 +6,21 @@ import { secondaryDark, vscBackground, vscBackgroundTransparent, + vscForeground, } from "."; import { ChevronDown, ChevronRight, ArrowPath, XMark, + MagnifyingGlass, } from "@styled-icons/heroicons-outline"; import { StopCircle } from "@styled-icons/heroicons-solid"; import { HistoryNode } from "../../../schema/HistoryNode"; -import ReactMarkdown from "react-markdown"; import HeaderButtonWithText from "./HeaderButtonWithText"; -import CodeBlock from "./CodeBlock"; +import MarkdownPreview from "@uiw/react-markdown-preview"; +import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util"; +import { GUIClientContext } from "../App"; interface StepContainerProps { historyNode: HistoryNode; @@ -31,6 +34,7 @@ interface StepContainerProps { onToggle: () => void; isFirst: boolean; isLast: boolean; + index: number; } // #region styled components @@ -38,7 +42,6 @@ interface StepContainerProps { 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; margin-left: 0px; margin-right: 0px; @@ -52,12 +55,7 @@ const StepContainerDiv = styled.div<{ open: boolean }>` `; const HeaderDiv = styled.div<{ error: boolean; loading: boolean }>` - background-color: ${(props) => - props.error - ? "#522" - : props.loading - ? vscBackgroundTransparent - : vscBackground}; + background-color: ${(props) => (props.error ? "#522" : vscBackground)}; display: grid; grid-template-columns: 1fr auto auto; grid-gap: 8px; @@ -72,19 +70,6 @@ const ContentDiv = styled.div<{ isUserInput: boolean }>` font-size: 13px; `; -const MarkdownPre = styled.pre` - background-color: ${secondaryDark}; - padding: 10px; - border-radius: ${defaultBorderRadius}; - border: 0.5px solid white; -`; - -const StyledCode = styled.code` - word-wrap: break-word; - color: #f69292; - background: transparent; -`; - const gradient = keyframes` 0% { background-position: 0px 0; @@ -124,6 +109,33 @@ const GradientBorder = styled.div<{ background-size: 200% 200%; `; +const StyledMarkdownPreview = styled(MarkdownPreview)` + pre { + background-color: ${secondaryDark}; + padding: 1px; + border-radius: ${defaultBorderRadius}; + border: 0.5px solid white; + } + + code { + color: #f78383; + word-wrap: break-word; + border-radius: ${defaultBorderRadius}; + background-color: ${secondaryDark}; + } + + pre > code { + background-color: ${secondaryDark}; + color: ${vscForeground}; + } + + background-color: ${vscBackground}; + font-family: "Lexend", sans-serif; + font-size: 13px; + padding: 8px; + color: ${vscForeground}; +`; + // #endregion function StepContainer(props: StepContainerProps) { @@ -131,6 +143,7 @@ function StepContainer(props: StepContainerProps) { const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null); const userInputRef = useRef<HTMLInputElement>(null); const isUserInput = props.historyNode.step.name === "UserInputStep"; + const client = useContext(GUIClientContext); useEffect(() => { if (userInputRef?.current) { @@ -158,7 +171,7 @@ function StepContainer(props: StepContainerProps) { > <StepContainerDiv open={props.open}> <GradientBorder - loading={props.historyNode.active as boolean | false} + loading={(props.historyNode.active as boolean) || false} isFirst={props.isFirst} isLast={props.isLast} borderColor={ @@ -170,7 +183,7 @@ function StepContainer(props: StepContainerProps) { } className="overflow-hidden cursor-pointer" onClick={(e) => { - if (e.metaKey) { + if (isMetaEquivalentKeyPressed(e)) { props.onToggleAll(); } else { props.onToggle(); @@ -178,7 +191,7 @@ function StepContainer(props: StepContainerProps) { }} > <HeaderDiv - loading={props.historyNode.active as boolean | false} + loading={(props.historyNode.active as boolean) || false} error={props.historyNode.observation?.error ? true : false} > <div className="m-2"> @@ -201,12 +214,27 @@ function StepContainer(props: StepContainerProps) { </HeaderButton> */} <> + {(props.historyNode.logs as any)?.length > 0 && ( + <HeaderButtonWithText + text="Logs" + onClick={(e) => { + e.stopPropagation(); + client?.showLogsAtIndex(props.index); + }} + > + <MagnifyingGlass size="1.4em" /> + </HeaderButtonWithText> + )} <HeaderButtonWithText onClick={(e) => { e.stopPropagation(); props.onDelete(); }} - text={props.historyNode.active ? "Stop (⌘⌫)" : "Delete"} + text={ + props.historyNode.active + ? `Stop (${getMetaKeyLabel()}⌫)` + : "Delete" + } > {props.historyNode.active ? ( <StopCircle size="1.6em" onClick={props.onDelete} /> @@ -242,31 +270,19 @@ function StepContainer(props: StepContainerProps) { )} {props.historyNode.observation?.error ? ( - <pre className="overflow-x-scroll"> - {props.historyNode.observation.error as string} - </pre> + <details> + <summary>View Traceback</summary> + <pre className="overflow-x-scroll"> + {props.historyNode.observation.error as string} + </pre> + </details> ) : ( - <ReactMarkdown - key={1} - className="overflow-x-scroll" - components={{ - pre: ({ node, ...props }) => { - return ( - <CodeBlock - children={(props.children[0] as any).props.children[0]} - /> - ); - }, - code: ({ node, ...props }) => { - return <StyledCode children={props.children[0] as any} />; - }, - ul: ({ node, ...props }) => { - return <ul className="ml-0" {...props} />; - }, + <StyledMarkdownPreview + source={props.historyNode.step.description || ""} + wrapperElement={{ + "data-color-mode": "dark", }} - > - {props.historyNode.step.description as any} - </ReactMarkdown> + /> )} </ContentDiv> </StepContainerDiv> diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index ea5727f0..cba3852d 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -1,7 +1,8 @@ // Write a component that displays a dialog box with a text field and a button. import React, { useEffect, useState } from "react"; import styled from "styled-components"; -import { Button, buttonColor, secondaryDark, vscBackground } from "."; +import { Button, secondaryDark, vscBackground, vscForeground } from "."; +import { isMetaEquivalentKeyPressed } from "../util"; const ScreenCover = styled.div` position: absolute; @@ -20,13 +21,13 @@ const DialogContainer = styled.div` `; const Dialog = styled.div` - background-color: white; + color: ${vscForeground}; + background-color: ${vscBackground}; border-radius: 8px; padding: 8px; display: flex; flex-direction: column; - /* box-shadow: 0 0 10px 0 rgba(255, 255, 255, 0.5); */ - border: 2px solid ${buttonColor}; + box-shadow: 0 0 10px 0 ${vscForeground}; width: fit-content; margin: auto; `; @@ -37,14 +38,16 @@ const TextArea = styled.textarea` padding: 8px; outline: 1px solid black; resize: none; + background-color: ${secondaryDark}; + color: ${vscForeground}; &:focus { - outline: 1px solid ${buttonColor}; + outline: 1px solid ${vscForeground}; } `; const P = styled.p` - color: black; + color: ${vscForeground}; margin: 8px auto; `; @@ -81,7 +84,11 @@ const TextDialog = (props: { rows={10} ref={textAreaRef} onKeyDown={(e) => { - if (e.key === "Enter" && e.metaKey && textAreaRef.current) { + if ( + e.key === "Enter" && + isMetaEquivalentKeyPressed(e) && + textAreaRef.current + ) { props.onEnter(textAreaRef.current.value); setText(""); } else if (e.key === "Escape") { diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index 9ae0f097..cb5e7915 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -3,12 +3,16 @@ import styled, { keyframes } from "styled-components"; export const defaultBorderRadius = "5px"; export const lightGray = "rgb(100 100 100)"; -export const secondaryDark = "rgb(45 45 45)"; -export const vscBackground = "rgb(30 30 30)"; +// export const secondaryDark = "rgb(45 45 45)"; +// export const vscBackground = "rgb(30 30 30)"; export const vscBackgroundTransparent = "#1e1e1ede"; export const buttonColor = "rgb(113 28 59)"; export const buttonColorHover = "rgb(113 28 59 0.67)"; +export const secondaryDark = "var(--vscode-textBlockQuote-background)"; +export const vscBackground = "var(--vscode-editor-background)"; +export const vscForeground = "var(--vscode-editor-foreground)"; + export const Button = styled.button` padding: 10px 12px; margin: 8px 0; @@ -46,8 +50,8 @@ export const TextArea = styled.textarea` resize: vertical; padding: 4px; - caret-color: white; - color: white; + caret-color: ${vscForeground}; + color: #{vscForeground}; &:focus { outline: 1px solid ${buttonColor}; @@ -120,7 +124,7 @@ export const MainTextInput = styled.textarea` border: 1px solid #ccc; margin: 8px 8px; background-color: ${vscBackground}; - color: white; + color: ${vscForeground}; outline: 1px solid orange; resize: none; `; @@ -137,8 +141,9 @@ export const appear = keyframes` `; export const HeaderButton = styled.button<{ inverted: boolean | undefined }>` - background-color: ${({ inverted }) => (inverted ? "white" : "transparent")}; - color: ${({ inverted }) => (inverted ? "black" : "white")}; + background-color: ${({ inverted }) => + inverted ? vscForeground : "transparent"}; + color: ${({ inverted }) => (inverted ? vscBackground : vscForeground)}; border: none; border-radius: ${defaultBorderRadius}; @@ -146,7 +151,9 @@ export const HeaderButton = styled.button<{ inverted: boolean | undefined }>` &:hover { background-color: ${({ inverted }) => - typeof inverted === "undefined" || inverted ? lightGray : "transparent"}; + typeof inverted === "undefined" || inverted + ? secondaryDark + : "transparent"}; } display: flex; align-items: center; |