aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/components')
-rw-r--r--ui/src/components/Button.tsx20
-rw-r--r--ui/src/components/CodeBlock.tsx39
-rw-r--r--ui/src/components/Drawer.tsx24
-rw-r--r--ui/src/components/HistoryList.tsx33
-rw-r--r--ui/src/components/HistorySearch.tsx54
-rw-r--r--ui/src/components/LoginOrRegister.tsx341
-rw-r--r--ui/src/components/Sidebar/Sidebar.tsx328
-rw-r--r--ui/src/components/Sidebar/index.tsx4
-rw-r--r--ui/src/components/dotfiles/Aliases.tsx180
-rw-r--r--ui/src/components/dotfiles/Vars.tsx194
-rw-r--r--ui/src/components/history/HistoryInspect.tsx40
-rw-r--r--ui/src/components/history/HistoryRow.tsx120
-rw-r--r--ui/src/components/history/Stats.tsx161
-rw-r--r--ui/src/components/home/QuickActions.tsx1
-rw-r--r--ui/src/components/runbooks/List.tsx141
-rw-r--r--ui/src/components/runbooks/editor/Editor.tsx200
-rw-r--r--ui/src/components/runbooks/editor/blocks/Directory/index.tsx89
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/extensions.ts158
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/index.css9
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/index.tsx229
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/terminal.tsx113
-rw-r--r--ui/src/components/runbooks/editor/index.css7
-rw-r--r--ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx28
-rw-r--r--ui/src/components/ui/alert.tsx59
-rw-r--r--ui/src/components/ui/button.tsx56
-rw-r--r--ui/src/components/ui/card.tsx79
-rw-r--r--ui/src/components/ui/chart.tsx363
-rw-r--r--ui/src/components/ui/data-table.tsx80
-rw-r--r--ui/src/components/ui/dialog.tsx120
-rw-r--r--ui/src/components/ui/dropdown-menu.tsx198
-rw-r--r--ui/src/components/ui/table.tsx117
-rw-r--r--ui/src/components/ui/toast.tsx127
-rw-r--r--ui/src/components/ui/toaster.tsx33
-rw-r--r--ui/src/components/ui/use-toast.ts192
34 files changed, 0 insertions, 3937 deletions
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 (
- <button
- type="button"
- className={`rounded ${props.style} px-2 py-1 font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500`}
- >
- {props.text}
- </button>
- );
-}
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 (
- <div className="overflow-auto">
- <Highlight
- theme={themes.github}
- code={code}
- prism={Prism}
- language={language}
- >
- {({ style, tokens, getLineProps, getTokenProps }) => (
- <pre style={style} className="p-4 break-words whitespace-pre-wrap">
- {tokens.map((line, i) => (
- <div key={i} {...getLineProps({ line })} data-vaul-no-drag>
- {i == 0 && (
- <span className="text-gray-500 select-none">$ </span>
- )}
- {line.map((token, key) => (
- <span
- key={key}
- {...getTokenProps({ token })}
- data-vaul-no-drag
- />
- ))}
- </div>
- ))}
- </pre>
- )}
- </Highlight>
- </div>
- );
-}
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 (
- <VDrawer.Root direction="right" open={open} onOpenChange={onOpenChange}>
- <VDrawer.Trigger asChild>{trigger}</VDrawer.Trigger>
- <VDrawer.Portal>
- <VDrawer.Overlay className="fixed inset-0 bg-black/40 z-50" />
- <VDrawer.Content
- style={{ width: width || "400px" }}
- className={`bg-white flex flex-col z-50 h-full mt-24 fixed bottom-0 right-0`}
- >
- {children}
- </VDrawer.Content>
- </VDrawer.Portal>
- </VDrawer.Root>
- );
-}
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 (
- <div
- role="list"
- className="divide-y divide-gray-100 bg-white shadow-sm ring-1 ring-gray-900/5 overflow-auto"
- style={{
- height: `${props.height}px`,
- position: "relative",
- }}
- >
- {props.items.map((i: any) => {
- let h = props.history[i.index];
-
- return (
- <div
- style={{
- position: "absolute",
- top: 0,
- left: 0,
- width: "100%",
- height: `${i.size}px`,
- transform: `translateY(${i.start}px)`,
- }}
- >
- <HistoryRow h={h} />
- </div>
- );
- })}
- </div>
- );
-}
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 (
- <div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
- <form
- className="relative flex flex-1"
- onSubmit={(e) => {
- e.preventDefault();
- }}
- >
- <label htmlFor="search-field" className="sr-only">
- Search
- </label>
- <MagnifyingGlassIcon
- className="pointer-events-none absolute inset-y-0 left-0 h-full w-5 text-gray-400"
- aria-hidden="true"
- />
- <input
- id="search-field"
- className="block h-full w-full border-0 py-0 pl-8 pr-0 text-gray-900 placeholder:text-gray-400 focus:ring-0 sm:text-sm outline-none"
- placeholder="Search..."
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- type="search"
- name="search"
- onChange={(query) => {
- props.setQuery(query.target.value);
- }}
- />
- </form>
- <div className="flex items-center gap-x-4 lg:gap-x-6">
- <button
- type="button"
- className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
- onClick={() => {
- props.refresh();
- }}
- >
- <ArrowPathIcon className="h-6 w-6" aria-hidden="true" />
- </button>
- </div>
- </div>
- );
-}
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<string | null>(null);
-
- const doLogin = async (e: React.FormEvent<HTMLFormElement>) => {
- 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 (
- <>
- <div className="flex min-h-full flex-1 flex-col justify-center px-6 ">
- <div className="sm:mx-auto sm:w-full sm:max-w-sm">
- <img className="mx-auto h-10 w-auto" src={Logo} alt="Atuin" />
-
- <h2 className="mt-5 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
- Sign in to your account
- </h2>
-
- <p className="text-sm text-center text-gray-600 mt-4 text-wrap">
- Backup and sync your data across devices. All data is end-to-end
- encrypted and stored securely in the cloud.
- </p>
- </div>
-
- <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
- <form
- className="space-y-6"
- action="#"
- method="POST"
- onSubmit={doLogin}
- >
- <div>
- <label
- htmlFor="username"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- Username
- </label>
- <div className="mt-2">
- <input
- id="username"
- name="username"
- type="username"
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 outline-none text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <div className="flex items-center justify-between">
- <label
- htmlFor="password"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- Password
- </label>
- <div className="text-sm">
- {/* You can't right now. Sorry. Validate emails first.
- <a
- href="#"
- className="font-semibold text-emerald-600 hover:text-emerald-500"
- >
- Forgot password?
- </a>
- */}
- </div>
- </div>
- <div className="mt-2">
- <input
- id="password"
- name="password"
- type="password"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- autoComplete="current-password"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset outline-none focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <div className="flex items-center justify-between">
- <label
- htmlFor="key"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- <p>Key</p>
- <p className="text-xs text-gray-500 font-normal">
- Paste the output of "atuin key" from another machine
- </p>
- </label>
- </div>
- <div className="mt-2">
- <input
- id="key"
- name="key"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- autoComplete="off"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset outline-none focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <button
- type="submit"
- className="flex w-full justify-center rounded-md bg-emerald-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-emerald-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-600"
- >
- Sign in
- </button>
- </div>
- </form>
-
- {errors && (
- <p className="mt-4 text-center text-sm text-red-500">{errors}</p>
- )}
-
- <p className="mt-10 text-center text-sm text-gray-500">
- Not a member?{" "}
- <a
- href="#"
- className="font-semibold leading-6 text-emerald-600 hover:text-emerald-500"
- onClick={(e) => {
- e.preventDefault();
- props.toggleRegister();
- }}
- >
- Register
- </a>
- </p>
- </div>
- </div>
- </>
- );
-}
-
-interface RegisterProps {
- toggleLogin: () => void;
- onClose: () => void;
-}
-
-function Register(props: RegisterProps) {
- const refreshUser = useStore((state) => state.refreshUser);
- const [errors, setErrors] = useState<string | null>(null);
-
- const doRegister = async (e: React.FormEvent<HTMLFormElement>) => {
- 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 (
- <>
- <div className="flex min-h-full flex-1 flex-col justify-center px-6 ">
- <div className="sm:mx-auto sm:w-full sm:max-w-sm">
- <img className="mx-auto h-10 w-auto" src={Logo} alt="Atuin" />
-
- <h2 className="mt-5 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
- Register for an account
- </h2>
-
- <p className="text-sm text-center text-gray-600 mt-4 text-wrap">
- Backup and sync your data across devices. All data is end-to-end
- encrypted and stored securely in the cloud.
- </p>
- </div>
-
- <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
- <form
- className="space-y-6"
- action="#"
- method="POST"
- onSubmit={doRegister}
- >
- <div>
- <label
- htmlFor="username"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- Username
- </label>
- <div className="mt-2">
- <input
- id="username"
- name="username"
- type="username"
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 outline-none text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <label
- htmlFor="email"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- Email
- </label>
- <div className="mt-2">
- <input
- id="email"
- name="email"
- type="email"
- autoComplete="email"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 outline-none text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <div className="flex items-center justify-between">
- <label
- htmlFor="password"
- className="block text-sm font-medium leading-6 text-gray-900"
- >
- Password
- </label>
- <div className="text-sm">
- {/* You can't right now. Sorry. Validate emails first.
- <a
- href="#"
- className="font-semibold text-emerald-600 hover:text-emerald-500"
- >
- Forgot password?
- </a>
- */}
- </div>
- </div>
- <div className="mt-2">
- <input
- id="password"
- name="password"
- type="password"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- autoComplete="current-password"
- required
- className="block w-full rounded-md border-0 px-1.5 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset outline-none focus:ring-emerald-600 sm:text-sm sm:leading-6"
- />
- </div>
- </div>
-
- <div>
- <button
- type="submit"
- className="flex w-full justify-center rounded-md bg-emerald-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-emerald-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-600"
- >
- Register
- </button>
- </div>
- </form>
-
- {errors && (
- <p className="mt-4 text-center text-sm text-red-500">{errors}</p>
- )}
-
- <p className="mt-10 text-center text-sm text-gray-500">
- Already have an account?{" "}
- <a
- href="#"
- className="font-semibold leading-6 text-emerald-600 hover:text-emerald-500"
- onClick={(e) => {
- e.preventDefault();
- props.toggleLogin();
- }}
- >
- Login
- </a>
- </p>
- </div>
- </div>
- </>
- );
-}
-
-export default function LoginOrRegister({ onClose }: { onClose: () => void }) {
- let [login, setLogin] = useState<boolean>(false);
-
- if (login) {
- return <Login onClose={onClose} toggleRegister={() => setLogin(false)} />;
- }
-
- return <Register onClose={onClose} toggleLogin={() => 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<ListboxProps<SidebarItem>, "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<HTMLElement, SidebarProps>(
- (
- {
- items,
- isCompact,
- defaultSelectedKey,
- onSelect,
- hideEndContent,
- sectionClasses: sectionClassesProp = {},
- itemClasses: itemClassesProp = {},
- iconClassName,
- classNames,
- className,
- ...props
- },
- ref,
- ) => {
- const [selected, setSelected] =
- React.useState<React.Key>(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 (
- <ListboxItem
- {...item}
- key={item.key}
- classNames={{
- base: cn(
- {
- "h-auto p-0": !isCompact && isNestType,
- },
- {
- "inline-block w-11": isCompact && isNestType,
- },
- ),
- }}
- endContent={
- isCompact || isNestType || hideEndContent
- ? null
- : item.endContent ?? null
- }
- startContent={
- isCompact || isNestType ? null : item.icon ? (
- <Icon
- className={cn(
- "text-default-500 group-data-[selected=true]:text-foreground",
- iconClassName,
- )}
- icon={item.icon}
- width={24}
- />
- ) : (
- item.startContent ?? null
- )
- }
- title={isCompact || isNestType ? null : item.title}
- >
- {isCompact ? (
- <Tooltip content={item.title} placement="right">
- <div className="flex w-full items-center justify-center">
- {item.icon ? (
- <Icon
- className={cn(
- "text-default-500 group-data-[selected=true]:text-foreground",
- iconClassName,
- )}
- icon={item.icon}
- width={24}
- />
- ) : (
- item.startContent ?? null
- )}
- </div>
- </Tooltip>
- ) : null}
- {!isCompact && isNestType ? (
- <Accordion className={"p-0"}>
- <AccordionItem
- key={item.key}
- aria-label={item.title}
- classNames={{
- heading: "pr-3",
- trigger: "p-0",
- content: "py-0 pl-4",
- }}
- title={
- item.icon ? (
- <div
- className={"flex h-11 items-center gap-2 px-2 py-1.5"}
- >
- <Icon
- className={cn(
- "text-default-500 group-data-[selected=true]:text-foreground",
- iconClassName,
- )}
- icon={item.icon}
- width={24}
- />
- <span className="text-small font-medium text-default-500 group-data-[selected=true]:text-foreground">
- {item.title}
- </span>
- </div>
- ) : (
- item.startContent ?? null
- )
- }
- >
- {item.items && item.items?.length > 0 ? (
- <Listbox
- className={"mt-0.5"}
- classNames={{
- list: cn("border-l border-default-200 pl-4"),
- }}
- items={item.items}
- variant="flat"
- >
- {item.items.map(renderItem)}
- </Listbox>
- ) : (
- renderItem(item)
- )}
- </AccordionItem>
- </Accordion>
- ) : null}
- </ListboxItem>
- );
- },
- [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 (
- <ListboxItem
- {...item}
- key={item.key}
- endContent={
- isCompact || hideEndContent ? null : item.endContent ?? null
- }
- startContent={
- isCompact ? null : item.icon ? (
- <Icon
- className={cn(
- "text-default-500 group-data-[selected=true]:text-foreground",
- iconClassName,
- )}
- icon={item.icon}
- width={24}
- />
- ) : (
- item.startContent ?? null
- )
- }
- textValue={item.title}
- title={isCompact ? null : item.title}
- >
- {isCompact ? (
- <Tooltip content={item.title} placement="right">
- <div className="flex w-full items-center justify-center">
- {item.icon ? (
- <Icon
- className={cn(
- "text-default-500 group-data-[selected=true]:text-foreground",
- iconClassName,
- )}
- icon={item.icon}
- width={24}
- />
- ) : (
- item.startContent ?? null
- )}
- </div>
- </Tooltip>
- ) : null}
- </ListboxItem>
- );
- },
- [isCompact, hideEndContent, iconClassName, itemClasses?.base],
- );
-
- return (
- <Listbox
- key={isCompact ? "compact" : "default"}
- ref={ref}
- hideSelectedIcon
- as="nav"
- className={cn("list-none", className)}
- classNames={{
- ...classNames,
- list: cn("items-center", classNames?.list),
- }}
- color="default"
- itemClasses={{
- ...itemClasses,
- base: cn(
- "px-3 min-h-11 rounded-large h-[44px] data-[selected=true]:bg-default-100",
- itemClasses?.base,
- ),
- title: cn(
- "text-small font-medium text-default-500 group-data-[selected=true]:text-foreground",
- itemClasses?.title,
- ),
- }}
- items={items}
- selectedKeys={[selected] as unknown as Selection}
- selectionMode="single"
- variant="flat"
- onSelectionChange={(keys) => {
- 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 ? (
- <ListboxSection
- key={item.key}
- classNames={sectionClasses}
- showDivider={isCompact}
- title={item.title}
- >
- {item.items.map(renderItem)}
- </ListboxSection>
- ) : (
- renderItem(item)
- );
- }}
- </Listbox>
- );
- },
-);
-
-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 (
- <div className="p-4">
- <h2 className="text-xl font-semibold leading-6 text-gray-900">
- Add alias
- </h2>
- <p className="mt-2">Add a new alias to your shell</p>
-
- <form
- className="mt-4"
- onSubmit={(e) => {
- e.preventDefault();
-
- invoke("set_alias", { name: name, value: value })
- .then(() => {
- console.log("Added alias");
-
- if (onAdd) onAdd();
- })
- .catch(() => {
- console.error("Failed to add alias");
- });
- }}
- >
- <input
- className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- placeholder="Alias name"
- />
-
- <input
- className="mt-4 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- type="text"
- value={value}
- onChange={(e) => setValue(e.target.value)}
- placeholder="Alias value"
- />
-
- <input
- type="submit"
- className="block mt-4 rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
- value="Add alias"
- />
- </form>
- </div>
- );
-}
-
-export default function Aliases() {
- const aliases = useStore((state) => state.aliases);
- const refreshAliases = useStore((state) => state.refreshAliases);
-
- let [aliasDrawerOpen, setAliasDrawerOpen] = useState(false);
-
- const columns: ColumnDef<Alias>[] = [
- {
- accessorKey: "name",
- header: "Name",
- },
- {
- accessorKey: "value",
- header: "Value",
- },
- {
- id: "actions",
- cell: ({ row }: any) => {
- const alias = row.original;
-
- return (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button variant="ghost" className="h-8 w-8 p-0 float-right">
- <span className="sr-only">Open menu</span>
- <MoreHorizontal className="h-4 w-4 text-right" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end">
- <DropdownMenuLabel>Actions</DropdownMenuLabel>
- <DropdownMenuItem
- onClick={() => deleteAlias(alias.name, refreshAliases)}
- >
- Delete
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- );
- },
- },
- ];
-
- useEffect(() => {
- refreshAliases();
- }, []);
-
- return (
- <div className="pt-10">
- <div className="sm:flex sm:items-center">
- <div className="sm:flex-auto">
- <h1 className="text-base font-semibold leading-6 text-gray-900">
- Aliases
- </h1>
- <p className="mt-2 text-sm text-gray-700">
- Aliases allow you to condense long commands into short,
- easy-to-remember commands.
- </p>
- </div>
- <div className="mt-4 sm:ml-16 sm:mt-0 flex-row">
- <Drawer
- open={aliasDrawerOpen}
- onOpenChange={setAliasDrawerOpen}
- width="30%"
- trigger={
- <button
- type="button"
- className="block rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
- >
- Add
- </button>
- }
- >
- <AddAlias
- onAdd={() => {
- refreshAliases();
- setAliasDrawerOpen(false);
- }}
- />
- </Drawer>
- </div>
- </div>
- <div className="mt-8 flow-root">
- <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
- <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
- <DataTable columns={columns} data={aliases} />
- </div>
- </div>
- </div>
- </div>
- );
-}
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<boolean>(false);
-
- // simple form to add vars
- return (
- <div className="p-4">
- <h2 className="text-xl font-semibold leading-6 text-gray-900">Add var</h2>
- <p className="mt-2">Add a new var to your shell</p>
-
- <form
- className="mt-4"
- onSubmit={(e) => {
- 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");
- });
- }}
- >
- <input
- className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- placeholder="Var name"
- />
-
- <input
- className="mt-4 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- type="text"
- value={value}
- onChange={(e) => setValue(e.target.value)}
- placeholder="Var value"
- />
-
- <div>
- <label>
- <input
- className="mt-4 bg-gray-50 mr-2 inline"
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- type="checkbox"
- value={exp.toString()}
- onChange={(e) => setExport(e.target.checked)}
- />
- Export the var and make it visible to subprocesses
- </label>
- </div>
-
- <input
- type="submit"
- className="block mt-4 rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
- value="Add var"
- />
- </form>
- </div>
- );
-}
-
-export default function Vars() {
- const vars = useStore((state) => state.vars);
- const refreshVars = useStore((state) => state.refreshVars);
-
- let [varDrawerOpen, setVarDrawerOpen] = useState(false);
-
- const columns: ColumnDef<Var>[] = [
- {
- accessorKey: "name",
- header: "Name",
- },
- {
- accessorKey: "value",
- header: "Value",
- },
- {
- id: "actions",
- cell: ({ row }: any) => {
- const shell_var = row.original;
-
- return (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button variant="ghost" className="h-8 w-8 p-0 float-right">
- <span className="sr-only">Open menu</span>
- <MoreHorizontal className="h-4 w-4 text-right" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end">
- <DropdownMenuLabel>Actions</DropdownMenuLabel>
- <DropdownMenuItem
- onClick={() => deleteVar(shell_var.name, refreshVars)}
- >
- Delete
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- );
- },
- },
- ];
-
- useEffect(() => {
- refreshVars();
- }, []);
-
- return (
- <div className="pt-10">
- <div className="sm:flex sm:items-center">
- <div className="sm:flex-auto">
- <h1 className="text-base font-semibold leading-6 text-gray-900">
- Vars
- </h1>
- <p className="mt-2 text-sm text-gray-700">
- Configure environment variables here
- </p>
- </div>
- <div className="mt-4 sm:ml-16 sm:mt-0 flex-row">
- <Drawer
- open={varDrawerOpen}
- onOpenChange={setVarDrawerOpen}
- width="30%"
- trigger={
- <button
- type="button"
- className="block rounded-md bg-green-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
- >
- Add
- </button>
- }
- >
- <AddVar
- onAdd={() => {
- refreshVars();
- setVarDrawerOpen(false);
- }}
- />
- </Drawer>
- </div>
- </div>
- <div className="mt-8 flow-root">
- <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
- <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
- <DataTable columns={columns} data={vars} />
- </div>
- </div>
- </div>
- </div>
- );
-}
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 (
- <div className="flex items-center justify-center h-full">
- <PacmanLoader color="#26bd65" />
- </div>
- );
-}
-
-export default function HistoryInspect({ history }: any) {
- let [other, setOther] = useState<ShellHistory[]>([]);
-
- useEffect(() => {
- (async () => {
- let inspect = await inspectCommandHistory(history);
- setOther(inspect.other);
- })();
- }, []);
-
- if (other.length == 0) return renderLoading();
-
- return (
- <div className="overflow-y-auto">
- <CodeBlock code={history.command} language="bash" />
-
- <div>
- {other.map((i: any) => {
- return <HistoryRow h={i} />;
- })}
- </div>
- </div>
- );
-}
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 (
- <li
- key={h.id}
- className={cn(
- "relative flex justify-between gap-x-6 px-4 py-5 hover:bg-gray-50 sm:px-6",
- { "py-5": !compact },
- { "py-1": compact },
- )}
- >
- <div className="flex min-w-0 gap-x-4">
- {!compact && (
- <div className="flex flex-col justify-center">
- <p className="flex text-xs text-gray-500 justify-center">
- {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(
- DateTime.TIME_WITH_SECONDS,
- )}
- </p>
- <p className="flex text-xs mt-1 text-gray-400 justify-center">
- {DateTime.fromMillis(h.timestamp / 1000000).toLocaleString(
- DateTime.DATE_SHORT,
- )}
- </p>
- </div>
- )}
- <div className="min-w-0 flex-col justify-center truncate">
- <Highlight
- theme={themes.github}
- code={h.command}
- language="bash"
- prism={Prism}
- >
- {({ style, tokens, getLineProps, getTokenProps }) => (
- <pre style={style} className="!bg-inherit text-sm">
- {tokens &&
- tokens.map((line, i) => {
- if (i != 0) return;
- return (
- <div key={i} {...getLineProps({ line })}>
- {line.map((token, key) => (
- <span key={key} {...getTokenProps({ token })} />
- ))}
- </div>
- );
- })}
- </pre>
- )}
- </Highlight>
- <p className="mt-1 flex text-xs leading-5 text-gray-500">
- <span className="relative truncate ">{h.user}</span>
-
- <span>&nbsp;on&nbsp;</span>
-
- <span className="relative truncate ">{h.host}</span>
-
- <span>&nbsp;in&nbsp;</span>
-
- <span className="relative truncate ">{h.cwd}</span>
- </p>
- </div>
- </div>
- <div className="flex shrink-0 items-center gap-x-4">
- <div className="hidden sm:flex sm:flex-col sm:items-end">
- <p className="text-sm leading-6 text-gray-900">{h.exit}</p>
- {h.duration ? (
- <p className="mt-1 text-xs leading-5 text-gray-500">
- <time dateTime={h.duration}>
- {msToTime(h.duration / 1000000)}
- </time>
- </p>
- ) : (
- <div />
- )}
- </div>
- <Drawer
- width="60%"
- trigger={
- <button type="button">
- <ChevronRightIcon
- className="h-5 w-5 flex-none text-gray-400"
- aria-hidden="true"
- />
- </button>
- }
- >
- <HistoryInspect history={h} />
- </Drawer>
- </div>
- </li>
- );
-}
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 (
- <div className="flex flex-col items-center justify-center h-full ">
- <div>
- <PacmanLoader color="#26bd65" />
- </div>
- <div className="block mt-4">
- <p>Crunching the latest numbers...</p>
- </div>
- </div>
- );
-}
-
-function TopTable({ stats }: any) {
- return (
- <div className="px-4 sm:px-6 lg:px-8">
- <div className="flex items-center">
- <div className="flex-auto">
- <h1 className="text-base font-semibold">Top commands</h1>
- </div>
- </div>
- <div className="mt-4 flow-root">
- <div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
- <div className="inline-block min-w-full py-2 align-middle">
- <table className="min-w-full divide-y divide-gray-300">
- <thead>
- <tr>
- <th
- scope="col"
- className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 lg:pl-8"
- >
- Command
- </th>
- <th
- scope="col"
- className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
- >
- Count
- </th>
- </tr>
- </thead>
- <tbody className="divide-y divide-gray-200 bg-white">
- {stats.map((stat: any) => (
- <tr>
- <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 lg:pl-8">
- {stat[0][0]}
- </td>
- <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
- {stat[1]}
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
- );
-}
-
-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 (
- <div className="flex flex-col overflow-y-scroll">
- <div className="flexfull">
- <dl className="grid grid-cols-1 sm:grid-cols-5 w-full">
- {stats.map((item: any) => (
- <div
- key={item.name}
- className="overflow-hidden bg-white px-4 py-5 shadow sm:p-6"
- >
- <dt className="truncate text-sm font-medium text-gray-500">
- {item.name}
- </dt>
- <dd className="mt-1 text-3xl font-semibold tracking-tight text-gray-900">
- {item.stat}
- </dd>
- </div>
- ))}
- </dl>
- </div>
-
- <div className="flex flex-col h-54 py-4 pl-5">
- <div className="flex flex-col h-48 pt-5 pr-5">
- <ResponsiveContainer width="100%" height="100%">
- <BarChart width={500} height={300} data={chart}>
- <XAxis dataKey="name" hide={true} />
- <YAxis />
- <Tooltip />
- <Bar dataKey="value" fill="#26bd65" />
- </BarChart>
- </ResponsiveContainer>
- </div>
- </div>
-
- <div>
- <TopTable stats={top.top} />
- </div>
- </div>
- );
-}
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 (
- <div className="w-48 flex flex-col border-r-1">
- <div className="overflow-y-auto flex-grow">
- <Listbox
- hideSelectedIcon
- items={runbooks.map((runbook: any): any => {
- 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={
- <ButtonGroup className="z-20">
- <Tooltip showArrow content="New Runbook" closeDelay={50}>
- <Button
- isIconOnly
- aria-label="New note"
- variant="light"
- size="sm"
- onPress={async () => {
- // otherwise the cursor is weirdly positioned in the new document
- window.getSelection()?.removeAllRanges();
-
- let runbook = await Runbook.create();
- setCurrentRunbook(runbook.id);
- refreshRunbooks();
- }}
- >
- <NotebookPenIcon className="p-[0.15rem]" />
- </Button>
- </Tooltip>
- </ButtonGroup>
- }
- >
- {([runbook, info]: [Runbook, { ptys: number }]) => (
- <ListboxItem
- key={runbook.id}
- onPress={() => {
- setCurrentRunbook(runbook.id);
- }}
- textValue={runbook.name || "Untitled"}
- endContent={
- <Dropdown>
- <Badge
- content={info?.ptys}
- color="primary"
- style={
- info && info?.ptys > 0
- ? {}
- : {
- display: "none",
- }
- }
- >
- <DropdownTrigger className="bg-transparent">
- <Button isIconOnly>
- <EllipsisVerticalIcon
- size="16px"
- className="bg-transparent"
- />
- </Button>
- </DropdownTrigger>
- </Badge>
- <DropdownMenu aria-label="Dynamic Actions">
- <DropdownItem
- key={"delete"}
- color="danger"
- className="text-danger"
- onPress={async () => {
- await Runbook.delete(runbook.id);
-
- if (runbook.id == currentRunbook) setCurrentRunbook("");
-
- refreshRunbooks();
- }}
- >
- Delete
- </DropdownItem>
- </DropdownMenu>
- </Dropdown>
- }
- >
- <div className="flex flex-col">
- <div className="text-md">{runbook.name || "Untitled"}</div>
- <div className="text-xs text-gray-500">
- <em>
- {DateTime.fromJSDate(runbook.updated).toLocaleString(
- DateTime.DATETIME_SHORT,
- )}
- </em>
- </div>
- </div>
- </ListboxItem>
- )}
- </Listbox>
- </div>
- </div>
- );
-};
-
-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: <CodeIcon size={18} />,
- aliases: ["code", "run"],
- group: "Execute",
-});
-
-const insertDirectory = (editor: typeof schema.BlockNoteEditor) => ({
- title: "Directory",
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "directory",
- });
- },
- icon: <FolderOpenIcon size={18} />,
- 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<Runbook | null>(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 (
- <div className="flex w-full h-full flex-col justify-center items-center">
- <Spinner />
- </div>
- );
- }
-
- if (editor === undefined) {
- return (
- <div className="flex w-full h-full flex-col justify-center items-center">
- <Spinner />
- </div>
- );
- }
-
- // Renders the editor instance.
- return (
- <div className="overflow-y-scroll w-full">
- <BlockNoteView
- editor={editor}
- slashMenu={false}
- sideMenu={false}
- onChange={debouncedOnChange}
- >
- <SuggestionMenuController
- triggerCharacter={"/"}
- getItems={async (query: any) =>
- filterSuggestionItems(
- [
- ...getDefaultReactSlashMenuItems(editor),
- insertRun(editor),
- insertDirectory(editor),
- ],
- query,
- )
- }
- />
-
- <SideMenuController
- sideMenu={(props: any) => (
- <SideMenu {...props}>
- <AddBlockButton {...props} />
- <DeleteBlock {...props} />
- </SideMenu>
- )}
- />
- </BlockNoteView>
- </div>
- );
-}
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 (
- <div className="w-full !max-w-full !outline-none overflow-none">
- <Tooltip
- content="Change working directory for all subsequent code blocks"
- delay={1000}
- >
- <div className="flex flex-row">
- <div className="mr-2">
- <Button
- isIconOnly
- variant="flat"
- aria-label="Select folder"
- onPress={selectFolder}
- >
- <FolderInputIcon />
- </Button>
- </div>
-
- <div className="w-full">
- <Input
- placeholder="~"
- value={value}
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- onValueChange={(val) => {
- setValue(val);
- onInputChange(val);
- }}
- />
- </div>
- </div>
- </Tooltip>
- </div>
- );
-};
-
-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 (
- <Directory path={block.props.path} onInputChange={onInputChange} />
- );
- },
- },
-);
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<String>(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<string>("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 (
- <div className="w-full !max-w-full !outline-none overflow-none">
- <div className="flex flex-row items-start">
- <div className="flex">
- <button
- onClick={handleToggle}
- className={`flex items-center justify-center flex-shrink-0 w-8 h-8 mr-2 rounded border focus:outline-none focus:ring-2 transition-all duration-300 ease-in-out ${
- isRunning
- ? "border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:border-red-300 focus:ring-red-300"
- : "border-green-200 bg-green-50 text-green-600 hover:bg-green-100 hover:border-green-300 focus:ring-green-300"
- }`}
- aria-label={isRunning ? "Stop code" : "Run code"}
- >
- <span
- className={`inline-block transition-transform duration-300 ease-in-out ${isRunning ? "rotate-180" : ""}`}
- >
- {isRunning ? <Square size={16} /> : <Play size={16} />}
- </span>
- </button>
- </div>
- <div className="flex-1 min-w-0 w-40">
- <CodeMirror
- id={id}
- placeholder={"Write your code here..."}
- className="!pt-0 max-w-full border border-gray-300 rounded"
- value={code}
- editable={isEditable}
- autoFocus
- onChange={(val) => {
- setValue(val);
- onChange(val);
- }}
- extensions={[customKeymap, ...extensions(), langs.shell()]}
- basicSetup={false}
- />
- <div
- className={`overflow-hidden transition-all duration-300 ease-in-out min-w-0 ${
- isRunning ? "block" : "hidden"
- }`}
- >
- {pty && <Terminal pty={pty} />}
- </div>
- </div>
- </div>
- </div>
- );
-};
-
-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 (
- <RunBlock
- onChange={onInputChange}
- id={block?.id}
- code={block.props.code}
- type={block.props.type}
- pty={block.props.pty}
- isEditable={editor.isEditable}
- onRun={onRun}
- onStop={onStop}
- editor={editor}
- />
- );
- },
- toExternalHTML: ({ block }) => {
- return (
- <pre lang="beep boop">
- <code lang="bash">{block?.props?.code}</code>
- </pre>
- );
- },
- },
-);
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<IDisposable | null>(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 (
- <div className="!max-w-full min-w-0 overflow-hidden" ref={terminalRef} />
- );
-};
-
-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 (
- <Components.SideMenu.Button
- label="Remove block"
- className="mx-1"
- icon={
- <TrashIcon
- size={24}
- onClick={() => {
- 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<HTMLDivElement> & VariantProps<typeof alertVariants>
->(({ className, variant, ...props }, ref) => (
- <div
- ref={ref}
- role="alert"
- className={cn(alertVariants({ variant }), className)}
- {...props}
- />
-))
-Alert.displayName = "Alert"
-
-const AlertTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLHeadingElement>
->(({ className, ...props }, ref) => (
- <h5
- ref={ref}
- className={cn("mb-1 font-medium leading-none tracking-tight", className)}
- {...props}
- />
-))
-AlertTitle.displayName = "AlertTitle"
-
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLParagraphElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn("text-sm [&_p]:leading-relaxed", className)}
- {...props}
- />
-))
-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<HTMLButtonElement>,
- VariantProps<typeof buttonVariants> {
- asChild?: boolean
-}
-
-const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
- return (
- <Comp
- className={cn(buttonVariants({ variant, size, className }))}
- ref={ref}
- {...props}
- />
- )
- }
-)
-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<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn(
- "rounded-lg border bg-card text-card-foreground shadow-sm",
- className
- )}
- {...props}
- />
-))
-Card.displayName = "Card"
-
-const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn("flex flex-col space-y-1.5 p-6", className)}
- {...props}
- />
-))
-CardHeader.displayName = "CardHeader"
-
-const CardTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLHeadingElement>
->(({ className, ...props }, ref) => (
- <h3
- ref={ref}
- className={cn(
- "text-2xl font-semibold leading-none tracking-tight",
- className
- )}
- {...props}
- />
-))
-CardTitle.displayName = "CardTitle"
-
-const CardDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes<HTMLParagraphElement>
->(({ className, ...props }, ref) => (
- <p
- ref={ref}
- className={cn("text-sm text-muted-foreground", className)}
- {...props}
- />
-))
-CardDescription.displayName = "CardDescription"
-
-const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
-))
-CardContent.displayName = "CardContent"
-
-const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes<HTMLDivElement>
->(({ className, ...props }, ref) => (
- <div
- ref={ref}
- className={cn("flex items-center p-6 pt-0", className)}
- {...props}
- />
-))
-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<keyof typeof THEMES, string> }
- )
-}
-
-type ChartContextProps = {
- config: ChartConfig
-}
-
-const ChartContext = React.createContext<ChartContextProps | null>(null)
-
-function useChart() {
- const context = React.useContext(ChartContext)
-
- if (!context) {
- throw new Error("useChart must be used within a <ChartContainer />")
- }
-
- 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 (
- <ChartContext.Provider value={{ config }}>
- <div
- data-chart={chartId}
- ref={ref}
- className={cn(
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
- className
- )}
- {...props}
- >
- <ChartStyle id={chartId} config={config} />
- <RechartsPrimitive.ResponsiveContainer>
- {children}
- </RechartsPrimitive.ResponsiveContainer>
- </div>
- </ChartContext.Provider>
- )
-})
-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 (
- <style
- dangerouslySetInnerHTML={{
- __html: Object.entries(THEMES)
- .map(
- ([theme, prefix]) => `
-${prefix} [data-chart=${id}] {
-${colorConfig
- .map(([key, itemConfig]) => {
- const color =
- itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
- itemConfig.color
- return color ? ` --color-${key}: ${color};` : null
- })
- .join("\n")}
-}
-`
- )
- .join("\n"),
- }}
- />
- )
-}
-
-const ChartTooltip = RechartsPrimitive.Tooltip
-
-const ChartTooltipContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
- React.ComponentProps<"div"> & {
- hideLabel?: boolean
- hideIndicator?: boolean
- indicator?: "line" | "dot" | "dashed"
- nameKey?: string
- labelKey?: string
- }
->(
- (
- {
- active,
- payload,
- className,
- indicator = "dot",
- hideLabel = false,
- hideIndicator = false,
- label,
- labelFormatter,
- labelClassName,
- formatter,
- color,
- nameKey,
- labelKey,
- },
- ref
- ) => {
- const { config } = useChart()
-
- const tooltipLabel = React.useMemo(() => {
- if (hideLabel || !payload?.length) {
- return null
- }
-
- const [item] = payload
- const key = `${labelKey || item.dataKey || item.name || "value"}`
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
- const value =
- !labelKey && typeof label === "string"
- ? config[label as keyof typeof config]?.label || label
- : itemConfig?.label
-
- if (labelFormatter) {
- return (
- <div className={cn("font-medium", labelClassName)}>
- {labelFormatter(value, payload)}
- </div>
- )
- }
-
- if (!value) {
- return null
- }
-
- return <div className={cn("font-medium", labelClassName)}>{value}</div>
- }, [
- label,
- labelFormatter,
- payload,
- hideLabel,
- labelClassName,
- config,
- labelKey,
- ])
-
- if (!active || !payload?.length) {
- return null
- }
-
- const nestLabel = payload.length === 1 && indicator !== "dot"
-
- return (
- <div
- ref={ref}
- className={cn(
- "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
- className
- )}
- >
- {!nestLabel ? tooltipLabel : null}
- <div className="grid gap-1.5">
- {payload.map((item, index) => {
- const key = `${nameKey || item.name || item.dataKey || "value"}`
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
- const indicatorColor = color || item.payload.fill || item.color
-
- return (
- <div
- key={item.dataKey}
- className={cn(
- "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
- indicator === "dot" && "items-center"
- )}
- >
- {formatter && item?.value !== undefined && item.name ? (
- formatter(item.value, item.name, item, index, item.payload)
- ) : (
- <>
- {itemConfig?.icon ? (
- <itemConfig.icon />
- ) : (
- !hideIndicator && (
- <div
- className={cn(
- "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
- {
- "h-2.5 w-2.5": indicator === "dot",
- "w-1": indicator === "line",
- "w-0 border-[1.5px] border-dashed bg-transparent":
- indicator === "dashed",
- "my-0.5": nestLabel && indicator === "dashed",
- }
- )}
- style={
- {
- "--color-bg": indicatorColor,
- "--color-border": indicatorColor,
- } as React.CSSProperties
- }
- />
- )
- )}
- <div
- className={cn(
- "flex flex-1 justify-between leading-none",
- nestLabel ? "items-end" : "items-center"
- )}
- >
- <div className="grid gap-1.5">
- {nestLabel ? tooltipLabel : null}
- <span className="text-muted-foreground">
- {itemConfig?.label || item.name}
- </span>
- </div>
- {item.value && (
- <span className="font-mono font-medium tabular-nums text-foreground">
- {item.value.toLocaleString()}
- </span>
- )}
- </div>
- </>
- )}
- </div>
- )
- })}
- </div>
- </div>
- )
- }
-)
-ChartTooltipContent.displayName = "ChartTooltip"
-
-const ChartLegend = RechartsPrimitive.Legend
-
-const ChartLegendContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> &
- Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
- hideIcon?: boolean
- nameKey?: string
- }
->(
- (
- { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
- ref
- ) => {
- const { config } = useChart()
-
- if (!payload?.length) {
- return null
- }
-
- return (
- <div
- ref={ref}
- className={cn(
- "flex items-center justify-center gap-4",
- verticalAlign === "top" ? "pb-3" : "pt-3",
- className
- )}
- >
- {payload.map((item) => {
- const key = `${nameKey || item.dataKey || "value"}`
- const itemConfig = getPayloadConfigFromPayload(config, item, key)
-
- return (
- <div
- key={item.value}
- className={cn(
- "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
- )}
- >
- {itemConfig?.icon && !hideIcon ? (
- <itemConfig.icon />
- ) : (
- <div
- className="h-2 w-2 shrink-0 rounded-[2px]"
- style={{
- backgroundColor: item.color,
- }}
- />
- )}
- {itemConfig?.label}
- </div>
- )
- })}
- </div>
- )
- }
-)
-ChartLegendContent.displayName = "ChartLegend"
-
-// Helper to extract item config from a payload.
-function getPayloadConfigFromPayload(
- config: ChartConfig,
- payload: unknown,
- key: string
-) {
- if (typeof payload !== "object" || payload === null) {
- return undefined
- }
-
- const payloadPayload =
- "payload" in payload &&
- typeof payload.payload === "object" &&
- payload.payload !== null
- ? payload.payload
- : undefined
-
- let configLabelKey: string = key
-
- if (
- key in payload &&
- typeof payload[key as keyof typeof payload] === "string"
- ) {
- configLabelKey = payload[key as keyof typeof payload] as string
- } else if (
- payloadPayload &&
- key in payloadPayload &&
- typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
- ) {
- configLabelKey = payloadPayload[
- key as keyof typeof payloadPayload
- ] as string
- }
-
- return configLabelKey in config
- ? config[configLabelKey]
- : config[key as keyof typeof config]
-}
-
-export {
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
- ChartLegend,
- ChartLegendContent,
- ChartStyle,
-}
diff --git a/ui/src/components/ui/data-table.tsx b/ui/src/components/ui/data-table.tsx
deleted file mode 100644
index cf96b620..00000000
--- a/ui/src/components/ui/data-table.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-"use client";
-
-import {
- ColumnDef,
- flexRender,
- getCoreRowModel,
- useReactTable,
-} from "@tanstack/react-table";
-
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-
-interface DataTableProps<TData, TValue> {
- columns: ColumnDef<TData, TValue>[];
- data: TData[];
-}
-
-export default function DataTable<TData, TValue>({
- columns,
- data,
-}: DataTableProps<TData, TValue>) {
- const table = useReactTable({
- data,
- columns,
- getCoreRowModel: getCoreRowModel(),
- });
-
- return (
- <div className="rounded-md border">
- <Table>
- <TableHeader>
- {table.getHeaderGroups().map((headerGroup) => (
- <TableRow key={headerGroup.id}>
- {headerGroup.headers.map((header) => {
- return (
- <TableHead key={header.id}>
- {header.isPlaceholder
- ? null
- : flexRender(
- header.column.columnDef.header,
- header.getContext(),
- )}
- </TableHead>
- );
- })}
- </TableRow>
- ))}
- </TableHeader>
- <TableBody>
- {table.getRowModel().rows?.length ? (
- table.getRowModel().rows.map((row) => (
- <TableRow
- key={row.id}
- data-state={row.getIsSelected() && "selected"}
- >
- {row.getVisibleCells().map((cell) => (
- <TableCell key={cell.id}>
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
- </TableCell>
- ))}
- </TableRow>
- ))
- ) : (
- <TableRow>
- <TableCell colSpan={columns.length} className="h-24 text-center">
- No results.
- </TableCell>
- </TableRow>
- )}
- </TableBody>
- </Table>
- </div>
- );
-}
diff --git a/ui/src/components/ui/dialog.tsx b/ui/src/components/ui/dialog.tsx
deleted file mode 100644
index c23630eb..00000000
--- a/ui/src/components/ui/dialog.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { X } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const Dialog = DialogPrimitive.Root
-
-const DialogTrigger = DialogPrimitive.Trigger
-
-const DialogPortal = DialogPrimitive.Portal
-
-const DialogClose = DialogPrimitive.Close
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Overlay>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Overlay
- ref={ref}
- className={cn(
- "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
- className
- )}
- {...props}
- />
-))
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
-
-const DialogContent = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
->(({ className, children, ...props }, ref) => (
- <DialogPortal>
- <DialogOverlay />
- <DialogPrimitive.Content
- ref={ref}
- className={cn(
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
- className
- )}
- {...props}
- >
- {children}
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
- <X className="h-4 w-4" />
- <span className="sr-only">Close</span>
- </DialogPrimitive.Close>
- </DialogPrimitive.Content>
- </DialogPortal>
-))
-DialogContent.displayName = DialogPrimitive.Content.displayName
-
-const DialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn(
- "flex flex-col space-y-1.5 text-center sm:text-left",
- className
- )}
- {...props}
- />
-)
-DialogHeader.displayName = "DialogHeader"
-
-const DialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLDivElement>) => (
- <div
- className={cn(
- "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
- className
- )}
- {...props}
- />
-)
-DialogFooter.displayName = "DialogFooter"
-
-const DialogTitle = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Title>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Title
- ref={ref}
- className={cn(
- "text-lg font-semibold leading-none tracking-tight",
- className
- )}
- {...props}
- />
-))
-DialogTitle.displayName = DialogPrimitive.Title.displayName
-
-const DialogDescription = React.forwardRef<
- React.ElementRef<typeof DialogPrimitive.Description>,
- React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
->(({ className, ...props }, ref) => (
- <DialogPrimitive.Description
- ref={ref}
- className={cn("text-sm text-muted-foreground", className)}
- {...props}
- />
-))
-DialogDescription.displayName = DialogPrimitive.Description.displayName
-
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-}
diff --git a/ui/src/components/ui/dropdown-menu.tsx b/ui/src/components/ui/dropdown-menu.tsx
deleted file mode 100644
index 769ff7aa..00000000
--- a/ui/src/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import * as React from "react"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { Check, ChevronRight, Circle } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const DropdownMenu = DropdownMenuPrimitive.Root
-
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
-
-const DropdownMenuGroup = DropdownMenuPrimitive.Group
-
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal
-
-const DropdownMenuSub = DropdownMenuPrimitive.Sub
-
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
- inset?: boolean
- }
->(({ className, inset, children, ...props }, ref) => (
- <DropdownMenuPrimitive.SubTrigger
- ref={ref}
- className={cn(
- "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
- inset && "pl-8",
- className
- )}
- {...props}
- >
- {children}
- <ChevronRight className="ml-auto h-4 w-4" />
- </DropdownMenuPrimitive.SubTrigger>
-))
-DropdownMenuSubTrigger.displayName =
- DropdownMenuPrimitive.SubTrigger.displayName
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
->(({ className, ...props }, ref) => (
- <DropdownMenuPrimitive.SubContent
- ref={ref}
- className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
- )}
- {...props}
- />
-))
-DropdownMenuSubContent.displayName =
- DropdownMenuPrimitive.SubContent.displayName
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
->(({ className, sideOffset = 4, ...props }, ref) => (
- <DropdownMenuPrimitive.Portal>
- <DropdownMenuPrimitive.Content
- ref={ref}
- sideOffset={sideOffset}
- className={cn(
- "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
- className
- )}
- {...props}
- />
- </DropdownMenuPrimitive.Portal>
-))
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Item>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
- inset?: boolean
- }
->(({ className, inset, ...props }, ref) => (
- <DropdownMenuPrimitive.Item
- ref={ref}
- className={cn(
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- inset && "pl-8",
- className
- )}
- {...props}
- />
-))
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
->(({ className, children, checked, ...props }, ref) => (
- <DropdownMenuPrimitive.CheckboxItem
- ref={ref}
- className={cn(
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
- )}
- checked={checked}
- {...props}
- >
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
- <DropdownMenuPrimitive.ItemIndicator>
- <Check className="h-4 w-4" />
- </DropdownMenuPrimitive.ItemIndicator>
- </span>
- {children}
- </DropdownMenuPrimitive.CheckboxItem>
-))
-DropdownMenuCheckboxItem.displayName =
- DropdownMenuPrimitive.CheckboxItem.displayName
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
->(({ className, children, ...props }, ref) => (
- <DropdownMenuPrimitive.RadioItem
- ref={ref}
- className={cn(
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
- className
- )}
- {...props}
- >
- <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
- <DropdownMenuPrimitive.ItemIndicator>
- <Circle className="h-2 w-2 fill-current" />
- </DropdownMenuPrimitive.ItemIndicator>
- </span>
- {children}
- </DropdownMenuPrimitive.RadioItem>
-))
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Label>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
- inset?: boolean
- }
->(({ className, inset, ...props }, ref) => (
- <DropdownMenuPrimitive.Label
- ref={ref}
- className={cn(
- "px-2 py-1.5 text-sm font-semibold",
- inset && "pl-8",
- className
- )}
- {...props}
- />
-))
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
->(({ className, ...props }, ref) => (
- <DropdownMenuPrimitive.Separator
- ref={ref}
- className={cn("-mx-1 my-1 h-px bg-muted", className)}
- {...props}
- />
-))
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
-
-const DropdownMenuShortcut = ({
- className,
- ...props
-}: React.HTMLAttributes<HTMLSpanElement>) => {
- return (
- <span
- className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
- {...props}
- />
- )
-}
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
-
-export {
- DropdownMenu,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuGroup,
- DropdownMenuPortal,
- DropdownMenuSub,
- DropdownMenuSubContent,
- DropdownMenuSubTrigger,
- DropdownMenuRadioGroup,
-}
diff --git a/ui/src/components/ui/table.tsx b/ui/src/components/ui/table.tsx
deleted file mode 100644
index 7f3502f8..00000000
--- a/ui/src/components/ui/table.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-const Table = React.forwardRef<
- HTMLTableElement,
- React.HTMLAttributes<HTMLTableElement>
->(({ className, ...props }, ref) => (
- <div className="relative w-full overflow-auto">
- <table
- ref={ref}
- className={cn("w-full caption-bottom text-sm", className)}
- {...props}
- />
- </div>
-))
-Table.displayName = "Table"
-
-const TableHeader = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes<HTMLTableSectionElement>
->(({ className, ...props }, ref) => (
- <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
-))
-TableHeader.displayName = "TableHeader"
-
-const TableBody = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes<HTMLTableSectionElement>
->(({ className, ...props }, ref) => (
- <tbody
- ref={ref}
- className={cn("[&_tr:last-child]:border-0", className)}
- {...props}
- />
-))
-TableBody.displayName = "TableBody"
-
-const TableFooter = React.forwardRef<
- HTMLTableSectionElement,
- React.HTMLAttributes<HTMLTableSectionElement>
->(({ className, ...props }, ref) => (
- <tfoot
- ref={ref}
- className={cn(
- "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
- className
- )}
- {...props}
- />
-))
-TableFooter.displayName = "TableFooter"
-
-const TableRow = React.forwardRef<
- HTMLTableRowElement,
- React.HTMLAttributes<HTMLTableRowElement>
->(({ className, ...props }, ref) => (
- <tr
- ref={ref}
- className={cn(
- "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
- className
- )}
- {...props}
- />
-))
-TableRow.displayName = "TableRow"
-
-const TableHead = React.forwardRef<
- HTMLTableCellElement,
- React.ThHTMLAttributes<HTMLTableCellElement>
->(({ className, ...props }, ref) => (
- <th
- ref={ref}
- className={cn(
- "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
- className
- )}
- {...props}
- />
-))
-TableHead.displayName = "TableHead"
-
-const TableCell = React.forwardRef<
- HTMLTableCellElement,
- React.TdHTMLAttributes<HTMLTableCellElement>
->(({ className, ...props }, ref) => (
- <td
- ref={ref}
- className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
- {...props}
- />
-))
-TableCell.displayName = "TableCell"
-
-const TableCaption = React.forwardRef<
- HTMLTableCaptionElement,
- React.HTMLAttributes<HTMLTableCaptionElement>
->(({ className, ...props }, ref) => (
- <caption
- ref={ref}
- className={cn("mt-4 text-sm text-muted-foreground", className)}
- {...props}
- />
-))
-TableCaption.displayName = "TableCaption"
-
-export {
- Table,
- TableHeader,
- TableBody,
- TableFooter,
- TableHead,
- TableRow,
- TableCell,
- TableCaption,
-}
diff --git a/ui/src/components/ui/toast.tsx b/ui/src/components/ui/toast.tsx
deleted file mode 100644
index a8224775..00000000
--- a/ui/src/components/ui/toast.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import * as React from "react"
-import * as ToastPrimitives from "@radix-ui/react-toast"
-import { cva, type VariantProps } from "class-variance-authority"
-import { X } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const ToastProvider = ToastPrimitives.Provider
-
-const ToastViewport = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Viewport>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
->(({ className, ...props }, ref) => (
- <ToastPrimitives.Viewport
- ref={ref}
- className={cn(
- "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
- className
- )}
- {...props}
- />
-))
-ToastViewport.displayName = ToastPrimitives.Viewport.displayName
-
-const toastVariants = cva(
- "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
- {
- variants: {
- variant: {
- default: "border bg-background text-foreground",
- destructive:
- "destructive group border-destructive bg-destructive text-destructive-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-const Toast = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Root>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
- VariantProps<typeof toastVariants>
->(({ className, variant, ...props }, ref) => {
- return (
- <ToastPrimitives.Root
- ref={ref}
- className={cn(toastVariants({ variant }), className)}
- {...props}
- />
- )
-})
-Toast.displayName = ToastPrimitives.Root.displayName
-
-const ToastAction = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Action>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
->(({ className, ...props }, ref) => (
- <ToastPrimitives.Action
- ref={ref}
- className={cn(
- "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
- className
- )}
- {...props}
- />
-))
-ToastAction.displayName = ToastPrimitives.Action.displayName
-
-const ToastClose = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Close>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
->(({ className, ...props }, ref) => (
- <ToastPrimitives.Close
- ref={ref}
- className={cn(
- "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
- className
- )}
- toast-close=""
- {...props}
- >
- <X className="h-4 w-4" />
- </ToastPrimitives.Close>
-))
-ToastClose.displayName = ToastPrimitives.Close.displayName
-
-const ToastTitle = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Title>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
->(({ className, ...props }, ref) => (
- <ToastPrimitives.Title
- ref={ref}
- className={cn("text-sm font-semibold", className)}
- {...props}
- />
-))
-ToastTitle.displayName = ToastPrimitives.Title.displayName
-
-const ToastDescription = React.forwardRef<
- React.ElementRef<typeof ToastPrimitives.Description>,
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
->(({ className, ...props }, ref) => (
- <ToastPrimitives.Description
- ref={ref}
- className={cn("text-sm opacity-90", className)}
- {...props}
- />
-))
-ToastDescription.displayName = ToastPrimitives.Description.displayName
-
-type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
-
-type ToastActionElement = React.ReactElement<typeof ToastAction>
-
-export {
- type ToastProps,
- type ToastActionElement,
- ToastProvider,
- ToastViewport,
- Toast,
- ToastTitle,
- ToastDescription,
- ToastClose,
- ToastAction,
-}
diff --git a/ui/src/components/ui/toaster.tsx b/ui/src/components/ui/toaster.tsx
deleted file mode 100644
index a2209ba5..00000000
--- a/ui/src/components/ui/toaster.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- Toast,
- ToastClose,
- ToastDescription,
- ToastProvider,
- ToastTitle,
- ToastViewport,
-} from "@/components/ui/toast"
-import { useToast } from "@/components/ui/use-toast"
-
-export function Toaster() {
- const { toasts } = useToast()
-
- return (
- <ToastProvider>
- {toasts.map(function ({ id, title, description, action, ...props }) {
- return (
- <Toast key={id} {...props}>
- <div className="grid gap-1">
- {title && <ToastTitle>{title}</ToastTitle>}
- {description && (
- <ToastDescription>{description}</ToastDescription>
- )}
- </div>
- {action}
- <ToastClose />
- </Toast>
- )
- })}
- <ToastViewport />
- </ToastProvider>
- )
-}
diff --git a/ui/src/components/ui/use-toast.ts b/ui/src/components/ui/use-toast.ts
deleted file mode 100644
index 16713070..00000000
--- a/ui/src/components/ui/use-toast.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-// Inspired by react-hot-toast library
-import * as React from "react"
-
-import type {
- ToastActionElement,
- ToastProps,
-} from "@/components/ui/toast"
-
-const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
-
-type ToasterToast = ToastProps & {
- id: string
- title?: React.ReactNode
- description?: React.ReactNode
- action?: ToastActionElement
-}
-
-const actionTypes = {
- ADD_TOAST: "ADD_TOAST",
- UPDATE_TOAST: "UPDATE_TOAST",
- DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST",
-} as const
-
-let count = 0
-
-function genId() {
- count = (count + 1) % Number.MAX_SAFE_INTEGER
- return count.toString()
-}
-
-type ActionType = typeof actionTypes
-
-type Action =
- | {
- type: ActionType["ADD_TOAST"]
- toast: ToasterToast
- }
- | {
- type: ActionType["UPDATE_TOAST"]
- toast: Partial<ToasterToast>
- }
- | {
- type: ActionType["DISMISS_TOAST"]
- toastId?: ToasterToast["id"]
- }
- | {
- type: ActionType["REMOVE_TOAST"]
- toastId?: ToasterToast["id"]
- }
-
-interface State {
- toasts: ToasterToast[]
-}
-
-const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
-
-const addToRemoveQueue = (toastId: string) => {
- if (toastTimeouts.has(toastId)) {
- return
- }
-
- const timeout = setTimeout(() => {
- toastTimeouts.delete(toastId)
- dispatch({
- type: "REMOVE_TOAST",
- toastId: toastId,
- })
- }, TOAST_REMOVE_DELAY)
-
- toastTimeouts.set(toastId, timeout)
-}
-
-export const reducer = (state: State, action: Action): State => {
- switch (action.type) {
- case "ADD_TOAST":
- return {
- ...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
- }
-
- case "UPDATE_TOAST":
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === action.toast.id ? { ...t, ...action.toast } : t
- ),
- }
-
- case "DISMISS_TOAST": {
- const { toastId } = action
-
- // ! Side effects ! - This could be extracted into a dismissToast() action,
- // but I'll keep it here for simplicity
- if (toastId) {
- addToRemoveQueue(toastId)
- } else {
- state.toasts.forEach((toast) => {
- addToRemoveQueue(toast.id)
- })
- }
-
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === toastId || toastId === undefined
- ? {
- ...t,
- open: false,
- }
- : t
- ),
- }
- }
- case "REMOVE_TOAST":
- if (action.toastId === undefined) {
- return {
- ...state,
- toasts: [],
- }
- }
- return {
- ...state,
- toasts: state.toasts.filter((t) => t.id !== action.toastId),
- }
- }
-}
-
-const listeners: Array<(state: State) => void> = []
-
-let memoryState: State = { toasts: [] }
-
-function dispatch(action: Action) {
- memoryState = reducer(memoryState, action)
- listeners.forEach((listener) => {
- listener(memoryState)
- })
-}
-
-type Toast = Omit<ToasterToast, "id">
-
-function toast({ ...props }: Toast) {
- const id = genId()
-
- const update = (props: ToasterToast) =>
- dispatch({
- type: "UPDATE_TOAST",
- toast: { ...props, id },
- })
- const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
-
- dispatch({
- type: "ADD_TOAST",
- toast: {
- ...props,
- id,
- open: true,
- onOpenChange: (open) => {
- if (!open) dismiss()
- },
- },
- })
-
- return {
- id: id,
- dismiss,
- update,
- }
-}
-
-function useToast() {
- const [state, setState] = React.useState<State>(memoryState)
-
- React.useEffect(() => {
- listeners.push(setState)
- return () => {
- const index = listeners.indexOf(setState)
- if (index > -1) {
- listeners.splice(index, 1)
- }
- }
- }, [state])
-
- return {
- ...state,
- toast,
- dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
- }
-}
-
-export { useToast, toast }