From 808138de633e410c1d3867d4fb7cb74967647605 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Tue, 30 Jul 2024 16:54:10 +0100 Subject: chore: remove ui directory (#2329) This is still in development, but rather than clutter the commit history and issues with an unreleased project I've split the UI into its own repo. Once ready for release, I'll either merge the ui code back in, or just make the repo public. --- ui/src/App.css | 27 -- ui/src/App.tsx | 237 -------------- ui/src/assets/icon.svg | 1 - ui/src/assets/logo-light.svg | 1 - ui/src/assets/react.svg | 1 - ui/src/components/Button.tsx | 20 -- ui/src/components/CodeBlock.tsx | 39 --- ui/src/components/Drawer.tsx | 24 -- ui/src/components/HistoryList.tsx | 33 -- ui/src/components/HistorySearch.tsx | 54 --- ui/src/components/LoginOrRegister.tsx | 341 ------------------- ui/src/components/Sidebar/Sidebar.tsx | 328 ------------------- ui/src/components/Sidebar/index.tsx | 4 - ui/src/components/dotfiles/Aliases.tsx | 180 ---------- ui/src/components/dotfiles/Vars.tsx | 194 ----------- ui/src/components/history/HistoryInspect.tsx | 40 --- ui/src/components/history/HistoryRow.tsx | 120 ------- ui/src/components/history/Stats.tsx | 161 --------- ui/src/components/home/QuickActions.tsx | 1 - ui/src/components/runbooks/List.tsx | 141 -------- ui/src/components/runbooks/editor/Editor.tsx | 200 ------------ .../runbooks/editor/blocks/Directory/index.tsx | 89 ----- .../runbooks/editor/blocks/Run/extensions.ts | 158 --------- .../runbooks/editor/blocks/Run/index.css | 9 - .../runbooks/editor/blocks/Run/index.tsx | 229 ------------- .../runbooks/editor/blocks/Run/terminal.tsx | 113 ------- ui/src/components/runbooks/editor/index.css | 7 - .../runbooks/editor/ui/DeleteBlockButton.tsx | 28 -- ui/src/components/ui/alert.tsx | 59 ---- ui/src/components/ui/button.tsx | 56 ---- ui/src/components/ui/card.tsx | 79 ----- ui/src/components/ui/chart.tsx | 363 --------------------- ui/src/components/ui/data-table.tsx | 80 ----- ui/src/components/ui/dialog.tsx | 120 ------- ui/src/components/ui/dropdown-menu.tsx | 198 ----------- ui/src/components/ui/table.tsx | 117 ------- ui/src/components/ui/toast.tsx | 127 ------- ui/src/components/ui/toaster.tsx | 33 -- ui/src/components/ui/use-toast.ts | 192 ----------- ui/src/global.d.ts | 1 - ui/src/lib/utils.ts | 48 --- ui/src/main.tsx | 22 -- ui/src/pages/Dotfiles.tsx | 109 ------- ui/src/pages/History.tsx | 73 ----- ui/src/pages/Home.tsx | 295 ----------------- ui/src/pages/Runbooks.tsx | 25 -- ui/src/state/client.ts | 33 -- ui/src/state/models.ts | 177 ---------- ui/src/state/runbooks/runbook.ts | 124 ------- ui/src/state/store.ts | 289 ---------------- ui/src/styles.css | 76 ----- ui/src/vite-env.d.ts | 1 - 52 files changed, 5477 deletions(-) delete mode 100644 ui/src/App.css delete mode 100644 ui/src/App.tsx delete mode 100644 ui/src/assets/icon.svg delete mode 100644 ui/src/assets/logo-light.svg delete mode 100644 ui/src/assets/react.svg delete mode 100644 ui/src/components/Button.tsx delete mode 100644 ui/src/components/CodeBlock.tsx delete mode 100644 ui/src/components/Drawer.tsx delete mode 100644 ui/src/components/HistoryList.tsx delete mode 100644 ui/src/components/HistorySearch.tsx delete mode 100644 ui/src/components/LoginOrRegister.tsx delete mode 100644 ui/src/components/Sidebar/Sidebar.tsx delete mode 100644 ui/src/components/Sidebar/index.tsx delete mode 100644 ui/src/components/dotfiles/Aliases.tsx delete mode 100644 ui/src/components/dotfiles/Vars.tsx delete mode 100644 ui/src/components/history/HistoryInspect.tsx delete mode 100644 ui/src/components/history/HistoryRow.tsx delete mode 100644 ui/src/components/history/Stats.tsx delete mode 100644 ui/src/components/home/QuickActions.tsx delete mode 100644 ui/src/components/runbooks/List.tsx delete mode 100644 ui/src/components/runbooks/editor/Editor.tsx delete mode 100644 ui/src/components/runbooks/editor/blocks/Directory/index.tsx delete mode 100644 ui/src/components/runbooks/editor/blocks/Run/extensions.ts delete mode 100644 ui/src/components/runbooks/editor/blocks/Run/index.css delete mode 100644 ui/src/components/runbooks/editor/blocks/Run/index.tsx delete mode 100644 ui/src/components/runbooks/editor/blocks/Run/terminal.tsx delete mode 100644 ui/src/components/runbooks/editor/index.css delete mode 100644 ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx delete mode 100644 ui/src/components/ui/alert.tsx delete mode 100644 ui/src/components/ui/button.tsx delete mode 100644 ui/src/components/ui/card.tsx delete mode 100644 ui/src/components/ui/chart.tsx delete mode 100644 ui/src/components/ui/data-table.tsx delete mode 100644 ui/src/components/ui/dialog.tsx delete mode 100644 ui/src/components/ui/dropdown-menu.tsx delete mode 100644 ui/src/components/ui/table.tsx delete mode 100644 ui/src/components/ui/toast.tsx delete mode 100644 ui/src/components/ui/toaster.tsx delete mode 100644 ui/src/components/ui/use-toast.ts delete mode 100644 ui/src/global.d.ts delete mode 100644 ui/src/lib/utils.ts delete mode 100644 ui/src/main.tsx delete mode 100644 ui/src/pages/Dotfiles.tsx delete mode 100644 ui/src/pages/History.tsx delete mode 100644 ui/src/pages/Home.tsx delete mode 100644 ui/src/pages/Runbooks.tsx delete mode 100644 ui/src/state/client.ts delete mode 100644 ui/src/state/models.ts delete mode 100644 ui/src/state/runbooks/runbook.ts delete mode 100644 ui/src/state/store.ts delete mode 100644 ui/src/styles.css delete mode 100644 ui/src/vite-env.d.ts (limited to 'ui/src') diff --git a/ui/src/App.css b/ui/src/App.css deleted file mode 100644 index 29ca80f1..00000000 --- a/ui/src/App.css +++ /dev/null @@ -1,27 +0,0 @@ -html { - overscroll-behavior: none; -} - -.logo.vite:hover { - filter: drop-shadow(0 0 2em #747bff); -} - -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafb); -} - -.history-header { - height: 150px; -} - -.history-search { - height: 64px; -} - -.history-list { - height: calc(100dvh - 4rem - 2rem); -} - -.history-item { - height: 90px; -} diff --git a/ui/src/App.tsx b/ui/src/App.tsx deleted file mode 100644 index 361a6fea..00000000 --- a/ui/src/App.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import "./App.css"; -import { open } from "@tauri-apps/plugin-shell"; - -import { useState, ReactElement } from "react"; -import { useStore } from "@/state/store"; - -import { Toaster } from "@/components/ui/toaster"; -import { KeyRoundIcon } from "lucide-react"; -import { Icon } from "@iconify/react"; - -import Home from "./pages/Home.tsx"; -import History from "./pages/History.tsx"; -import Dotfiles from "./pages/Dotfiles.tsx"; -import LoginOrRegister from "./components/LoginOrRegister.tsx"; -import Runbooks from "./pages/Runbooks.tsx"; - -import { - Avatar, - User, - Button, - ScrollShadow, - Spacer, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownSection, - DropdownTrigger, - Modal, - ModalContent, - useDisclosure, -} from "@nextui-org/react"; -import Sidebar, { SidebarItem } from "@/components/Sidebar"; -import icon from "@/assets/icon.svg"; -import { logout } from "./state/client.ts"; - -enum Section { - Home, - History, - Dotfiles, - Runbooks, -} - -function renderMain(section: Section): ReactElement { - switch (section) { - case Section.Home: - return ; - case Section.History: - return ; - case Section.Dotfiles: - return ; - case Section.Runbooks: - return ; - } -} - -function App() { - // routers don't really work in Tauri. It's not a browser! - // I think hashrouter may work, but I'd rather avoiding thinking of them as - // pages - const [section, setSection] = useState(Section.Home); - const user = useStore((state: any) => state.user); - const refreshUser = useStore((state: any) => state.refreshUser); - const { isOpen, onOpen, onOpenChange } = useDisclosure(); - - const navigation: SidebarItem[] = [ - { - key: "personal", - title: "Personal", - items: [ - { - key: "home", - icon: "solar:home-2-linear", - title: "Home", - onPress: () => setSection(Section.Home), - }, - { - key: "runbooks", - icon: "solar:notebook-linear", - title: "Runbooks", - onPress: () => { - console.log("runbooks"); - setSection(Section.Runbooks); - }, - }, - { - key: "history", - icon: "solar:history-outline", - title: "History", - onPress: () => setSection(Section.History), - }, - { - key: "dotfiles", - icon: "solar:file-smile-linear", - title: "Dotfiles", - onPress: () => setSection(Section.Dotfiles), - }, - ], - }, - ]; - - return ( -
-
-
-
-
- icon -
-
- - - - - - - -
- - - - - - - - - - - } - > - Settings - - - - open("https://forum.atuin.sh")} - startContent={ - - } - > - Help & Feedback - - - {(user.username && ( - - } - onClick={() => { - logout(); - refreshUser(); - }} - > - Log Out - - )) || ( - } - onPress={onOpen} - > - Log in or Register - - )} - - - -
-
- - {renderMain(section)} - - - - - {(onClose) => ( - <> - - - )} - - -
-
- ); -} - -export default App; diff --git a/ui/src/assets/icon.svg b/ui/src/assets/icon.svg deleted file mode 100644 index 0e4dd607..00000000 --- a/ui/src/assets/icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/src/assets/logo-light.svg b/ui/src/assets/logo-light.svg deleted file mode 100644 index 697df883..00000000 --- a/ui/src/assets/logo-light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/src/assets/react.svg b/ui/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/ui/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/src/components/Button.tsx b/ui/src/components/Button.tsx deleted file mode 100644 index 5f7e1160..00000000 --- a/ui/src/components/Button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export enum ButtonStyle { - PrimarySm = "bg-emerald-500 hover:bg-emerald-600", - PrimarySmFill = "bg-emerald-500 hover:bg-emerald-600 w-full text-sm", -} - -interface ButtonProps { - text: string; - style: ButtonStyle; -} - -export default function Button(props: ButtonProps) { - return ( - - ); -} diff --git a/ui/src/components/CodeBlock.tsx b/ui/src/components/CodeBlock.tsx deleted file mode 100644 index 4eb54a1c..00000000 --- a/ui/src/components/CodeBlock.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Highlight, themes } from "prism-react-renderer"; - -// @ts-ignore -import Prism from "prismjs"; - -// @ts-ignore -import "prismjs/components/prism-bash"; - -export default function CodeBlock({ code, language }: any) { - return ( -
- - {({ style, tokens, getLineProps, getTokenProps }) => ( -
-            {tokens.map((line, i) => (
-              
- {i == 0 && ( - $ - )} - {line.map((token, key) => ( - - ))} -
- ))} -
- )} -
-
- ); -} diff --git a/ui/src/components/Drawer.tsx b/ui/src/components/Drawer.tsx deleted file mode 100644 index 91753624..00000000 --- a/ui/src/components/Drawer.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Drawer as VDrawer } from "vaul"; - -export default function Drawer({ - trigger, - children, - width, - open, - onOpenChange, -}: any) { - return ( - - {trigger} - - - - {children} - - - - ); -} diff --git a/ui/src/components/HistoryList.tsx b/ui/src/components/HistoryList.tsx deleted file mode 100644 index 948aa5c9..00000000 --- a/ui/src/components/HistoryList.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import HistoryRow from "./history/HistoryRow"; - -export default function HistoryList(props: any) { - return ( -
- {props.items.map((i: any) => { - let h = props.history[i.index]; - - return ( -
- -
- ); - })} -
- ); -} diff --git a/ui/src/components/HistorySearch.tsx b/ui/src/components/HistorySearch.tsx deleted file mode 100644 index 33a3e536..00000000 --- a/ui/src/components/HistorySearch.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ArrowPathIcon } from "@heroicons/react/24/outline"; -import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; - -interface HistorySearchProps { - query: string; - refresh: () => void; - setQuery: (query: string) => void; -} - -export default function HistorySearch(props: HistorySearchProps) { - return ( -
-
{ - e.preventDefault(); - }} - > - -
- ); -} diff --git a/ui/src/components/LoginOrRegister.tsx b/ui/src/components/LoginOrRegister.tsx deleted file mode 100644 index 97f8a790..00000000 --- a/ui/src/components/LoginOrRegister.tsx +++ /dev/null @@ -1,341 +0,0 @@ -import Logo from "@/assets/logo-light.svg"; -import { useState } from "react"; - -import { login, register } from "@/state/client"; -import { useStore } from "@/state/store"; - -interface LoginProps { - toggleRegister: () => void; - onClose: () => void; -} - -function Login(props: LoginProps) { - const refreshUser = useStore((state) => state.refreshUser); - const [errors, setErrors] = useState(null); - - const doLogin = async (e: React.FormEvent) => { - e.preventDefault(); - - const form = e.currentTarget; - const username = form.username.value; - const password = form.password.value; - const key = form.key.value; - - console.log("Logging in..."); - try { - await login(username, password, key); - refreshUser(); - props.onClose(); - } catch (e: any) { - console.error(e); - setErrors(e); - } - }; - - return ( - <> -
-
- Atuin - -

- Sign in to your account -

- -

- Backup and sync your data across devices. All data is end-to-end - encrypted and stored securely in the cloud. -

-
- -
-
-
- -
- -
-
- -
-
- -
- {/* You can't right now. Sorry. Validate emails first. - - Forgot password? - - */} -
-
-
- -
-
- -
-
- -
-
- -
-
- -
- -
-
- - {errors && ( -

{errors}

- )} - -

- Not a member?{" "} - { - e.preventDefault(); - props.toggleRegister(); - }} - > - Register - -

-
-
- - ); -} - -interface RegisterProps { - toggleLogin: () => void; - onClose: () => void; -} - -function Register(props: RegisterProps) { - const refreshUser = useStore((state) => state.refreshUser); - const [errors, setErrors] = useState(null); - - const doRegister = async (e: React.FormEvent) => { - e.preventDefault(); - - const form = e.currentTarget; - const username = form.username.value; - const email = form.email.value; - const password = form.password.value; - - try { - await register(username, email, password); - refreshUser(); - props.onClose(); - } catch (e: any) { - setErrors(e); - } - }; - - return ( - <> -
-
- Atuin - -

- Register for an account -

- -

- Backup and sync your data across devices. All data is end-to-end - encrypted and stored securely in the cloud. -

-
- -
-
-
- -
- -
-
- -
- -
- -
-
- -
-
- -
- {/* You can't right now. Sorry. Validate emails first. - - Forgot password? - - */} -
-
-
- -
-
- -
- -
-
- - {errors && ( -

{errors}

- )} - -

- Already have an account?{" "} - { - e.preventDefault(); - props.toggleLogin(); - }} - > - Login - -

-
-
- - ); -} - -export default function LoginOrRegister({ onClose }: { onClose: () => void }) { - let [login, setLogin] = useState(false); - - if (login) { - return setLogin(false)} />; - } - - return setLogin(true)} />; -} diff --git a/ui/src/components/Sidebar/Sidebar.tsx b/ui/src/components/Sidebar/Sidebar.tsx deleted file mode 100644 index 99e2bf82..00000000 --- a/ui/src/components/Sidebar/Sidebar.tsx +++ /dev/null @@ -1,328 +0,0 @@ -"use client"; - -import { - Accordion, - AccordionItem, - type ListboxProps, - type ListboxSectionProps, - type Selection, -} from "@nextui-org/react"; -import React from "react"; -import { - Listbox, - Tooltip, - ListboxItem, - ListboxSection, -} from "@nextui-org/react"; -import { Icon } from "@iconify/react"; - -import { cn } from "@/lib/utils"; - -export enum SidebarItemType { - Nest = "nest", -} - -export type SidebarItem = { - key: string; - title: string; - icon?: string; - href?: string; - onPress?: () => void; - type?: SidebarItemType.Nest; - startContent?: React.ReactNode; - endContent?: React.ReactNode; - items?: SidebarItem[]; - className?: string; -}; - -export type SidebarProps = Omit, "children"> & { - items: SidebarItem[]; - isCompact?: boolean; - hideEndContent?: boolean; - iconClassName?: string; - sectionClasses?: ListboxSectionProps["classNames"]; - classNames?: ListboxProps["classNames"]; - defaultSelectedKey: string; - onSelect?: (key: string) => void; -}; - -const Sidebar = React.forwardRef( - ( - { - items, - isCompact, - defaultSelectedKey, - onSelect, - hideEndContent, - sectionClasses: sectionClassesProp = {}, - itemClasses: itemClassesProp = {}, - iconClassName, - classNames, - className, - ...props - }, - ref, - ) => { - const [selected, setSelected] = - React.useState(defaultSelectedKey); - - const sectionClasses = { - ...sectionClassesProp, - base: cn(sectionClassesProp?.base, "w-full", { - "p-0 max-w-[44px]": isCompact, - }), - group: cn(sectionClassesProp?.group, { - "flex flex-col gap-1": isCompact, - }), - heading: cn(sectionClassesProp?.heading, { - hidden: isCompact, - }), - }; - - const itemClasses = { - ...itemClassesProp, - base: cn(itemClassesProp?.base, { - "w-11 h-11 gap-0 p-0": isCompact, - }), - }; - - const renderNestItem = React.useCallback( - (item: SidebarItem) => { - const isNestType = - item.items && - item.items?.length > 0 && - item?.type === SidebarItemType.Nest; - - if (isNestType) { - // Is a nest type item , so we need to remove the href - delete item.href; - } - - return ( - - ) : ( - item.startContent ?? null - ) - } - title={isCompact || isNestType ? null : item.title} - > - {isCompact ? ( - -
- {item.icon ? ( - - ) : ( - item.startContent ?? null - )} -
-
- ) : null} - {!isCompact && isNestType ? ( - - - - - {item.title} - - - ) : ( - item.startContent ?? null - ) - } - > - {item.items && item.items?.length > 0 ? ( - - {item.items.map(renderItem)} - - ) : ( - renderItem(item) - )} - - - ) : null} -
- ); - }, - [isCompact, hideEndContent, iconClassName, items], - ); - - const renderItem = React.useCallback( - (item: SidebarItem) => { - const isNestType = - item.items && - item.items?.length > 0 && - item?.type === SidebarItemType.Nest; - - if (isNestType) { - return renderNestItem(item); - } - - return ( - - ) : ( - item.startContent ?? null - ) - } - textValue={item.title} - title={isCompact ? null : item.title} - > - {isCompact ? ( - -
- {item.icon ? ( - - ) : ( - item.startContent ?? null - )} -
-
- ) : null} -
- ); - }, - [isCompact, hideEndContent, iconClassName, itemClasses?.base], - ); - - return ( - { - const key = Array.from(keys)[0]; - - setSelected(key as React.Key); - onSelect?.(key as string); - }} - {...props} - > - {(item) => { - return item.items && - item.items?.length > 0 && - item?.type === SidebarItemType.Nest ? ( - renderNestItem(item) - ) : item.items && item.items?.length > 0 ? ( - - {item.items.map(renderItem)} - - ) : ( - renderItem(item) - ); - }} - - ); - }, -); - -Sidebar.displayName = "Sidebar"; - -export default Sidebar; diff --git a/ui/src/components/Sidebar/index.tsx b/ui/src/components/Sidebar/index.tsx deleted file mode 100644 index 10020952..00000000 --- a/ui/src/components/Sidebar/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import Sidebar, { SidebarItem } from "./Sidebar"; - -export type { SidebarItem }; -export default Sidebar; diff --git a/ui/src/components/dotfiles/Aliases.tsx b/ui/src/components/dotfiles/Aliases.tsx deleted file mode 100644 index 61fd001c..00000000 --- a/ui/src/components/dotfiles/Aliases.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { useEffect, useState } from "react"; - -import DataTable from "@/components/ui/data-table"; -import { Button } from "@/components/ui/button"; -import { MoreHorizontal } from "lucide-react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; - -import { ColumnDef } from "@tanstack/react-table"; - -import { invoke } from "@tauri-apps/api/core"; -import Drawer from "@/components/Drawer"; - -import { Alias } from "@/state/models"; -import { useStore } from "@/state/store"; - -function deleteAlias(name: string, refreshAliases: () => void) { - invoke("delete_alias", { name: name }) - .then(() => { - refreshAliases(); - }) - .catch(() => { - console.error("Failed to delete alias"); - }); -} - -function AddAlias({ onAdd: onAdd }: { onAdd?: () => void }) { - let [name, setName] = useState(""); - let [value, setValue] = useState(""); - - // simple form to add aliases - return ( -
-

