From 2d3d96e5b55a225eb97251850909eb7a0a7242ed Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 20 Aug 2023 20:21:33 -0700
Subject: feat: :sparkles: delete context groups

---
 extension/react-app/src/components/ComboBox.tsx    | 109 ++------------------
 extension/react-app/src/components/TextDialog.tsx  |  13 +++
 .../components/dialogs/AddContextGroupDialog.tsx   |  50 ++++++++++
 .../dialogs/SelectContextGroupDialog.tsx           | 111 +++++++++++++++++++++
 .../src/hooks/AbstractContinueGUIClientProtocol.ts |   2 +
 .../src/hooks/ContinueGUIClientProtocol.ts         |   4 +
 6 files changed, 186 insertions(+), 103 deletions(-)
 create mode 100644 extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx
 create mode 100644 extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx

(limited to 'extension/react-app/src')

diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index c407a779..31cb4371 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -7,8 +7,6 @@ import React, {
 import { useCombobox } from "downshift";
 import styled from "styled-components";
 import {
-  Button,
-  TextInput,
   defaultBorderRadius,
   lightGray,
   secondaryDark,
@@ -33,44 +31,14 @@ import {
 } from "../redux/slices/uiStateSlice";
 import { useDispatch, useSelector } from "react-redux";
 import { RootStore } from "../redux/store";
+import SelectContextGroupDialog from "./dialogs/SelectContextGroupDialog";
+import AddContextGroupDialog from "./dialogs/AddContextGroupDialog";
 
 const SEARCH_INDEX_NAME = "continue_context_items";
 
 // #region styled components
 const mainInputFontSize = 13;
 
-const MiniPillSpan = styled.span`
-  padding: 3px;
-  padding-left: 6px;
-  padding-right: 6px;
-  border-radius: ${defaultBorderRadius};
-  color: ${vscForeground};
-  background-color: #fff3;
-  overflow: hidden;
-  font-size: 12px;
-  display: flex;
-  align-items: center;
-  text-align: center;
-  justify-content: center;
-`;
-
-const ContextGroupSelectDiv = styled.div`
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 8px;
-  border-radius: ${defaultBorderRadius};
-  background-color: ${secondaryDark};
-  color: ${vscForeground};
-  margin-top: 8px;
-  cursor: pointer;
-
-  &:hover {
-    background-color: ${vscBackground};
-    color: ${vscForeground};
-  }
-`;
-
 const EmptyPillDiv = styled.div`
   padding: 4px;
   padding-left: 8px;
@@ -374,81 +342,16 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
   }, [inputRef.current]);
 
   const showSelectContextGroupDialog = () => {
-    dispatch(
-      setDialogMessage(
-        <div className="px-4">
-          <h2>Saved Context Groups</h2>
-
-          {savedContextGroups && Object.keys(savedContextGroups).length > 0 ? (
-            <div className="overflow-scroll">
-              {Object.keys(savedContextGroups).map((key: string) => {
-                const contextGroup = savedContextGroups[key];
-                return (
-                  <ContextGroupSelectDiv
-                    onClick={() => {
-                      dispatch(setDialogMessage(undefined));
-                      dispatch(setShowDialog(false));
-                      client?.selectContextGroup(key);
-                    }}
-                  >
-                    <b>{key}: </b>
-
-                    {contextGroup.map((contextItem) => {
-                      return (
-                        <MiniPillSpan>
-                          {contextItem.description.name}
-                        </MiniPillSpan>
-                      );
-                    })}
-                  </ContextGroupSelectDiv>
-                );
-              })}
-            </div>
-          ) : (
-            <div>No saved context groups</div>
-          )}
-          <Button
-            className="ml-auto"
-            onClick={() => {
-              dispatch(setDialogMessage(undefined));
-              dispatch(setShowDialog(false));
-            }}
-          >
-            Cancel
-          </Button>
-        </div>
-      )
-    );
+    dispatch(setDialogMessage(<SelectContextGroupDialog />));
     dispatch(setShowDialog(true));
   };
 
   const showDialogToSaveContextGroup = () => {
-    let inputElement: HTMLInputElement | null = null;
     dispatch(
       setDialogMessage(
-        <div>
-          <TextInput
-            defaultValue="My Context Group"
-            type="text"
-            ref={(input) => {
-              inputElement = input;
-            }}
-          />
-          <br />
-          <Button
-            className="ml-auto"
-            onClick={() => {
-              dispatch(setDialogMessage(undefined));
-              dispatch(setShowDialog(false));
-              const title = inputElement
-                ? inputElement.value
-                : "My Context Group";
-              client?.saveContextGroup(title, props.selectedContextItems);
-            }}
-          >
-            Create
-          </Button>
-        </div>
+        <AddContextGroupDialog
+          selectedContextItems={props.selectedContextItems}
+        />
       )
     );
     dispatch(setShowDialog(true));
diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx
index 7fcc41f1..44d25ae6 100644
--- a/extension/react-app/src/components/TextDialog.tsx
+++ b/extension/react-app/src/components/TextDialog.tsx
@@ -58,6 +58,19 @@ const TextDialog = (props: {
   onClose: () => void;
   message?: string | JSX.Element;
 }) => {
+  useEffect(() => {
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (event.key === "Escape") {
+        props.onClose();
+      }
+    };
+
+    document.addEventListener("keydown", handleKeyDown);
+    return () => {
+      document.removeEventListener("keydown", handleKeyDown);
+    };
+  }, [props]);
+
   return (
     <ScreenCover
       onClick={() => {
diff --git a/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx
new file mode 100644
index 00000000..f6c7c626
--- /dev/null
+++ b/extension/react-app/src/components/dialogs/AddContextGroupDialog.tsx
@@ -0,0 +1,50 @@
+import { useContext } from "react";
+import { Button, TextInput } from "..";
+import { GUIClientContext } from "../../App";
+import { useDispatch } from "react-redux";
+import {
+  setDialogMessage,
+  setShowDialog,
+} from "../../redux/slices/uiStateSlice";
+import { ContextItem } from "../../../../schema/FullState";
+
+function AddContextGroupDialog({
+  selectedContextItems,
+}: {
+  selectedContextItems: ContextItem[];
+}) {
+  const dispatch = useDispatch();
+  const client = useContext(GUIClientContext);
+
+  let inputElement: HTMLInputElement | null = null;
+
+  const handleCreate = () => {
+    dispatch(setDialogMessage(undefined));
+    dispatch(setShowDialog(false));
+    const title = inputElement ? inputElement.value : "My Context Group";
+    client?.saveContextGroup(title, selectedContextItems);
+  };
+
+  return (
+    <div>
+      <TextInput
+        defaultValue="My Context Group"
+        type="text"
+        ref={(input) => {
+          inputElement = input;
+        }}
+        onKeyDown={(e) => {
+          if (e.key === "Enter") {
+            handleCreate();
+          }
+        }}
+      />
+      <br />
+      <Button className="ml-auto" onClick={handleCreate}>
+        Create
+      </Button>
+    </div>
+  );
+}
+
+export default AddContextGroupDialog;
diff --git a/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx
new file mode 100644
index 00000000..b8fc2bb7
--- /dev/null
+++ b/extension/react-app/src/components/dialogs/SelectContextGroupDialog.tsx
@@ -0,0 +1,111 @@
+import { useDispatch, useSelector } from "react-redux";
+import { RootStore } from "../../redux/store";
+import { useContext } from "react";
+import { GUIClientContext } from "../../App";
+import { TrashIcon } from "@heroicons/react/24/outline";
+import HeaderButtonWithText from "../HeaderButtonWithText";
+import {
+  setDialogMessage,
+  setShowDialog,
+} from "../../redux/slices/uiStateSlice";
+import {
+  Button,
+  defaultBorderRadius,
+  secondaryDark,
+  vscBackground,
+  vscForeground,
+} from "..";
+import styled from "styled-components";
+
+const MiniPillSpan = styled.span`
+  padding: 3px;
+  padding-left: 6px;
+  padding-right: 6px;
+  border-radius: ${defaultBorderRadius};
+  color: ${vscForeground};
+  background-color: #fff3;
+  overflow: hidden;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  text-align: center;
+  justify-content: center;
+`;
+
+const ContextGroupSelectDiv = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px;
+  border-radius: ${defaultBorderRadius};
+  background-color: ${secondaryDark};
+  color: ${vscForeground};
+  margin-top: 8px;
+  cursor: pointer;
+
+  &:hover {
+    background-color: ${vscBackground};
+    color: ${vscForeground};
+  }
+`;
+
+function SelectContextGroupDialog() {
+  const dispatch = useDispatch();
+  const savedContextGroups = useSelector(
+    (state: RootStore) => state.serverState.saved_context_groups
+  );
+  const client = useContext(GUIClientContext);
+
+  return (
+    <div className="px-4">
+      <h2>Saved Context Groups</h2>
+
+      {savedContextGroups && Object.keys(savedContextGroups).length > 0 ? (
+        <div className="overflow-scroll">
+          {Object.keys(savedContextGroups).map((key: string) => {
+            const contextGroup = savedContextGroups[key];
+            return (
+              <ContextGroupSelectDiv
+                onClick={() => {
+                  dispatch(setDialogMessage(undefined));
+                  dispatch(setShowDialog(false));
+                  client?.selectContextGroup(key);
+                }}
+              >
+                <b>{key}: </b>
+
+                {contextGroup.map((contextItem) => {
+                  return (
+                    <MiniPillSpan>{contextItem.description.name}</MiniPillSpan>
+                  );
+                })}
+                <HeaderButtonWithText
+                  text="Delete"
+                  onClick={(e) => {
+                    e.stopPropagation();
+                    client?.deleteContextGroup(key);
+                  }}
+                >
+                  <TrashIcon width="1.4em" height="1.4em" />
+                </HeaderButtonWithText>
+              </ContextGroupSelectDiv>
+            );
+          })}
+        </div>
+      ) : (
+        <div>No saved context groups</div>
+      )}
+      <Button
+        className="ml-auto"
+        onClick={() => {
+          dispatch(setDialogMessage(undefined));
+          dispatch(setShowDialog(false));
+        }}
+      >
+        Cancel
+      </Button>
+    </div>
+  );
+}
+
+export default SelectContextGroupDialog;
diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
index 2e8aaeef..c9e7def2 100644
--- a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts
@@ -40,6 +40,8 @@ abstract class AbstractContinueGUIClientProtocol {
   abstract saveContextGroup(title: string, contextItems: ContextItem[]): void;
 
   abstract selectContextGroup(id: string): void;
+
+  abstract deleteContextGroup(id: string): void;
 }
 
 export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index aa558adb..b3ac2570 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -143,6 +143,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
   selectContextGroup(id: string): void {
     this.messenger?.send("select_context_group", { id });
   }
+
+  deleteContextGroup(id: string): void {
+    this.messenger?.send("delete_context_group", { id });
+  }
 }
 
 export default ContinueGUIClientProtocol;
-- 
cgit v1.2.3-70-g09d2