diff options
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 | 68 | ||||
| -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 | 167 | ||||
| -rw-r--r-- | extension/react-app/src/components/StepContainer.tsx | 89 | ||||
| -rw-r--r-- | extension/react-app/src/components/TextDialog.tsx | 7 | 
7 files changed, 335 insertions, 458 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 801c3a03..f11e07af 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -1,30 +1,19 @@ -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,  } 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; @@ -180,6 +169,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,6 +231,11 @@ 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 +            }              editing={section.editing}              pinned={section.pinned}              index={idx} @@ -272,7 +287,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. ${getMetaKeyLabel()}⏎ to edit.`}            {...getInputProps({              onChange: (e) => {                const target = e.target as HTMLTextAreaElement; @@ -285,6 +300,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 +333,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 && @@ -357,10 +378,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> */} +      {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> +        )}        <ContextDropdown          onMouseEnter={() => {            setHoveringContextDropdown(true); 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..d9d779d1 100644 --- a/extension/react-app/src/components/PillButton.tsx +++ b/extension/react-app/src/components/PillButton.tsx @@ -1,12 +1,11 @@  import { useContext, useState } from "react";  import styled from "styled-components"; +import { StyledTooltip, defaultBorderRadius, secondaryDark } from ".";  import { -  StyledTooltip, -  defaultBorderRadius, -  lightGray, -  secondaryDark, -} from "."; -import { Trash, PaintBrush, MapPin } from "@styled-icons/heroicons-outline"; +  Trash, +  PaintBrush, +  ExclamationTriangle, +} from "@styled-icons/heroicons-outline";  import { GUIClientContext } from "../App";  const Button = styled.button` @@ -31,7 +30,6 @@ const GridDiv = styled.div`    grid-template-columns: 1fr 1fr;    align-items: center;    border-radius: ${defaultBorderRadius}; -  overflow: hidden;    background-color: ${secondaryDark};  `; @@ -48,6 +46,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 +68,7 @@ interface PillButtonProps {    index: number;    editing: boolean;    pinned: boolean; +  warning?: string;  }  const PillButton = (props: PillButtonProps) => { @@ -63,75 +77,96 @@ 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]); -              }} -            > -              <PaintBrush style={{ margin: "auto" }} width="1.6em"></PaintBrush> -            </ButtonDiv> +      <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> +              <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 range (with rest of file as context)" +            : "Edit this range"} +        </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..93bdbc89 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 { useEffect, useRef, useState } from "react";  import styled, { keyframes } from "styled-components";  import {    appear, @@ -15,9 +15,9 @@ import {  } 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";  interface StepContainerProps {    historyNode: HistoryNode; @@ -72,19 +72,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 +111,31 @@ 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: #f69292; +    word-wrap: break-word; +  } + +  pre > code { +    background-color: ${secondaryDark}; +    color: white; +  } + +  background-color: ${vscBackground}; +  font-family: "Lexend", sans-serif; +  font-size: 13px; +  padding: 8px; +  color: white; +`; +  // #endregion  function StepContainer(props: StepContainerProps) { @@ -158,7 +170,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 +182,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 +190,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"> @@ -206,7 +218,11 @@ function StepContainer(props: StepContainerProps) {                    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 +258,16 @@ 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} />; -                }, -              }} -            > -              {props.historyNode.step.description as any} -            </ReactMarkdown> +            <StyledMarkdownPreview +              source={props.historyNode.step.description || ""} +            />            )}          </ContentDiv>        </StepContainerDiv> diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index ea5727f0..646d6846 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -2,6 +2,7 @@  import React, { useEffect, useState } from "react";  import styled from "styled-components";  import { Button, buttonColor, secondaryDark, vscBackground } from "."; +import { isMetaEquivalentKeyPressed } from "../util";  const ScreenCover = styled.div`    position: absolute; @@ -81,7 +82,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") { | 