- Add alias -

-

Add a new alias to your shell

- -
{ - e.preventDefault(); - - invoke("set_alias", { name: name, value: value }) - .then(() => { - console.log("Added alias"); - - if (onAdd) onAdd(); - }) - .catch(() => { - console.error("Failed to add alias"); - }); - }} - > - setName(e.target.value)} - placeholder="Alias name" - /> - - setValue(e.target.value)} - placeholder="Alias value" - /> - - -
-
- ); -} - -export default function Aliases() { - const aliases = useStore((state) => state.aliases); - const refreshAliases = useStore((state) => state.refreshAliases); - - let [aliasDrawerOpen, setAliasDrawerOpen] = useState(false); - - const columns: ColumnDef[] = [ - { - accessorKey: "name", - header: "Name", - }, - { - accessorKey: "value", - header: "Value", - }, - { - id: "actions", - cell: ({ row }: any) => { - const alias = row.original; - - return ( - - - - - - Actions - deleteAlias(alias.name, refreshAliases)} - > - Delete - - - - ); - }, - }, - ]; - - useEffect(() => { - refreshAliases(); - }, []); - - return ( -
-
-
-

- Aliases -

-

- Aliases allow you to condense long commands into short, - easy-to-remember commands. -

-
-
- - Add - - } - > - { - refreshAliases(); - setAliasDrawerOpen(false); - }} - /> - -
-
-
-
-
- -
-
-
-
- ); -} diff --git a/ui/src/components/dotfiles/Vars.tsx b/ui/src/components/dotfiles/Vars.tsx deleted file mode 100644 index b2379aa7..00000000 --- a/ui/src/components/dotfiles/Vars.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { useEffect, useState } from "react"; - -import DataTable from "@/components/ui/data-table"; -import { Button } from "@/components/ui/button"; -import { MoreHorizontal } from "lucide-react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; - -import { ColumnDef } from "@tanstack/react-table"; - -import { invoke } from "@tauri-apps/api/core"; -import Drawer from "@/components/Drawer"; - -import { Var } from "@/state/models"; -import { useStore } from "@/state/store"; - -function deleteVar(name: string, refreshVars: () => void) { - invoke("delete_var", { name: name }) - .then(() => { - refreshVars(); - }) - .catch(() => { - console.error("Failed to delete var"); - }); -} - -function AddVar({ onAdd: onAdd }: { onAdd?: () => void }) { - let [name, setName] = useState(""); - let [value, setValue] = useState(""); - let [exp, setExport] = useState(false); - - // simple form to add vars - return ( -
-

Add var

-

Add a new var to your shell

- -
{ - e.preventDefault(); - - invoke("set_var", { name: name, value: value, export: exp }) - .then(() => { - console.log("Added var"); - - if (onAdd) onAdd(); - }) - .catch(() => { - console.error("Failed to add var"); - }); - }} - > - setName(e.target.value)} - placeholder="Var name" - /> - - setValue(e.target.value)} - placeholder="Var value" - /> - -
- -
- - -
-
- ); -} - -export default function Vars() { - const vars = useStore((state) => state.vars); - const refreshVars = useStore((state) => state.refreshVars); - - let [varDrawerOpen, setVarDrawerOpen] = useState(false); - - const columns: ColumnDef[] = [ - { - accessorKey: "name", - header: "Name", - }, - { - accessorKey: "value", - header: "Value", - }, - { - id: "actions", - cell: ({ row }: any) => { - const shell_var = row.original; - - return ( - - - - - - Actions - deleteVar(shell_var.name, refreshVars)} - > - Delete - - - - ); - }, - }, - ]; - - useEffect(() => { - refreshVars(); - }, []); - - return ( -
-
-
-

- Vars -

-

- Configure environment variables here -

-
-
- - Add - - } - > - { - refreshVars(); - setVarDrawerOpen(false); - }} - /> - -
-
-
-
-
- -
-
-
-
- ); -} diff --git a/ui/src/components/history/HistoryInspect.tsx b/ui/src/components/history/HistoryInspect.tsx deleted file mode 100644 index 6c46f2db..00000000 --- a/ui/src/components/history/HistoryInspect.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useState, useEffect } from "react"; - -import PacmanLoader from "react-spinners/PacmanLoader"; - -import CodeBlock from "@/components/CodeBlock"; -import HistoryRow from "@/components/history/HistoryRow"; -import { ShellHistory, inspectCommandHistory } from "@/state/models"; - -function renderLoading() { - return ( -
- -
- ); -} - -export default function HistoryInspect({ history }: any) { - let [other, setOther] = useState([]); - - useEffect(() => { - (async () => { - let inspect = await inspectCommandHistory(history); - setOther(inspect.other); - })(); - }, []); - - if (other.length == 0) return renderLoading(); - - return ( -
- - -
- {other.map((i: any) => { - return ; - })} -
-
- ); -} diff --git a/ui/src/components/history/HistoryRow.tsx b/ui/src/components/history/HistoryRow.tsx deleted file mode 100644 index 4d893e61..00000000 --- a/ui/src/components/history/HistoryRow.tsx +++ /dev/null @@ -1,120 +0,0 @@ -// @ts-ignore -import { DateTime } from "luxon"; -import { ChevronRightIcon } from "@heroicons/react/20/solid"; -import { Highlight, themes } from "prism-react-renderer"; - -// @ts-ignore -import Prism from "prismjs"; - -// @ts-ignore -import "prismjs/components/prism-bash"; - -import Drawer from "../Drawer"; -import HistoryInspect from "./HistoryInspect"; -import { cn } from "@/lib/utils"; - -function msToTime(ms: number) { - let milliseconds = parseInt(ms.toFixed(1)); - let seconds = parseInt((ms / 1000).toFixed(1)); - let minutes = parseInt((ms / (1000 * 60)).toFixed(1)); - let hours = parseInt((ms / (1000 * 60 * 60)).toFixed(1)); - let days = parseInt((ms / (1000 * 60 * 60 * 24)).toFixed(1)); - - if (milliseconds < 1000) return milliseconds + "ms"; - else if (seconds < 60) return seconds + "s"; - else if (minutes < 60) return minutes + "m"; - else if (hours < 24) return hours + "hr"; - else return days + " Days"; -} - -export default function HistoryRow({ h, compact }: any) { - return ( -
  • -
    - {!compact && ( -
    -

    - {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString( - DateTime.TIME_WITH_SECONDS, - )} -

    -

    - {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString( - DateTime.DATE_SHORT, - )} -

    -
    - )} -
    - - {({ style, tokens, getLineProps, getTokenProps }) => ( -
    -                {tokens &&
    -                  tokens.map((line, i) => {
    -                    if (i != 0) return;
    -                    return (
    -                      
    - {line.map((token, key) => ( - - ))} -
    - ); - })} -
    - )} -
    -

    - {h.user} - -  on  - - {h.host} - -  in  - - {h.cwd} -

    -
    -
    -
    -
    -

    {h.exit}

    - {h.duration ? ( -

    - -

    - ) : ( -
    - )} -
    - - -
    -
  • - ); -} diff --git a/ui/src/components/history/Stats.tsx b/ui/src/components/history/Stats.tsx deleted file mode 100644 index f399eaf0..00000000 --- a/ui/src/components/history/Stats.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import { useState, useEffect } from "react"; -import { invoke } from "@tauri-apps/api/core"; -import PacmanLoader from "react-spinners/PacmanLoader"; - -import { - BarChart, - Bar, - XAxis, - YAxis, - Tooltip, - ResponsiveContainer, -} from "recharts"; - -function renderLoading() { - return ( -
    -
    - -
    -
    -

    Crunching the latest numbers...

    -
    -
    - ); -} - -function TopTable({ stats }: any) { - return ( -
    -
    -
    -

    Top commands

    -
    -
    -
    -
    -
    - - - - - - - - - {stats.map((stat: any) => ( - - - - - ))} - -
    - Command - - Count -
    - {stat[0][0]} - - {stat[1]} -
    -
    -
    -
    -
    - ); -} - -export default function Stats() { - const [stats, setStats]: any = useState([]); - const [top, setTop]: any = useState([]); - const [chart, setChart]: any = useState([]); - - useEffect(() => { - if (stats.length != 0) return; - - invoke("global_stats") - .then((s: any) => { - console.log(s.daily); - - setStats([ - { - name: "Total history", - stat: s.total_history.toLocaleString(), - }, - { - name: "Unique history", - stat: s.stats.unique_commands.toLocaleString(), - }, - { - name: "Last 1d", - stat: s.last_1d.toLocaleString(), - }, - { - name: "Last 7d", - stat: s.last_7d.toLocaleString(), - }, - { - name: "Last 30d", - stat: s.last_30d.toLocaleString(), - }, - ]); - - setChart(s.daily); - - setTop(s.stats); - }) - .catch((e) => { - console.log(e); - }); - }, []); - - if (stats.length == 0) { - return renderLoading(); - } - - return ( -
    -
    -
    - {stats.map((item: any) => ( -
    -
    - {item.name} -
    -
    - {item.stat} -
    -
    - ))} -
    -
    - -
    -
    - - - - - - - - -
    -
    - -
    - -
    -
    - ); -} diff --git a/ui/src/components/home/QuickActions.tsx b/ui/src/components/home/QuickActions.tsx deleted file mode 100644 index a22e4493..00000000 --- a/ui/src/components/home/QuickActions.tsx +++ /dev/null @@ -1 +0,0 @@ -export default function QuickActions() {} diff --git a/ui/src/components/runbooks/List.tsx b/ui/src/components/runbooks/List.tsx deleted file mode 100644 index 42da3885..00000000 --- a/ui/src/components/runbooks/List.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useEffect } from "react"; -import { - Button, - ButtonGroup, - Tooltip, - Listbox, - ListboxItem, - Dropdown, - DropdownTrigger, - DropdownMenu, - DropdownItem, - Badge, -} from "@nextui-org/react"; - -import { EllipsisVerticalIcon } from "lucide-react"; - -import { DateTime } from "luxon"; - -import { NotebookPenIcon } from "lucide-react"; -import Runbook from "@/state/runbooks/runbook"; -import { AtuinState, useStore } from "@/state/store"; - -const NoteSidebar = () => { - const runbooks = useStore((state: AtuinState) => state.runbooks); - const refreshRunbooks = useStore( - (state: AtuinState) => state.refreshRunbooks, - ); - - const currentRunbook = useStore((state: AtuinState) => state.currentRunbook); - const setCurrentRunbook = useStore( - (state: AtuinState) => state.setCurrentRunbook, - ); - const runbookInfo = useStore((state: AtuinState) => state.runbookInfo); - - useEffect(() => { - refreshRunbooks(); - }, []); - - return ( -
    -
    - { - return [runbook, runbookInfo[runbook.id]]; - })} - variant="flat" - aria-label="Runbook list" - selectionMode="single" - selectedKeys={currentRunbook ? [currentRunbook] : []} - itemClasses={{ base: "data-[selected=true]:bg-gray-200" }} - topContent={ - - - - - - } - > - {([runbook, info]: [Runbook, { ptys: number }]) => ( - { - setCurrentRunbook(runbook.id); - }} - textValue={runbook.name || "Untitled"} - endContent={ - - 0 - ? {} - : { - display: "none", - } - } - > - - - - - - { - await Runbook.delete(runbook.id); - - if (runbook.id == currentRunbook) setCurrentRunbook(""); - - refreshRunbooks(); - }} - > - Delete - - - - } - > -
    -
    {runbook.name || "Untitled"}
    -
    - - {DateTime.fromJSDate(runbook.updated).toLocaleString( - DateTime.DATETIME_SHORT, - )} - -
    -
    -
    - )} -
    -
    -
    - ); -}; - -export default NoteSidebar; diff --git a/ui/src/components/runbooks/editor/Editor.tsx b/ui/src/components/runbooks/editor/Editor.tsx deleted file mode 100644 index 6b0522f5..00000000 --- a/ui/src/components/runbooks/editor/Editor.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; - -import "./index.css"; - -import { Spinner } from "@nextui-org/react"; - -// Errors, but it all works fine and is there. Maybe missing ts defs? -// I'll figure it out later -import { - // @ts-ignore - BlockNoteSchema, - // @ts-ignore - BlockNoteEditor, - // @ts-ignore - defaultBlockSpecs, - // @ts-ignore - filterSuggestionItems, - // @ts-ignore - insertOrUpdateBlock, -} from "@blocknote/core"; - -import { - //@ts-ignore - SuggestionMenuController, - // @ts-ignore - AddBlockButton, - // @ts-ignore - getDefaultReactSlashMenuItems, - // @ts-ignore - SideMenu, - // @ts-ignore - SideMenuController, -} from "@blocknote/react"; -import { BlockNoteView } from "@blocknote/mantine"; - -import "@blocknote/core/fonts/inter.css"; -import "@blocknote/mantine/style.css"; - -import { CodeIcon, FolderOpenIcon } from "lucide-react"; -import { useDebounceCallback } from "usehooks-ts"; - -import Run from "@/components/runbooks/editor/blocks/Run"; -import Directory from "@/components/runbooks/editor/blocks/Directory"; - -import { DeleteBlock } from "@/components/runbooks/editor/ui/DeleteBlockButton"; -import { AtuinState, useStore } from "@/state/store"; -import Runbook from "@/state/runbooks/runbook"; - -// Our schema with block specs, which contain the configs and implementations for blocks -// that we want our editor to use. -const schema = BlockNoteSchema.create({ - blockSpecs: { - // Adds all default blocks. - ...defaultBlockSpecs, - - // Adds the code block. - run: Run, - directory: Directory, - }, -}); - -// Slash menu item to insert an Alert block -const insertRun = (editor: typeof schema.BlockNoteEditor) => ({ - title: "Code", - onItemClick: () => { - insertOrUpdateBlock(editor, { - type: "run", - }); - }, - icon: , - aliases: ["code", "run"], - group: "Execute", -}); - -const insertDirectory = (editor: typeof schema.BlockNoteEditor) => ({ - title: "Directory", - onItemClick: () => { - insertOrUpdateBlock(editor, { - type: "directory", - }); - }, - icon: , - aliases: ["directory", "dir", "folder"], - group: "Execute", -}); - -export default function Editor() { - const runbookId = useStore((store: AtuinState) => store.currentRunbook); - const refreshRunbooks = useStore( - (store: AtuinState) => store.refreshRunbooks, - ); - let [runbook, setRunbook] = useState(null); - - useEffect(() => { - if (!runbookId) return; - - const fetchRunbook = async () => { - let rb = await Runbook.load(runbookId); - - setRunbook(rb); - }; - - fetchRunbook(); - }, [runbookId]); - - const onChange = async () => { - if (!runbook) return; - - console.log("saved!"); - runbook.name = fetchName(); - if (editor) runbook.content = JSON.stringify(editor.document); - - await runbook.save(); - refreshRunbooks(); - }; - - const debouncedOnChange = useDebounceCallback(onChange, 1000); - - const editor = useMemo(() => { - if (!runbook) return undefined; - if (runbook.content) { - return BlockNoteEditor.create({ - initialContent: JSON.parse(runbook.content), - schema, - }); - } - - return BlockNoteEditor.create({ schema }); - }, [runbook]); - - const fetchName = (): string => { - // Infer the title from the first text block - if (!editor) return "Untitled"; - - let blocks = editor.document; - for (const block of blocks) { - if (block.type == "heading" || block.type == "paragraph") { - if (block.content.length == 0) continue; - // @ts-ignore - if (block.content[0].text.length == 0) continue; - - // @ts-ignore - return block.content[0].text; - } - } - - return "Untitled"; - }; - - if (!runbook) { - return ( -
    - -
    - ); - } - - if (editor === undefined) { - return ( -
    - -
    - ); - } - - // Renders the editor instance. - return ( -
    - - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(editor), - insertRun(editor), - insertDirectory(editor), - ], - query, - ) - } - /> - - ( - - - - - )} - /> - -
    - ); -} diff --git a/ui/src/components/runbooks/editor/blocks/Directory/index.tsx b/ui/src/components/runbooks/editor/blocks/Directory/index.tsx deleted file mode 100644 index 3e4f93d9..00000000 --- a/ui/src/components/runbooks/editor/blocks/Directory/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useState } from "react"; -import { Input, Tooltip, Button } from "@nextui-org/react"; -import { FolderInputIcon } from "lucide-react"; - -// @ts-ignore -import { createReactBlockSpec } from "@blocknote/react"; - -import { open } from "@tauri-apps/plugin-dialog"; - -interface DirectoryProps { - path: string; - onInputChange: (val: string) => void; -} - -const Directory = ({ path, onInputChange }: DirectoryProps) => { - const [value, setValue] = useState(path); - - const selectFolder = async () => { - const path = await open({ - multiple: false, - directory: true, - }); - - setValue(path || ""); - onInputChange(path || ""); - }; - - return ( -
    - -
    -
    - -
    - -
    - { - setValue(val); - onInputChange(val); - }} - /> -
    -
    -
    -
    - ); -}; - -export default createReactBlockSpec( - { - type: "directory", - propSchema: { - path: { default: "" }, - }, - content: "none", - }, - { - // @ts-ignore - render: ({ block, editor, code, type }) => { - const onInputChange = (val: string) => { - editor.updateBlock(block, { - // @ts-ignore - props: { ...block.props, path: val }, - }); - }; - - return ( - - ); - }, - }, -); diff --git a/ui/src/components/runbooks/editor/blocks/Run/extensions.ts b/ui/src/components/runbooks/editor/blocks/Run/extensions.ts deleted file mode 100644 index 76fc4343..00000000 --- a/ui/src/components/runbooks/editor/blocks/Run/extensions.ts +++ /dev/null @@ -1,158 +0,0 @@ -// Based on the basicSetup extension, as suggested by the source. Customized for Atuin. - -import { - KeyBinding, - lineNumbers, - highlightActiveLineGutter, - highlightSpecialChars, - drawSelection, - dropCursor, - rectangularSelection, - crosshairCursor, - highlightActiveLine, - keymap, -} from "@codemirror/view"; -import { EditorState, Extension } from "@codemirror/state"; -import { history, defaultKeymap, historyKeymap } from "@codemirror/commands"; -import { highlightSelectionMatches, searchKeymap } from "@codemirror/search"; - -import { - closeBrackets, - autocompletion, - closeBracketsKeymap, - completionKeymap, - CompletionContext, -} from "@codemirror/autocomplete"; - -import { - foldGutter, - indentOnInput, - syntaxHighlighting, - defaultHighlightStyle, - bracketMatching, - indentUnit, - foldKeymap, -} from "@codemirror/language"; - -import { lintKeymap } from "@codemirror/lint"; -import { invoke } from "@tauri-apps/api/core"; - -export interface MinimalSetupOptions { - highlightSpecialChars?: boolean; - history?: boolean; - drawSelection?: boolean; - syntaxHighlighting?: boolean; - - defaultKeymap?: boolean; - historyKeymap?: boolean; -} - -export interface BasicSetupOptions extends MinimalSetupOptions { - lineNumbers?: boolean; - highlightActiveLineGutter?: boolean; - foldGutter?: boolean; - dropCursor?: boolean; - allowMultipleSelections?: boolean; - indentOnInput?: boolean; - bracketMatching?: boolean; - closeBrackets?: boolean; - autocompletion?: boolean; - rectangularSelection?: boolean; - crosshairCursor?: boolean; - highlightActiveLine?: boolean; - highlightSelectionMatches?: boolean; - - closeBracketsKeymap?: boolean; - searchKeymap?: boolean; - foldKeymap?: boolean; - completionKeymap?: boolean; - lintKeymap?: boolean; - tabSize?: number; -} - -function myCompletions(context: CompletionContext) { - let word = context.matchBefore(/^.*/); - - if (!word) return null; - if (word.from == word.to && !context.explicit) return null; - - return invoke("prefix_search", { query: word.text }).then( - // @ts-ignore - (results: string[]) => { - let options = results.map((i) => { - return { label: i, type: "text" }; - }); - - return { - from: word.from, - options, - }; - }, - ); -} - -const buildAutocomplete = (): Extension => { - let ac = autocompletion({ override: [myCompletions] }); - - return ac; -}; - -export const extensions = (options: BasicSetupOptions = {}): Extension[] => { - const { crosshairCursor: initCrosshairCursor = false } = options; - - let keymaps: KeyBinding[] = []; - if (options.closeBracketsKeymap !== false) { - keymaps = keymaps.concat(closeBracketsKeymap); - } - if (options.defaultKeymap !== false) { - keymaps = keymaps.concat(defaultKeymap); - } - if (options.searchKeymap !== false) { - keymaps = keymaps.concat(searchKeymap); - } - if (options.historyKeymap !== false) { - keymaps = keymaps.concat(historyKeymap); - } - if (options.foldKeymap !== false) { - keymaps = keymaps.concat(foldKeymap); - } - if (options.completionKeymap !== false) { - keymaps = keymaps.concat(completionKeymap); - } - if (options.lintKeymap !== false) { - keymaps = keymaps.concat(lintKeymap); - } - const extensions: Extension[] = []; - if (options.lineNumbers !== false) extensions.push(lineNumbers()); - if (options.highlightActiveLineGutter !== false) - extensions.push(highlightActiveLineGutter()); - if (options.highlightSpecialChars !== false) - extensions.push(highlightSpecialChars()); - if (options.history !== false) extensions.push(history()); - if (options.foldGutter !== false) extensions.push(foldGutter()); - if (options.drawSelection !== false) extensions.push(drawSelection()); - if (options.dropCursor !== false) extensions.push(dropCursor()); - if (options.allowMultipleSelections !== false) - extensions.push(EditorState.allowMultipleSelections.of(true)); - if (options.indentOnInput !== false) extensions.push(indentOnInput()); - if (options.syntaxHighlighting !== false) - extensions.push( - syntaxHighlighting(defaultHighlightStyle, { fallback: true }), - ); - - if (options.bracketMatching !== false) extensions.push(bracketMatching()); - if (options.closeBrackets !== false) extensions.push(closeBrackets()); - if (options.autocompletion !== false) extensions.push(buildAutocomplete()); - - if (options.rectangularSelection !== false) - extensions.push(rectangularSelection()); - if (initCrosshairCursor !== false) extensions.push(crosshairCursor()); - if (options.highlightActiveLine !== false) - extensions.push(highlightActiveLine()); - if (options.highlightSelectionMatches !== false) - extensions.push(highlightSelectionMatches()); - if (options.tabSize && typeof options.tabSize === "number") - extensions.push(indentUnit.of(" ".repeat(options.tabSize))); - - return extensions.concat([keymap.of(keymaps.flat())]).filter(Boolean); -}; diff --git a/ui/src/components/runbooks/editor/blocks/Run/index.css b/ui/src/components/runbooks/editor/blocks/Run/index.css deleted file mode 100644 index e854c03b..00000000 --- a/ui/src/components/runbooks/editor/blocks/Run/index.css +++ /dev/null @@ -1,9 +0,0 @@ -ProseMirror-focused { - outline: none !important; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1) !important; -} - -.cm-editor.cm-focused { - outline: none !important; - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1) !important; -} diff --git a/ui/src/components/runbooks/editor/blocks/Run/index.tsx b/ui/src/components/runbooks/editor/blocks/Run/index.tsx deleted file mode 100644 index bef083ba..00000000 --- a/ui/src/components/runbooks/editor/blocks/Run/index.tsx +++ /dev/null @@ -1,229 +0,0 @@ -// @ts-ignore -import { createReactBlockSpec } from "@blocknote/react"; - -import "./index.css"; - -import CodeMirror from "@uiw/react-codemirror"; -import { keymap } from "@codemirror/view"; -import { langs } from "@uiw/codemirror-extensions-langs"; - -import { Play, Square } from "lucide-react"; -import { useState } from "react"; - -import { extensions } from "./extensions"; -import { platform } from "@tauri-apps/plugin-os"; -import { invoke } from "@tauri-apps/api/core"; -import Terminal from "./terminal.tsx"; - -import "@xterm/xterm/css/xterm.css"; -import { AtuinState, useStore } from "@/state/store.ts"; - -interface RunBlockProps { - onChange: (val: string) => void; - onRun?: (pty: string) => void; - onStop?: (pty: string) => void; - id: string; - code: string; - type: string; - pty: string; - isEditable: boolean; - editor: any; -} - -const findFirstParentOfType = (editor: any, id: string, type: string): any => { - // TODO: the types for blocknote aren't working. Now I'm doing this sort of shit, - // really need to fix that. - const document = editor.document; - var lastOfType = null; - - // Iterate through ALL of the blocks. - for (let i = 0; i < document.length; i++) { - if (document[i].id == id) return lastOfType; - - if (document[i].type == type) lastOfType = document[i]; - } - - return lastOfType; -}; - -const RunBlock = ({ - onChange, - id, - code, - isEditable, - onRun, - onStop, - pty, - editor, -}: RunBlockProps) => { - const [value, setValue] = useState(code); - const cleanupPtyTerm = useStore((store: AtuinState) => store.cleanupPtyTerm); - const terminals = useStore((store: AtuinState) => store.terminals); - - const [currentRunbook, incRunbookPty, decRunbookPty] = useStore( - (store: AtuinState) => [ - store.currentRunbook, - store.incRunbookPty, - store.decRunbookPty, - ], - ); - - const isRunning = pty !== null && pty !== ""; - - const handleToggle = async (event: any | null) => { - if (event) event.stopPropagation(); - - // If there's no code, don't do anything - if (!value) return; - - if (isRunning) { - await invoke("pty_kill", { pid: pty }); - - terminals[pty].terminal.dispose(); - cleanupPtyTerm(pty); - - if (onStop) onStop(pty); - if (currentRunbook) decRunbookPty(currentRunbook); - } - - if (!isRunning) { - let cwd = findFirstParentOfType(editor, id, "directory"); - - if (cwd) { - cwd = cwd.props.path; - } else { - cwd = "~"; - } - - let pty = await invoke("pty_open", { cwd }); - if (onRun) onRun(pty); - - if (currentRunbook) incRunbookPty(currentRunbook); - - let isWindows = platform() == "windows"; - let cmdEnd = isWindows ? "\r\n" : "\n"; - - let val = !value.endsWith("\n") ? value + cmdEnd : value; - await invoke("pty_write", { pid: pty, data: val }); - } - }; - - const handleCmdEnter = () => { - handleToggle(null); - return true; - }; - - const customKeymap = keymap.of([ - { - key: "Mod-Enter", - run: handleCmdEnter, - }, - ]); - - return ( -
    -
    -
    - -
    -
    - { - setValue(val); - onChange(val); - }} - extensions={[customKeymap, ...extensions(), langs.shell()]} - basicSetup={false} - /> -
    - {pty && } -
    -
    -
    -
    - ); -}; - -export default createReactBlockSpec( - { - type: "run", - propSchema: { - type: { - default: "bash", - }, - code: { default: "" }, - pty: { default: "" }, - global: { default: false }, - }, - content: "none", - }, - { - // @ts-ignore - render: ({ block, editor, code, type }) => { - const onInputChange = (val: string) => { - editor.updateBlock(block, { - // @ts-ignore - props: { ...block.props, code: val }, - }); - }; - - const onRun = (pty: string) => { - editor.updateBlock(block, { - // @ts-ignore - props: { ...block.props, pty: pty }, - }); - }; - - const onStop = (_pty: string) => { - editor?.updateBlock(block, { - props: { ...block.props, pty: "" }, - }); - }; - - return ( - - ); - }, - toExternalHTML: ({ block }) => { - return ( -
    -          {block?.props?.code}
    -        
    - ); - }, - }, -); diff --git a/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx b/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx deleted file mode 100644 index a6dc589f..00000000 --- a/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { listen } from "@tauri-apps/api/event"; -import "@xterm/xterm/css/xterm.css"; -import { useStore } from "@/state/store"; -import { invoke } from "@tauri-apps/api/core"; -import { IDisposable } from "@xterm/xterm"; - -const usePersistentTerminal = (pty: string) => { - const newPtyTerm = useStore((store) => store.newPtyTerm); - const terminals = useStore((store) => store.terminals); - const [isReady, setIsReady] = useState(false); - - useEffect(() => { - if (!terminals.hasOwnProperty(pty)) { - // create a new terminal and store it in the store. - // this means we can resume the same instance even across mount/dismount - newPtyTerm(pty); - } - - setIsReady(true); - - return () => { - // We don't dispose of the terminal when the component unmounts - }; - }, [pty, terminals, newPtyTerm]); - - return { terminalData: terminals[pty], isReady }; -}; - -const TerminalComponent = ({ pty }: any) => { - const terminalRef = useRef(null); - const { terminalData, isReady } = usePersistentTerminal(pty); - const [isAttached, setIsAttached] = useState(false); - const cleanupListenerRef = useRef<(() => void) | null>(null); - const keyDispose = useRef(null); - - useEffect(() => { - // no pty? no terminal - if (pty == null) return; - - // the terminal may still be being created so hold off - if (!isReady) return; - - const windowResize = () => { - if (!terminalData || !terminalData.fitAddon) return; - - terminalData.fitAddon.fit(); - }; - - // terminal object needs attaching to a ref to a div - if (!isAttached && terminalData && terminalData.terminal) { - // If it's never been attached, attach it - if (!terminalData.terminal.element && terminalRef.current) { - terminalData.terminal.open(terminalRef.current); - - // it might have been previously attached, but need moving elsewhere - } else if (terminalData && terminalRef.current) { - // @ts-ignore - terminalRef.current.appendChild(terminalData.terminal.element); - } - - terminalData.fitAddon.fit(); - setIsAttached(true); - - window.addEventListener("resize", windowResize); - - const disposeOnKey = terminalData.terminal.onKey(async (event) => { - await invoke("pty_write", { pid: pty, data: event.key }); - }); - - keyDispose.current = disposeOnKey; - } - - listen(`pty-${pty}`, (event: any) => { - terminalData.terminal.write(event.payload); - }).then((ul) => { - cleanupListenerRef.current = ul; - }); - - // Customize further as needed - return () => { - if ( - terminalData && - terminalData.terminal && - terminalData.terminal.element - ) { - // Instead of removing, we just detach - if (terminalData.terminal.element.parentElement) { - terminalData.terminal.element.parentElement.removeChild( - terminalData.terminal.element, - ); - } - setIsAttached(false); - } - - if (cleanupListenerRef.current) { - cleanupListenerRef.current(); - } - - if (keyDispose.current) keyDispose.current.dispose(); - - window.removeEventListener("resize", windowResize); - }; - }, [terminalData, isReady]); - - if (!isReady) return null; - - return ( -
    - ); -}; - -export default TerminalComponent; diff --git a/ui/src/components/runbooks/editor/index.css b/ui/src/components/runbooks/editor/index.css deleted file mode 100644 index 067cc500..00000000 --- a/ui/src/components/runbooks/editor/index.css +++ /dev/null @@ -1,7 +0,0 @@ -.editor a { - color: #0000ee; -} - -.editor a:hover { - cursor: pointer; -} diff --git a/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx b/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx deleted file mode 100644 index 84a9f5c8..00000000 --- a/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { - SideMenuProps, - useBlockNoteEditor, - useComponentsContext, -} from "@blocknote/react"; -import { TrashIcon } from "lucide-react"; - -// Custom Side Menu button to remove the hovered block. -export function DeleteBlock(props: SideMenuProps) { - const editor = useBlockNoteEditor(); - - const Components = useComponentsContext()!; - - return ( - { - editor.removeBlocks([props.block]); - }} - /> - } - /> - ); -} diff --git a/ui/src/components/ui/alert.tsx b/ui/src/components/ui/alert.tsx deleted file mode 100644 index 41fa7e05..00000000 --- a/ui/src/components/ui/alert.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const alertVariants = cva( - "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: - "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
    -)) -Alert.displayName = "Alert" - -const AlertTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
    -)) -AlertTitle.displayName = "AlertTitle" - -const AlertDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
    -)) -AlertDescription.displayName = "AlertDescription" - -export { Alert, AlertTitle, AlertDescription } diff --git a/ui/src/components/ui/button.tsx b/ui/src/components/ui/button.tsx deleted file mode 100644 index 0ba42773..00000000 --- a/ui/src/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } -) -Button.displayName = "Button" - -export { Button, buttonVariants } diff --git a/ui/src/components/ui/card.tsx b/ui/src/components/ui/card.tsx deleted file mode 100644 index afa13ecf..00000000 --- a/ui/src/components/ui/card.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from "react" - -import { cn } from "@/lib/utils" - -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
    -)) -Card.displayName = "Card" - -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
    -)) -CardHeader.displayName = "CardHeader" - -const CardTitle = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

    -)) -CardTitle.displayName = "CardTitle" - -const CardDescription = React.forwardRef< - HTMLParagraphElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

    -)) -CardDescription.displayName = "CardDescription" - -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

    -)) -CardContent.displayName = "CardContent" - -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
    -)) -CardFooter.displayName = "CardFooter" - -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/ui/src/components/ui/chart.tsx b/ui/src/components/ui/chart.tsx deleted file mode 100644 index a21d77ee..00000000 --- a/ui/src/components/ui/chart.tsx +++ /dev/null @@ -1,363 +0,0 @@ -import * as React from "react" -import * as RechartsPrimitive from "recharts" - -import { cn } from "@/lib/utils" - -// Format: { THEME_NAME: CSS_SELECTOR } -const THEMES = { light: "", dark: ".dark" } as const - -export type ChartConfig = { - [k in string]: { - label?: React.ReactNode - icon?: React.ComponentType - } & ( - | { color?: string; theme?: never } - | { color?: never; theme: Record } - ) -} - -type ChartContextProps = { - config: ChartConfig -} - -const ChartContext = React.createContext(null) - -function useChart() { - const context = React.useContext(ChartContext) - - if (!context) { - throw new Error("useChart must be used within a ") - } - - return context -} - -const ChartContainer = React.forwardRef< - HTMLDivElement, - React.ComponentProps<"div"> & { - config: ChartConfig - children: React.ComponentProps< - typeof RechartsPrimitive.ResponsiveContainer - >["children"] - } ->(({ id, className, children, config, ...props }, ref) => { - const uniqueId = React.useId() - const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` - - return ( - -
    - - - {children} - -
    -
    - ) -}) -ChartContainer.displayName = "Chart" - -const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter( - ([_, config]) => config.theme || config.color - ) - - if (!colorConfig.length) { - return null - } - - return ( -