diff options
| author | Ellie Huxtable <ellie@atuin.sh> | 2024-07-30 16:54:10 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-30 16:54:10 +0100 |
| commit | 808138de633e410c1d3867d4fb7cb74967647605 (patch) | |
| tree | f180b7066b91d8d8d8006219a118439be1621d74 /ui/src | |
| parent | chore(deps): bump debian (#2320) (diff) | |
| download | atuin-808138de633e410c1d3867d4fb7cb74967647605.zip | |
chore: remove ui directory (#2329)
This is still in development, but rather than clutter the commit history
and issues with an unreleased project I've split the UI into its own
repo.
Once ready for release, I'll either merge the ui code back in, or just
make the repo public.
Diffstat (limited to 'ui/src')
52 files changed, 0 insertions, 5477 deletions
diff --git a/ui/src/App.css b/ui/src/App.css deleted file mode 100644 index 29ca80f1..00000000 --- a/ui/src/App.css +++ /dev/null @@ -1,27 +0,0 @@ -html { - overscroll-behavior: none; -} - -.logo.vite:hover { - filter: drop-shadow(0 0 2em #747bff); -} - -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafb); -} - -.history-header { - height: 150px; -} - -.history-search { - height: 64px; -} - -.history-list { - height: calc(100dvh - 4rem - 2rem); -} - -.history-item { - height: 90px; -} diff --git a/ui/src/App.tsx b/ui/src/App.tsx deleted file mode 100644 index 361a6fea..00000000 --- a/ui/src/App.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import "./App.css"; -import { open } from "@tauri-apps/plugin-shell"; - -import { useState, ReactElement } from "react"; -import { useStore } from "@/state/store"; - -import { Toaster } from "@/components/ui/toaster"; -import { KeyRoundIcon } from "lucide-react"; -import { Icon } from "@iconify/react"; - -import Home from "./pages/Home.tsx"; -import History from "./pages/History.tsx"; -import Dotfiles from "./pages/Dotfiles.tsx"; -import LoginOrRegister from "./components/LoginOrRegister.tsx"; -import Runbooks from "./pages/Runbooks.tsx"; - -import { - Avatar, - User, - Button, - ScrollShadow, - Spacer, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownSection, - DropdownTrigger, - Modal, - ModalContent, - useDisclosure, -} from "@nextui-org/react"; -import Sidebar, { SidebarItem } from "@/components/Sidebar"; -import icon from "@/assets/icon.svg"; -import { logout } from "./state/client.ts"; - -enum Section { - Home, - History, - Dotfiles, - Runbooks, -} - -function renderMain(section: Section): ReactElement { - switch (section) { - case Section.Home: - return <Home />; - case Section.History: - return <History />; - case Section.Dotfiles: - return <Dotfiles />; - case Section.Runbooks: - return <Runbooks />; - } -} - -function App() { - // routers don't really work in Tauri. It's not a browser! - // I think hashrouter may work, but I'd rather avoiding thinking of them as - // pages - const [section, setSection] = useState(Section.Home); - const user = useStore((state: any) => state.user); - const refreshUser = useStore((state: any) => state.refreshUser); - const { isOpen, onOpen, onOpenChange } = useDisclosure(); - - const navigation: SidebarItem[] = [ - { - key: "personal", - title: "Personal", - items: [ - { - key: "home", - icon: "solar:home-2-linear", - title: "Home", - onPress: () => setSection(Section.Home), - }, - { - key: "runbooks", - icon: "solar:notebook-linear", - title: "Runbooks", - onPress: () => { - console.log("runbooks"); - setSection(Section.Runbooks); - }, - }, - { - key: "history", - icon: "solar:history-outline", - title: "History", - onPress: () => setSection(Section.History), - }, - { - key: "dotfiles", - icon: "solar:file-smile-linear", - title: "Dotfiles", - onPress: () => setSection(Section.Dotfiles), - }, - ], - }, - ]; - - return ( - <div - className="flex w-screen select-none" - style={{ maxWidth: "100vw", height: "calc(100dvh - 2rem)" }} - > - <div className="flex w-full"> - <div className="relative flex flex-col !border-r-small border-divider transition-width pb-6 pt-4 items-center"> - <div className="flex items-center gap-0 px-3 justify-center"> - <div className="flex h-8 w-8"> - <img src={icon} alt="icon" className="h-8 w-8" /> - </div> - </div> - - <ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6"> - <Sidebar - defaultSelectedKey="home" - isCompact={true} - items={navigation} - className="z-50" - /> - </ScrollShadow> - - <Spacer y={2} /> - - <div className="flex items-center gap-3 px-3"> - <Dropdown showArrow placement="right-start"> - <DropdownTrigger> - <Button disableRipple isIconOnly radius="full" variant="light"> - <Avatar - isBordered - className="flex-none" - size="sm" - name={user.username || ""} - /> - </Button> - </DropdownTrigger> - <DropdownMenu aria-label="Custom item styles"> - <DropdownItem - key="profile" - isReadOnly - className="h-14 opacity-100" - textValue="Signed in as" - > - <User - avatarProps={{ - size: "sm", - name: user.username || "Anonymous User", - showFallback: true, - imgProps: { - className: "transition-none", - }, - }} - classNames={{ - name: "text-default-600", - description: "text-default-500", - }} - description={ - user.bio || (user.username && "No bio") || "Sign up now" - } - name={user.username || "Anonymous User"} - /> - </DropdownItem> - - <DropdownItem - key="settings" - description="Configure Atuin" - startContent={ - <Icon icon="solar:settings-linear" width={24} /> - } - > - Settings - </DropdownItem> - - <DropdownSection aria-label="Help & Feedback"> - <DropdownItem - key="help_and_feedback" - description="Get in touch" - onPress={() => open("https://forum.atuin.sh")} - startContent={ - <Icon width={24} icon="solar:question-circle-linear" /> - } - > - Help & Feedback - </DropdownItem> - - {(user.username && ( - <DropdownItem - key="logout" - startContent={ - <Icon width={24} icon="solar:logout-broken" /> - } - onClick={() => { - logout(); - refreshUser(); - }} - > - Log Out - </DropdownItem> - )) || ( - <DropdownItem - key="signup" - description="Sync, backup and share your data" - className="bg-emerald-100" - startContent={<KeyRoundIcon size="18px" />} - onPress={onOpen} - > - Log in or Register - </DropdownItem> - )} - </DropdownSection> - </DropdownMenu> - </Dropdown> - </div> - </div> - - {renderMain(section)} - - <Toaster /> - <Modal - isOpen={isOpen} - onOpenChange={onOpenChange} - placement="top-center" - > - <ModalContent className="p-8"> - {(onClose) => ( - <> - <LoginOrRegister onClose={onClose} /> - </> - )} - </ModalContent> - </Modal> - </div> - </div> - ); -} - -export default App; diff --git a/ui/src/assets/icon.svg b/ui/src/assets/icon.svg deleted file mode 100644 index 0e4dd607..00000000 --- a/ui/src/assets/icon.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" width="344.41" height="309.08" viewBox="0 0 344.41 309.08"><g id="Layer_1-2"><path d="m99.75,291.76c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.4,1.38,2.29,3.16,2.59,5.13.07.41.11.87.11,1.36v10.52s-1.68.27-1.68.27c-2.22.36-4.56.63-6.95.83-2.36.22-4.89.36-7.71.43l-2.05.05v-12.1Zm-2.84,12.05c-.1,0-.19,0-.28,0-2.45-.06-4.4-.15-6.2-.3h-.13c-2.76-.21-5.49-.52-8.1-.94l-1.68-.27v-10.52c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.74,1.72,2.7,4.03,2.7,6.49v12.05h-2Zm22.07-15.26c0-.43.03-.91.1-1.38.68-4.47,4.59-7.82,9.1-7.82,2.44,0,4.74.96,6.49,2.69,1.75,1.76,2.71,4.07,2.71,6.51v5.57s-.54.58-.54.58c-1.71,1.81-4.39,3.42-7.97,4.79-2.19.83-4.7,1.56-7.45,2.17l-2.43.54v-13.65Zm146.15-19.7c0-4.73,3.84-8.58,8.57-8.58,2.29,0,4.44.89,6.06,2.52,1.3,1.3,2.14,2.96,2.41,4.79.06.33.1.77.1,1.28v9.76s-1.68.27-1.68.27c-2.09.33-4.23.59-6.38.76-2.15.2-4.54.33-7.04.39l-2.04.04v-11.22Zm-2.43,11.18c-.08,0-.17,0-.25,0-2.07-.04-3.9-.13-5.68-.27h-.13c-2.55-.2-5.05-.49-7.41-.86l-1.69-.27v-9.37s6.25-1.01,6.25-1.01c3.03-.49,5.97-1.43,8.74-2.8l1,2.01,1-.21c.11.52.16,1.06.16,1.61v11.18h-2Zm20.01-14.12c0-.37.03-.76.08-1.24.65-4.21,4.29-7.33,8.49-7.33,2.28,0,4.43.89,6.06,2.5,1.63,1.63,2.53,3.79,2.53,6.07v5.17s-.55.58-.55.58c-1.6,1.68-4.07,3.17-7.35,4.42-1.95.75-4.25,1.41-6.83,1.99l-2.43.54v-12.69Zm-240.96-5.95c-.09,0-.18,0-.26,0-2.12-.05-3.94-.15-5.66-.28h-.13c-2.46-.18-4.95-.47-7.41-.86l-1.69-.27v-9.77c0-4.31,3.22-7.96,7.49-8.5l1.41-.18,4.2,8.42c1.01,2.01,2.23,3.91,3.61,5.63l.44.55v5.27h-2Z" fill="#fff"/><path d="m14.35,190.88l25.24,50.06c-1.34-.76-2.85-1.16-4.42-1.16-4.96,0-9,4.04-9,9v7.45l-2.45-.57c-1.67-.39-3.27-.84-4.76-1.33-3.39-1.17-9.03-3.68-9.03-7.47v-39.14c.16-6.06,1.7-11.84,4.42-16.84Zm275.98,14.02c-.1-.12-.2-.23-.31-.34l-17.42,42.87c-2.38,5.85-6.64,10.61-11.98,13.64.68.38,1.32.87,1.89,1.44.29.29.55.59.81.93l1.59,2.14,1.61-2.13c1.72-2.28,4.34-3.59,7.18-3.59,2.38,0,4.63.93,6.33,2.61l1.96,1.94,1.23-2.47c1.53-3.05,4.7-5.02,8.06-5.02,1.64,0,3.25.45,4.65,1.28l3.03,1.82v-32.27c-.24-8.66-3.3-16.77-8.63-22.85Zm-163.34,17.02c-6.75-7.69-16.31-11.75-27.65-11.75s-20.91,4.06-27.66,11.75c-5.81,6.62-9.14,15.45-9.39,24.92v42.82c0,4.03,6.09,6.73,9.75,7.99,1.62.53,3.37,1.02,5.19,1.45l2.46.58v-7.92c0-5.53,4.5-10.03,10.03-10.03,2.67,0,5.19,1.04,7.11,2.95.32.32.62.66.91,1.04l1.59,2.14,1.61-2.13c1.92-2.55,4.84-4.01,8.01-4.01,2.66,0,5.16,1.03,7.05,2.91l1.96,1.94,1.24-2.47c1.7-3.4,5.23-5.6,8.99-5.6,1.83,0,3.62.49,5.18,1.43l3.03,1.81v-34.97c-.25-9.42-3.59-18.26-9.39-24.87ZM332.31,60.61c-12.06-21.14-27.47-44.23-52.73-52.68-20.59-7.09-44.02-1.54-59.24,13.76-10.03,9.84-15.89,22.44-19.46,35.27-6.24,23.59-7.07,46.88-7.01,70.75.07,23.56,3.41,44.38,5.07,56.48,1.23,8.87,2.45,16.24,3.36,21.46-4.12,2.78-8.46,6.59-12.06,11.81-9.35,13.58-8.75,29.48-6.64,40.36,1.26,6.49,3.04,11.19,3.59,12.58,5.91,1.03,25.44,4.02,44.35-1.25,1.59,2.58,5.68,4.35,8.37,5.26,1.49.5,3.1.94,4.76,1.33l2.46.58v-7.45c0-1.4.32-2.72.9-3.91l-9.1,1.66c6.45-2.65,12.6-6.46,17.84-11.88,12.54-12.97,15.02-30.01,19.42-60.94,3.06-21.5,3.21-39.68,2.73-52.52,11.25,1.45,23.01-.57,33.51-6.77,6.36-3.67,11.69-8.43,15.91-13.91,12.92-16.83,15.23-40.44,3.98-59.97Z" fill="#26bd65"/><path d="m272.86,215.67c-.92,5.25-1.89,9.97-3.05,14.26l3.05-14.26Z" fill="#26bd65"/><path d="m112.87,102.44c-5.02-.03-9.86-1.12-14.38-3.23l-19.37-9.02c-1.93-.91-2.91-2.84-2.5-4.93.41-2.09,2.05-3.51,4.18-3.61l94.48-4.69c.5-.03,1.02-.04,1.54-.05h.22c1.94,0,3.92.15,5.88.45l10.68,1.68-.25,1.93c-.8,6.24-1.39,12.79-1.75,19.49l-.08,1.6-1.58.26c-1.82.3-3.71.46-5.6.46l-71.47-.35Z" fill="#b3e5fc"/><path d="m280.91,177.2c.86-10.19,1.2-20.5,1-30.64l-.04-2.1,2.1.07c.63.02,1.26.03,1.89.03.89,0,1.77-.02,2.66-.06l1.53-.07.46,1.46c2.85,9.01,2.29,18.71-1.56,27.32-1.06,2.36-2.77,4.38-4.94,5.83l-3.45,2.3.35-4.14Zm-137.27,20.12c2.2,0,4.4-.51,6.37-1.47l44.66-21.73-.19-1.43c-1.58-11.73-3.39-27.44-3.44-44.98,0-4.91,0-10.76.16-16.86v-.46s-.45-1.03-.45-1.03c-1.65-3.67-5.31-6.06-9.32-6.08l-68.83-.34c-4.28,0-8.15,2.7-9.63,6.71l-15.99,43.19c-2.47,6.69.22,14.12,6.38,17.67l42.99,24.85c2.22,1.28,4.74,1.96,7.3,1.96h0Zm-110.02-17.32c2.26,0,4.49-.38,6.62-1.12l32.98-11.48c5.71-1.99,10.19-6.38,12.28-12.04l16.11-43.53c1.88-5.07-.49-10.83-5.39-13.12l-22.31-10.4c-2.09-.98-4.33-1.47-6.64-1.47h-.3c-5.08.09-9.89,2.75-12.88,7.1l-38.98,57.01c-4.92,7.21-3.45,17.12,3.36,22.58l2.55,2.04c3.56,2.85,8.04,4.43,12.61,4.43h0Z" fill="#4fc3f7"/><path d="m162.77,274.65c-4.85,0-9.04-3.62-9.74-8.43l-7.79-53.52c-1.16-7.93,2.91-15.63,10.12-19.15l39.32-19.13,1.07,7.47.38,2.67c.87,6.3,1.84,12.55,2.86,18.57l.2,1.19-.96.73c-4.05,3.09-7.53,6.72-10.34,10.78-9.89,14.37-9.31,31.05-7.08,42.51.89,4.58,2.04,8.32,2.84,10.66l.77,2.24-20.06,3.27c-.57.09-1.06.13-1.53.13h-.06Zm-17.7-.33c-.45,0-.91-.04-1.36-.12l-1.65-.29v-27.24c-.51-20.29-14.11-42.18-42.73-42.18-20.07,0-42,13.21-42.73,42.23v8.28s-3.45-3.63-3.45-3.63c-2.33-2.45-4.07-5.46-5.05-8.7l-13.75-45.29c-2.45-8.07,1.87-16.57,9.84-19.34l31.41-10.93c1.66-.58,3.39-.87,5.16-.87,2.75,0,5.46.73,7.83,2.1l47.65,27.55c4.17,2.41,6.96,6.53,7.67,11.3l8.52,58.55c.32,2.2-.3,4.31-1.73,5.96-1.4,1.64-3.45,2.6-5.61,2.62h-.01ZM11.32,173.97l3.04-2.46,10.23,8.2c3.53,2.82,6.09,6.6,7.4,10.93l12.23,40.27-3.7,1.48-29.2-58.42Zm264.02,43.95c1.26-6.95,2.36-14.63,3.63-23.52l.03-.22c.14-.99.27-2.01.4-3.02v-.09s.12-.5.12-.5c.9-4.25,2.89-8.25,5.75-11.55l8.28-9.59,3.39,2.01-17.75,47.54-3.84-1.06Z" fill="#0288d1"/><path d="m252.88,68.7c-5.28,0-9.56,4.28-9.56,9.56s4.28,9.56,9.56,9.56,9.56-4.28,9.56-9.56-4.28-9.56-9.56-9.56Z" fill="#263238"/><circle cx="317.93" cy="78.26" r="9.56" fill="#263238"/><path d="m310.52,94.56c-1.41,1.55-2.91,2.72-4.44,3.48-1.11.55-2.21.86-3.28.99-.16.02-.32.04-.47.04h-.01c-2.84.2-5.46-.84-7.57-2.15-1.62-.8-3-1.71-4.07-2.5-.4-.29-.88-.49-1.4-.58-.5.12-.96.4-1.29.8-1.53,1.84-4.48,4.75-8.41,5.73-.71.19-1.44.31-2.21.35-.53.02-1.05.01-1.55-.04-4.13-.39-7.31-3.05-8.95-4.71-.94-.96-2.47-.98-3.43-.05-.95.94-.97,2.47-.04,3.43,2.22,2.26,6.68,5.91,12.67,6.2.25.01.5.02.76.02s.52,0,.78-.02c.74-.04,1.45-.12,2.14-.25,4.49-.83,8-3.46,10.31-5.76,4.11,3.18,8.49,4.67,12.69,4.36.66-.05,1.32-.14,1.98-.29,1.19-.25,2.35-.66,3.49-1.22,2.08-1.03,4.06-2.57,5.88-4.56.9-.99.83-2.52-.16-3.42-.99-.9-2.52-.83-3.42.15Z" fill="#263238"/><path d="m336.51,58.2c-13.31-23.33-29.23-46.12-55.36-54.86-22.35-7.7-47.57-1.84-64.2,14.87-9.54,9.36-16.33,21.61-20.75,37.49-1.17,4.42-2.14,8.84-2.97,13.23l-10.17-1.61c-1.8-.28-3.63-.42-5.44-.4-.46,0-.93.01-1.39.04l-103.33,5.13c-10.32.51-19.96,5.9-25.79,14.43L5.8,146.95c-6.81,9.95-7.69,22.82-2.3,33.61l4.02,8.04c-2.64,5.28-4.25,11.58-4.44,18.94v39.31c0,4.21,2.36,10.04,13.68,13.95,7.73,2.53,16.68,3.8,26.69,3.8,3,0,6.1-.11,9.28-.35.61.31,1.24.6,1.87.87v24.54c0,4.61,2.59,10.99,14.97,15.26,8.46,2.77,18.25,4.16,29.2,4.16,3.6,0,7.31-.14,11.16-.44.1-.01.22-.02.33-.04,1.07-.16,2.22-.31,3.42-.47,11.06-1.45,27.76-3.65,30.3-17.6.05-.29.08-.58.08-.87v-5.25l3.96.83c2.3.48,4.66.71,7.02.7,1.7-.02,3.41-.16,5.09-.43l53.15-8.66c4.63-.23,9.46-.77,14.32-1.78,2.08,2.1,5.27,4.13,10.11,5.8,7.74,2.53,16.68,3.81,26.7,3.81,3.28,0,6.68-.13,10.19-.41.1-.01.21-.02.3-.03.99-.15,2.04-.29,3.14-.43,10.1-1.33,25.37-3.33,27.69-16.09.05-.26.07-.53.07-.79v-39.31c-.27-10.59-3.49-18.97-8.44-25.29l10.22-27.36c3.82-10.22,2.37-21.67-3.86-30.62l-.36-.52c3.98-1.28,7.82-2.99,11.48-5.15,6.77-3.91,12.6-9.01,17.33-15.16,8.11-10.56,12.24-23.19,12.24-35.89,0-10.11-2.61-20.25-7.9-29.43Zm-147.48,69.5c.05,17.37,1.84,33.18,3.46,45.24l-43.36,21.1c-3.75,1.83-8.17,1.67-11.79-.42l-42.99-24.85c-5.31-3.06-7.63-9.51-5.51-15.25l15.99-43.19c1.2-3.26,4.32-5.42,7.8-5.4l68.77.34c3.25.02,6.18,1.94,7.51,4.9l.28.62c-.15,5.83-.17,11.48-.16,16.91Zm-110.44-42.06c.18-.92.88-1.93,2.31-2l94.48-4.69c.49-.03.99-.04,1.48-.05,1.92,0,3.86.14,5.76.43l8.75,1.38c-.86,6.67-1.42,13.23-1.76,19.64-1.8.3-3.63.44-5.45.43l-71.28-.35c-4.68-.03-9.3-1.06-13.54-3.04l-19.37-9.02c-1.29-.61-1.56-1.81-1.38-2.73Zm-61.84,66.43l38.98-57.01c2.56-3.74,6.73-6.15,11.26-6.23,2.11-.04,4.16.39,6.07,1.28l22.31,10.4c3.99,1.86,5.89,6.48,4.36,10.61l-16.11,43.53c-1.88,5.09-5.94,9.07-11.06,10.85l-32.98,11.48c-5.9,2.05-12.44.93-17.32-2.98l-2.55-2.04c-6-4.81-7.3-13.54-2.96-19.89Zm7.42,96.7v4.93c-1.58-.37-3.11-.8-4.59-1.28-4.73-1.63-7.65-3.76-7.65-5.57v-39.14c.07-2.67.44-5.38,1.13-8.03l19.23,38.48c-4.67,1.26-8.12,5.54-8.12,10.61Zm17.58,9.19c-.11,0-.21,0-.3,0-1.99-.05-3.8-.14-5.54-.28h-.07c-2.48-.18-4.92-.47-7.24-.84v-8.06c0-3.34,2.5-6.11,5.74-6.52l3.57,7.15c1.08,2.15,2.37,4.16,3.84,5.99v2.57Zm-28.64-84.89l10.23,8.2c3.2,2.56,5.55,6.03,6.74,9.95l12.23,40.27-29.2-58.42Zm83.8,128.74c-.12,0-.23-.01-.33-.01-2.17-.05-4.16-.14-6.06-.3h-.07c-2.72-.2-5.38-.51-7.93-.92v-8.82c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.09,2.11,1.36,1.35,2.11,3.16,2.11,5.08v10.05Zm19.23-1.23c-2.19.35-4.46.62-6.8.81-2.46.23-5,.36-7.59.42v-10.05c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.08,2.11,1.11,1.1,1.8,2.48,2.03,4.02.06.34.09.69.09,1.06v8.82Zm1.26-17.37c-2.26-2.24-5.27-3.48-8.46-3.48-3.92,0-7.41,1.89-9.6,4.8-.34-.46-.71-.88-1.12-1.28-2.27-2.26-5.29-3.52-8.51-3.52-6.64,0-12.03,5.4-12.03,12.03v5.39c-1.73-.4-3.41-.87-5.02-1.4-5.17-1.78-8.38-4.11-8.38-6.09v-42.82c.45-16.71,11.6-34.68,35.06-34.68s34.6,17.96,35.04,34.68v31.39c-1.86-1.12-3.99-1.72-6.2-1.72-1.75,0-3.43.38-4.95,1.07-2.51,1.15-4.59,3.14-5.83,5.63Zm17.98,10.12c-1.49,1.58-3.97,3.05-7.23,4.3-2.09.79-4.5,1.49-7.17,2.08v-11.16c0-.36.03-.74.08-1.1.53-3.47,3.59-6.1,7.12-6.1,1.91,0,3.72.76,5.08,2.11,1.36,1.37,2.12,3.18,2.12,5.09v4.78Zm13.8-22.95c-1.04,1.22-2.54,1.92-4.11,1.93-.33,0-.67-.03-1.01-.09v-25.56c-.77-30.35-23.73-44.18-44.73-44.18s-43.97,13.83-44.73,44.18v3.32c-2.09-2.2-3.68-4.89-4.59-7.9l-13.75-45.29c-2.13-7.01,1.66-14.46,8.58-16.87l31.41-10.93c3.75-1.31,7.89-.92,11.33,1.07l47.65,27.55c3.62,2.09,6.08,5.72,6.69,9.86l8.52,58.55c.23,1.6-.21,3.15-1.26,4.36Zm29.67-11.66c.9,4.63,2.05,8.43,2.91,10.93l-17.72,2.89c-.4.06-.81.1-1.21.1-3.9.03-7.26-2.86-7.82-6.72l-7.79-53.52c-1.03-7.05,2.62-13.94,9.02-17.06l36.86-17.93c.16,1.11.31,2.2.46,3.23.21,1.49.41,2.89.59,4.19.86,6.19,1.82,12.45,2.87,18.63-4.23,3.23-7.84,7-10.77,11.23-10.28,14.94-9.7,32.19-7.4,44.03Zm66.28,15.06c-1.59-.37-3.12-.8-4.59-1.29-.05-.01-.1-.03-.15-.05l4.74-.77v2.11Zm17.57-4.93v9.18c-.1,0-.2,0-.3,0-1.98-.04-3.8-.13-5.54-.27h-.06c-2.49-.19-4.92-.47-7.25-.84v-5.96l4.57-.74c3.28-.53,6.41-1.55,9.31-2.98-.28.14-.56.28-.85.41.08.39.12.8.12,1.21Zm17.58,8.06c-2,.32-4.07.57-6.22.74-2.24.21-4.56.33-6.93.38v-9.18c0-3.62,2.94-6.58,6.57-6.58,1.75,0,3.41.69,4.65,1.93,1.01,1.01,1.64,2.27,1.85,3.67.06.31.08.64.08.98v8.06Zm1.15-15.88c-1.78-1.76-4.06-2.84-6.52-3.11-.05.06-.1.12-.15.18,2.15-2.7,3.91-5.74,5.15-9.06l13.45-35.99c2.27,4.62,3.46,9.74,3.6,14.74v28.69c-1.7-1.02-3.65-1.57-5.68-1.57-4.23,0-8.01,2.47-9.85,6.12Zm2.48-114.52c1.58.05,3.14.04,4.7-.03,2.69,8.51,2.17,17.75-1.48,25.9-.94,2.09-2.43,3.78-4.23,4.98.87-10.27,1.21-20.58,1.01-30.85Zm2.87,33.81l8.28-9.59-17.75,47.54c1.28-7.05,2.4-14.92,3.67-23.82.14-1.01.27-2.03.4-3.05l.09-.41c.84-3.95,2.67-7.62,5.31-10.67Zm11.09,85.59v4.37c-1.37,1.44-3.63,2.79-6.61,3.93-1.91.73-4.11,1.36-6.55,1.9v-10.2c0-.33.03-.67.07-1,.49-3.18,3.28-5.57,6.5-5.57,1.76,0,3.41.69,4.65,1.92,1.25,1.25,1.94,2.91,1.94,4.65Zm26.62-148.29c-3.95,5.14-8.82,9.4-14.53,12.7-5.05,2.98-10.52,4.96-16.21,5.9-4.62.77-9.39.85-14.21.23-.06-.01-.12-.01-.18-.01-.02-.01-.04-.01-.07-.02-6.87-.53-13.53-2.18-19.78-4.9-3.9-1.7-7.61-3.8-11.05-6.25-4.46-3.16-8.49-6.9-11.99-11.11-1.7-2.06-4.75-2.34-6.81-.63-2.06,1.7-2.34,4.75-.64,6.81,4.04,4.86,8.69,9.17,13.83,12.82,3.98,2.83,8.28,5.26,12.79,7.23,5.92,2.58,12.14,4.31,18.55,5.2.37,15.84-.56,31.79-2.79,47.5-4.26,29.89-6.61,46.35-18.11,58.26-6.22,6.44-24.61,20.5-62.52,14.7-.72-2.13-1.67-5.33-2.42-9.17-1.94-10.03-2.5-24.53,5.87-36.69,1.44-2.08,3.08-4.01,4.92-5.8.14.56.25,1.12.39,1.68.32,1.29.67,2.59,1.04,3.88.61,2.12,2.55,3.5,4.65,3.5.45,0,.9-.06,1.34-.19,2.57-.74,4.06-3.42,3.32-5.99-.34-1.18-.66-2.37-.96-3.56-.73-2.91-1.35-5.9-1.84-8.86,0-.01,0-.02-.01-.03v-.02c-1.23-7.05-2.35-14.22-3.33-21.29-.18-1.31-.38-2.73-.59-4.23-1.75-12.31-4.38-30.92-4.44-51.61-.05-20.06.62-37.61,3.6-54.59.86-4.98,1.93-9.91,3.23-14.84,3.94-14.14,9.89-24.98,18.23-33.16,14.07-14.14,35.36-19.08,54.27-12.57,23.09,7.72,37.71,28.83,50.07,50.5,9.98,17.33,8.56,38.76-3.62,54.61Z" fill="#263238"/></g></svg>
\ No newline at end of file diff --git a/ui/src/assets/logo-light.svg b/ui/src/assets/logo-light.svg deleted file mode 100644 index 697df883..00000000 --- a/ui/src/assets/logo-light.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?><svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" width="1161.98" height="309.08" viewBox="0 0 1161.98 309.08"><g id="Layer_1-2"><path d="m99.75,291.76c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.4,1.38,2.29,3.16,2.59,5.13.07.41.11.87.11,1.36v10.52s-1.68.27-1.68.27c-2.22.36-4.56.63-6.95.83-2.36.22-4.89.36-7.71.43l-2.05.05v-12.1Zm-2.84,12.05c-.1,0-.19,0-.28,0-2.45-.06-4.4-.15-6.2-.3h-.13c-2.76-.21-5.49-.52-8.1-.94l-1.68-.27v-10.52c0-5.07,4.12-9.19,9.19-9.19,2.46,0,4.77.96,6.5,2.7,1.74,1.72,2.7,4.03,2.7,6.49v12.05h-2Zm22.07-15.26c0-.43.03-.91.1-1.38.68-4.47,4.59-7.82,9.1-7.82,2.44,0,4.74.96,6.49,2.69,1.75,1.76,2.71,4.07,2.71,6.51v5.57s-.54.58-.54.58c-1.71,1.81-4.39,3.42-7.97,4.79-2.19.83-4.7,1.56-7.45,2.17l-2.43.54v-13.65Zm146.15-19.7c0-4.73,3.84-8.58,8.57-8.58,2.29,0,4.44.89,6.06,2.52,1.3,1.3,2.14,2.96,2.41,4.79.06.33.1.77.1,1.28v9.76s-1.68.27-1.68.27c-2.09.33-4.23.59-6.38.76-2.15.2-4.54.33-7.04.39l-2.04.04v-11.22Zm-2.43,11.18c-.08,0-.17,0-.25,0-2.07-.04-3.9-.13-5.68-.27h-.13c-2.55-.2-5.05-.49-7.41-.86l-1.69-.27v-9.37s6.25-1.01,6.25-1.01c3.03-.49,5.97-1.43,8.74-2.8l1,2.01,1-.21c.11.52.16,1.06.16,1.61v11.18h-2Zm20.01-14.12c0-.37.03-.76.08-1.24.65-4.21,4.29-7.33,8.49-7.33,2.28,0,4.43.89,6.06,2.5,1.63,1.63,2.53,3.79,2.53,6.07v5.17s-.55.58-.55.58c-1.6,1.68-4.07,3.17-7.35,4.42-1.95.75-4.25,1.41-6.83,1.99l-2.43.54v-12.69Zm-240.96-5.95c-.09,0-.18,0-.26,0-2.12-.05-3.94-.15-5.66-.28h-.13c-2.46-.18-4.95-.47-7.41-.86l-1.69-.27v-9.77c0-4.31,3.22-7.96,7.49-8.5l1.41-.18,4.2,8.42c1.01,2.01,2.23,3.91,3.61,5.63l.44.55v5.27h-2Z" fill="#fff"/><path d="m14.35,190.88l25.24,50.06c-1.34-.76-2.85-1.16-4.42-1.16-4.96,0-9,4.04-9,9v7.45l-2.45-.57c-1.67-.39-3.27-.84-4.76-1.33-3.39-1.17-9.03-3.68-9.03-7.47v-39.14c.16-6.06,1.7-11.84,4.42-16.84Zm275.98,14.02c-.1-.12-.2-.23-.31-.34l-17.42,42.87c-2.38,5.85-6.64,10.61-11.98,13.64.68.38,1.32.87,1.89,1.44.29.29.55.59.81.93l1.59,2.14,1.61-2.13c1.72-2.28,4.34-3.59,7.18-3.59,2.38,0,4.63.93,6.33,2.61l1.96,1.94,1.23-2.47c1.53-3.05,4.7-5.02,8.06-5.02,1.64,0,3.25.45,4.65,1.28l3.03,1.82v-32.27c-.24-8.66-3.3-16.77-8.63-22.85Zm-163.34,17.02c-6.75-7.69-16.31-11.75-27.65-11.75s-20.91,4.06-27.66,11.75c-5.81,6.62-9.14,15.45-9.39,24.92v42.82c0,4.03,6.09,6.73,9.75,7.99,1.62.53,3.37,1.02,5.19,1.45l2.46.58v-7.92c0-5.53,4.5-10.03,10.03-10.03,2.67,0,5.19,1.04,7.11,2.95.32.32.62.66.91,1.04l1.59,2.14,1.61-2.13c1.92-2.55,4.84-4.01,8.01-4.01,2.66,0,5.16,1.03,7.05,2.91l1.96,1.94,1.24-2.47c1.7-3.4,5.23-5.6,8.99-5.6,1.83,0,3.62.49,5.18,1.43l3.03,1.81v-34.97c-.25-9.42-3.59-18.26-9.39-24.87ZM332.31,60.61c-12.06-21.14-27.47-44.23-52.73-52.68-20.59-7.09-44.02-1.54-59.24,13.76-10.03,9.84-15.89,22.44-19.46,35.27-6.24,23.59-7.07,46.88-7.01,70.75.07,23.56,3.41,44.38,5.07,56.48,1.23,8.87,2.45,16.24,3.36,21.46-4.12,2.78-8.46,6.59-12.06,11.81-9.35,13.58-8.75,29.48-6.64,40.36,1.26,6.49,3.04,11.19,3.59,12.58,5.91,1.03,25.44,4.02,44.35-1.25,1.59,2.58,5.68,4.35,8.37,5.26,1.49.5,3.1.94,4.76,1.33l2.46.58v-7.45c0-1.4.32-2.72.9-3.91l-9.1,1.66c6.45-2.65,12.6-6.46,17.84-11.88,12.54-12.97,15.02-30.01,19.42-60.94,3.06-21.5,3.21-39.68,2.73-52.52,11.25,1.45,23.01-.57,33.51-6.77,6.36-3.67,11.69-8.43,15.91-13.91,12.92-16.83,15.23-40.44,3.98-59.97Z" fill="#26bd65"/><path d="m272.86,215.67c-.92,5.25-1.89,9.97-3.05,14.26l3.05-14.26Z" fill="#26bd65"/><path d="m112.87,102.44c-5.02-.03-9.86-1.12-14.38-3.23l-19.37-9.02c-1.93-.91-2.91-2.84-2.5-4.93.41-2.09,2.05-3.51,4.18-3.61l94.48-4.69c.5-.03,1.02-.04,1.54-.05h.22c1.94,0,3.92.15,5.88.45l10.68,1.68-.25,1.93c-.8,6.24-1.39,12.79-1.75,19.49l-.08,1.6-1.58.26c-1.82.3-3.71.46-5.6.46l-71.47-.35Z" fill="#b3e5fc"/><path d="m280.91,177.2c.86-10.19,1.2-20.5,1-30.64l-.04-2.1,2.1.07c.63.02,1.26.03,1.89.03.89,0,1.77-.02,2.66-.06l1.53-.07.46,1.46c2.85,9.01,2.29,18.71-1.56,27.32-1.06,2.36-2.77,4.38-4.94,5.83l-3.45,2.3.35-4.14Zm-137.27,20.12c2.2,0,4.4-.51,6.37-1.47l44.66-21.73-.19-1.43c-1.58-11.73-3.39-27.44-3.44-44.98,0-4.91,0-10.76.16-16.86v-.46s-.45-1.03-.45-1.03c-1.65-3.67-5.31-6.06-9.32-6.08l-68.83-.34c-4.28,0-8.15,2.7-9.63,6.71l-15.99,43.19c-2.47,6.69.22,14.12,6.38,17.67l42.99,24.85c2.22,1.28,4.74,1.96,7.3,1.96h0Zm-110.02-17.32c2.26,0,4.49-.38,6.62-1.12l32.98-11.48c5.71-1.99,10.19-6.38,12.28-12.04l16.11-43.53c1.88-5.07-.49-10.83-5.39-13.12l-22.31-10.4c-2.09-.98-4.33-1.47-6.64-1.47h-.3c-5.08.09-9.89,2.75-12.88,7.1l-38.98,57.01c-4.92,7.21-3.45,17.12,3.36,22.58l2.55,2.04c3.56,2.85,8.04,4.43,12.61,4.43h0Z" fill="#4fc3f7"/><path d="m162.77,274.65c-4.85,0-9.04-3.62-9.74-8.43l-7.79-53.52c-1.16-7.93,2.91-15.63,10.12-19.15l39.32-19.13,1.07,7.47.38,2.67c.87,6.3,1.84,12.55,2.86,18.57l.2,1.19-.96.73c-4.05,3.09-7.53,6.72-10.34,10.78-9.89,14.37-9.31,31.05-7.08,42.51.89,4.58,2.04,8.32,2.84,10.66l.77,2.24-20.06,3.27c-.57.09-1.06.13-1.53.13h-.06Zm-17.7-.33c-.45,0-.91-.04-1.36-.12l-1.65-.29v-27.24c-.51-20.29-14.11-42.18-42.73-42.18-20.07,0-42,13.21-42.73,42.23v8.28s-3.45-3.63-3.45-3.63c-2.33-2.45-4.07-5.46-5.05-8.7l-13.75-45.29c-2.45-8.07,1.87-16.57,9.84-19.34l31.41-10.93c1.66-.58,3.39-.87,5.16-.87,2.75,0,5.46.73,7.83,2.1l47.65,27.55c4.17,2.41,6.96,6.53,7.67,11.3l8.52,58.55c.32,2.2-.3,4.31-1.73,5.96-1.4,1.64-3.45,2.6-5.61,2.62h-.01ZM11.32,173.97l3.04-2.46,10.23,8.2c3.53,2.82,6.09,6.6,7.4,10.93l12.23,40.27-3.7,1.48-29.2-58.42Zm264.02,43.95c1.26-6.95,2.36-14.63,3.63-23.52l.03-.22c.14-.99.27-2.01.4-3.02v-.09s.12-.5.12-.5c.9-4.25,2.89-8.25,5.75-11.55l8.28-9.59,3.39,2.01-17.75,47.54-3.84-1.06Z" fill="#0288d1"/><path d="m252.88,68.7c-5.28,0-9.56,4.28-9.56,9.56s4.28,9.56,9.56,9.56,9.56-4.28,9.56-9.56-4.28-9.56-9.56-9.56Z" fill="#263238"/><circle cx="317.93" cy="78.26" r="9.56" fill="#263238"/><path d="m310.52,94.56c-1.41,1.55-2.91,2.72-4.44,3.48-1.11.55-2.21.86-3.28.99-.16.02-.32.04-.47.04h-.01c-2.84.2-5.46-.84-7.57-2.15-1.62-.8-3-1.71-4.07-2.5-.4-.29-.88-.49-1.4-.58-.5.12-.96.4-1.29.8-1.53,1.84-4.48,4.75-8.41,5.73-.71.19-1.44.31-2.21.35-.53.02-1.05.01-1.55-.04-4.13-.39-7.31-3.05-8.95-4.71-.94-.96-2.47-.98-3.43-.05-.95.94-.97,2.47-.04,3.43,2.22,2.26,6.68,5.91,12.67,6.2.25.01.5.02.76.02s.52,0,.78-.02c.74-.04,1.45-.12,2.14-.25,4.49-.83,8-3.46,10.31-5.76,4.11,3.18,8.49,4.67,12.69,4.36.66-.05,1.32-.14,1.98-.29,1.19-.25,2.35-.66,3.49-1.22,2.08-1.03,4.06-2.57,5.88-4.56.9-.99.83-2.52-.16-3.42-.99-.9-2.52-.83-3.42.15Z" fill="#263238"/><path d="m336.51,58.2c-13.31-23.33-29.23-46.12-55.36-54.86-22.35-7.7-47.57-1.84-64.2,14.87-9.54,9.36-16.33,21.61-20.75,37.49-1.17,4.42-2.14,8.84-2.97,13.23l-10.17-1.61c-1.8-.28-3.63-.42-5.44-.4-.46,0-.93.01-1.39.04l-103.33,5.13c-10.32.51-19.96,5.9-25.79,14.43L5.8,146.95c-6.81,9.95-7.69,22.82-2.3,33.61l4.02,8.04c-2.64,5.28-4.25,11.58-4.44,18.94v39.31c0,4.21,2.36,10.04,13.68,13.95,7.73,2.53,16.68,3.8,26.69,3.8,3,0,6.1-.11,9.28-.35.61.31,1.24.6,1.87.87v24.54c0,4.61,2.59,10.99,14.97,15.26,8.46,2.77,18.25,4.16,29.2,4.16,3.6,0,7.31-.14,11.16-.44.1-.01.22-.02.33-.04,1.07-.16,2.22-.31,3.42-.47,11.06-1.45,27.76-3.65,30.3-17.6.05-.29.08-.58.08-.87v-5.25l3.96.83c2.3.48,4.66.71,7.02.7,1.7-.02,3.41-.16,5.09-.43l53.15-8.66c4.63-.23,9.46-.77,14.32-1.78,2.08,2.1,5.27,4.13,10.11,5.8,7.74,2.53,16.68,3.81,26.7,3.81,3.28,0,6.68-.13,10.19-.41.1-.01.21-.02.3-.03.99-.15,2.04-.29,3.14-.43,10.1-1.33,25.37-3.33,27.69-16.09.05-.26.07-.53.07-.79v-39.31c-.27-10.59-3.49-18.97-8.44-25.29l10.22-27.36c3.82-10.22,2.37-21.67-3.86-30.62l-.36-.52c3.98-1.28,7.82-2.99,11.48-5.15,6.77-3.91,12.6-9.01,17.33-15.16,8.11-10.56,12.24-23.19,12.24-35.89,0-10.11-2.61-20.25-7.9-29.43Zm-147.48,69.5c.05,17.37,1.84,33.18,3.46,45.24l-43.36,21.1c-3.75,1.83-8.17,1.67-11.79-.42l-42.99-24.85c-5.31-3.06-7.63-9.51-5.51-15.25l15.99-43.19c1.2-3.26,4.32-5.42,7.8-5.4l68.77.34c3.25.02,6.18,1.94,7.51,4.9l.28.62c-.15,5.83-.17,11.48-.16,16.91Zm-110.44-42.06c.18-.92.88-1.93,2.31-2l94.48-4.69c.49-.03.99-.04,1.48-.05,1.92,0,3.86.14,5.76.43l8.75,1.38c-.86,6.67-1.42,13.23-1.76,19.64-1.8.3-3.63.44-5.45.43l-71.28-.35c-4.68-.03-9.3-1.06-13.54-3.04l-19.37-9.02c-1.29-.61-1.56-1.81-1.38-2.73Zm-61.84,66.43l38.98-57.01c2.56-3.74,6.73-6.15,11.26-6.23,2.11-.04,4.16.39,6.07,1.28l22.31,10.4c3.99,1.86,5.89,6.48,4.36,10.61l-16.11,43.53c-1.88,5.09-5.94,9.07-11.06,10.85l-32.98,11.48c-5.9,2.05-12.44.93-17.32-2.98l-2.55-2.04c-6-4.81-7.3-13.54-2.96-19.89Zm7.42,96.7v4.93c-1.58-.37-3.11-.8-4.59-1.28-4.73-1.63-7.65-3.76-7.65-5.57v-39.14c.07-2.67.44-5.38,1.13-8.03l19.23,38.48c-4.67,1.26-8.12,5.54-8.12,10.61Zm17.58,9.19c-.11,0-.21,0-.3,0-1.99-.05-3.8-.14-5.54-.28h-.07c-2.48-.18-4.92-.47-7.24-.84v-8.06c0-3.34,2.5-6.11,5.74-6.52l3.57,7.15c1.08,2.15,2.37,4.16,3.84,5.99v2.57Zm-28.64-84.89l10.23,8.2c3.2,2.56,5.55,6.03,6.74,9.95l12.23,40.27-29.2-58.42Zm83.8,128.74c-.12,0-.23-.01-.33-.01-2.17-.05-4.16-.14-6.06-.3h-.07c-2.72-.2-5.38-.51-7.93-.92v-8.82c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.09,2.11,1.36,1.35,2.11,3.16,2.11,5.08v10.05Zm19.23-1.23c-2.19.35-4.46.62-6.8.81-2.46.23-5,.36-7.59.42v-10.05c0-3.96,3.22-7.19,7.19-7.19,1.92,0,3.73.75,5.08,2.11,1.11,1.1,1.8,2.48,2.03,4.02.06.34.09.69.09,1.06v8.82Zm1.26-17.37c-2.26-2.24-5.27-3.48-8.46-3.48-3.92,0-7.41,1.89-9.6,4.8-.34-.46-.71-.88-1.12-1.28-2.27-2.26-5.29-3.52-8.51-3.52-6.64,0-12.03,5.4-12.03,12.03v5.39c-1.73-.4-3.41-.87-5.02-1.4-5.17-1.78-8.38-4.11-8.38-6.09v-42.82c.45-16.71,11.6-34.68,35.06-34.68s34.6,17.96,35.04,34.68v31.39c-1.86-1.12-3.99-1.72-6.2-1.72-1.75,0-3.43.38-4.95,1.07-2.51,1.15-4.59,3.14-5.83,5.63Zm17.98,10.12c-1.49,1.58-3.97,3.05-7.23,4.3-2.09.79-4.5,1.49-7.17,2.08v-11.16c0-.36.03-.74.08-1.1.53-3.47,3.59-6.1,7.12-6.1,1.91,0,3.72.76,5.08,2.11,1.36,1.37,2.12,3.18,2.12,5.09v4.78Zm13.8-22.95c-1.04,1.22-2.54,1.92-4.11,1.93-.33,0-.67-.03-1.01-.09v-25.56c-.77-30.35-23.73-44.18-44.73-44.18s-43.97,13.83-44.73,44.18v3.32c-2.09-2.2-3.68-4.89-4.59-7.9l-13.75-45.29c-2.13-7.01,1.66-14.46,8.58-16.87l31.41-10.93c3.75-1.31,7.89-.92,11.33,1.07l47.65,27.55c3.62,2.09,6.08,5.72,6.69,9.86l8.52,58.55c.23,1.6-.21,3.15-1.26,4.36Zm29.67-11.66c.9,4.63,2.05,8.43,2.91,10.93l-17.72,2.89c-.4.06-.81.1-1.21.1-3.9.03-7.26-2.86-7.82-6.72l-7.79-53.52c-1.03-7.05,2.62-13.94,9.02-17.06l36.86-17.93c.16,1.11.31,2.2.46,3.23.21,1.49.41,2.89.59,4.19.86,6.19,1.82,12.45,2.87,18.63-4.23,3.23-7.84,7-10.77,11.23-10.28,14.94-9.7,32.19-7.4,44.03Zm66.28,15.06c-1.59-.37-3.12-.8-4.59-1.29-.05-.01-.1-.03-.15-.05l4.74-.77v2.11Zm17.57-4.93v9.18c-.1,0-.2,0-.3,0-1.98-.04-3.8-.13-5.54-.27h-.06c-2.49-.19-4.92-.47-7.25-.84v-5.96l4.57-.74c3.28-.53,6.41-1.55,9.31-2.98-.28.14-.56.28-.85.41.08.39.12.8.12,1.21Zm17.58,8.06c-2,.32-4.07.57-6.22.74-2.24.21-4.56.33-6.93.38v-9.18c0-3.62,2.94-6.58,6.57-6.58,1.75,0,3.41.69,4.65,1.93,1.01,1.01,1.64,2.27,1.85,3.67.06.31.08.64.08.98v8.06Zm1.15-15.88c-1.78-1.76-4.06-2.84-6.52-3.11-.05.06-.1.12-.15.18,2.15-2.7,3.91-5.74,5.15-9.06l13.45-35.99c2.27,4.62,3.46,9.74,3.6,14.74v28.69c-1.7-1.02-3.65-1.57-5.68-1.57-4.23,0-8.01,2.47-9.85,6.12Zm2.48-114.52c1.58.05,3.14.04,4.7-.03,2.69,8.51,2.17,17.75-1.48,25.9-.94,2.09-2.43,3.78-4.23,4.98.87-10.27,1.21-20.58,1.01-30.85Zm2.87,33.81l8.28-9.59-17.75,47.54c1.28-7.05,2.4-14.92,3.67-23.82.14-1.01.27-2.03.4-3.05l.09-.41c.84-3.95,2.67-7.62,5.31-10.67Zm11.09,85.59v4.37c-1.37,1.44-3.63,2.79-6.61,3.93-1.91.73-4.11,1.36-6.55,1.9v-10.2c0-.33.03-.67.07-1,.49-3.18,3.28-5.57,6.5-5.57,1.76,0,3.41.69,4.65,1.92,1.25,1.25,1.94,2.91,1.94,4.65Zm26.62-148.29c-3.95,5.14-8.82,9.4-14.53,12.7-5.05,2.98-10.52,4.96-16.21,5.9-4.62.77-9.39.85-14.21.23-.06-.01-.12-.01-.18-.01-.02-.01-.04-.01-.07-.02-6.87-.53-13.53-2.18-19.78-4.9-3.9-1.7-7.61-3.8-11.05-6.25-4.46-3.16-8.49-6.9-11.99-11.11-1.7-2.06-4.75-2.34-6.81-.63-2.06,1.7-2.34,4.75-.64,6.81,4.04,4.86,8.69,9.17,13.83,12.82,3.98,2.83,8.28,5.26,12.79,7.23,5.92,2.58,12.14,4.31,18.55,5.2.37,15.84-.56,31.79-2.79,47.5-4.26,29.89-6.61,46.35-18.11,58.26-6.22,6.44-24.61,20.5-62.52,14.7-.72-2.13-1.67-5.33-2.42-9.17-1.94-10.03-2.5-24.53,5.87-36.69,1.44-2.08,3.08-4.01,4.92-5.8.14.56.25,1.12.39,1.68.32,1.29.67,2.59,1.04,3.88.61,2.12,2.55,3.5,4.65,3.5.45,0,.9-.06,1.34-.19,2.57-.74,4.06-3.42,3.32-5.99-.34-1.18-.66-2.37-.96-3.56-.73-2.91-1.35-5.9-1.84-8.86,0-.01,0-.02-.01-.03v-.02c-1.23-7.05-2.35-14.22-3.33-21.29-.18-1.31-.38-2.73-.59-4.23-1.75-12.31-4.38-30.92-4.44-51.61-.05-20.06.62-37.61,3.6-54.59.86-4.98,1.93-9.91,3.23-14.84,3.94-14.14,9.89-24.98,18.23-33.16,14.07-14.14,35.36-19.08,54.27-12.57,23.09,7.72,37.71,28.83,50.07,50.5,9.98,17.33,8.56,38.76-3.62,54.61Z" fill="#263238"/><path d="m425.44,285.08h-44.92c-2.5,0-3.43-1.87-1.87-3.74l161.6-212.76c.94-1.56,2.5-1.87,4.06-1.87h44.92c1.87,0,3.12,1.25,3.12,3.12v212.14c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-1.25-3.12-3.12v-25.89c0-1.56-.94-2.5-2.5-2.5h-84.23c-1.25,0-2.5.62-3.12,1.56l-22.46,28.08c-.94,1.25-2.5,1.87-4.06,1.87Zm69.57-72.06h44.3c1.56,0,2.5-.94,2.5-2.5v-58.34c0-2.18-1.87-2.81-3.12-.94l-44.92,58.65c-1.25,1.87-.62,3.12,1.25,3.12Zm199.03-127.59v41.18c0,1.56.94,2.5,2.5,2.5h21.21c1.87,0,3.12,1.25,3.12,3.12v31.82c0,1.87-1.25,3.12-3.12,3.12h-21.21c-1.56,0-2.5.94-2.5,2.5v112.31c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-1.25-3.12-3.12v-112.31c0-1.56-.94-2.5-2.5-2.5h-23.71c-1.87,0-3.12-1.25-3.12-3.12v-31.82c0-1.87,1.25-3.12,3.12-3.12h23.71c1.56,0,2.5-.94,2.5-2.5v-41.18c0-1.87,1.25-3.12,3.12-3.12h44.3c1.87,0,3.12,1.25,3.12,3.12Zm96.08,46.8l.31,74.87c0,26.21,12.48,33.69,26.52,33.69,22.46,0,30.89-16.53,30.89-35.56v-73c0-1.87,1.25-3.12,3.12-3.12h44.3c1.87,0,3.12,1.25,3.12,3.12v149.74c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-1.25-3.12-3.12v-10.61c0-2.18-1.25-2.5-3.12-1.25-10.3,9.67-23.4,15.29-37.75,15.6-35.25.62-67.07-27.77-67.07-71.13l-.31-82.36c0-1.87,1.25-3.12,3.12-3.12h44.3c1.87,0,3.12.94,3.12,3.12Zm160.66-71.13c15.6,0,27.45,11.85,27.45,26.52s-12.17,26.52-27.45,26.52-27.45-11.85-27.45-26.52,12.17-26.52,27.45-26.52Zm-25.27,220.87v-149.74c0-1.87,1.25-3.12,3.12-3.12h44.3c1.87,0,3.12,1.25,3.12,3.12v149.74c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-1.25-3.12-3.12Zm185.93,0l-.31-74.25c0-26.21-12.48-33.69-26.52-33.69-22.46,0-30.89,16.53-30.89,35.56v72.38c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-1.25-3.12-3.12v-149.74c0-1.87,1.25-3.12,3.12-3.12h44.3c1.87,0,3.12,1.25,3.12,3.12v9.05c0,2.18,1.56,2.81,3.12,1.25,10.3-9.67,23.71-15.91,37.75-15.91,35.25,0,67.07,30.26,67.07,73.62l.31,81.74c0,1.87-1.25,3.12-3.12,3.12h-44.3c-1.87,0-3.12-.94-3.12-3.12Z" fill="#263238"/></g></svg>
\ No newline at end of file diff --git a/ui/src/assets/react.svg b/ui/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/ui/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file diff --git a/ui/src/components/Button.tsx b/ui/src/components/Button.tsx deleted file mode 100644 index 5f7e1160..00000000 --- a/ui/src/components/Button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export enum ButtonStyle { - PrimarySm = "bg-emerald-500 hover:bg-emerald-600", - PrimarySmFill = "bg-emerald-500 hover:bg-emerald-600 w-full text-sm", -} - -interface ButtonProps { - text: string; - style: ButtonStyle; -} - -export default function Button(props: ButtonProps) { - return ( - <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> on </span> - - <span className="relative truncate ">{h.host}</span> - - <span> in </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 } diff --git a/ui/src/global.d.ts b/ui/src/global.d.ts deleted file mode 100644 index d97caa3a..00000000 --- a/ui/src/global.d.ts +++ /dev/null @@ -1 +0,0 @@ -type Option<T> = T | null; diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts deleted file mode 100644 index db66b3be..00000000 --- a/ui/src/lib/utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ClassValue } from "clsx"; - -import clsx from "clsx"; -import { extendTailwindMerge } from "tailwind-merge"; - -const COMMON_UNITS = ["small", "medium", "large"]; - -/** - * We need to extend the tailwind merge to include NextUI's custom classes. - * - * So we can use classes like `text-small` or `text-default-500` and override them. - */ -const twMerge = extendTailwindMerge({ - extend: { - theme: { - opacity: ["disabled"], - spacing: ["divider"], - borderWidth: COMMON_UNITS, - borderRadius: COMMON_UNITS, - }, - classGroups: { - shadow: [{ shadow: COMMON_UNITS }], - "font-size": [{ text: ["tiny", ...COMMON_UNITS] }], - "bg-image": ["bg-stripe-gradient"], - }, - }, -}); - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - -// edge still uses the old one -export function getWeekInfo() { - let locale = new Intl.Locale(navigator.language); - - // @ts-ignore - if (locale.getWeekInfo) { - // @ts-ignore - return locale.getWeekInfo(); - // @ts-ignore - } else if (locale.weekInfo) { - // @ts-ignore - return locale.weekInfo; - } - - throw new Error("Could not fetch week info via new or old api"); -} diff --git a/ui/src/main.tsx b/ui/src/main.tsx deleted file mode 100644 index 5fddc82f..00000000 --- a/ui/src/main.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { NextUIProvider } from "@nextui-org/react"; -import App from "./App"; -import "./styles.css"; - -ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - <React.StrictMode> - <NextUIProvider> - <main className="text-foreground bg-background"> - <div - data-tauri-drag-region - className="w-full min-h-8 z-10 border-b-1" - /> - - <div className="z-20 "> - <App /> - </div> - </main> - </NextUIProvider> - </React.StrictMode>, -); diff --git a/ui/src/pages/Dotfiles.tsx b/ui/src/pages/Dotfiles.tsx deleted file mode 100644 index 85f5b0e0..00000000 --- a/ui/src/pages/Dotfiles.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useState } from "react"; -import Aliases from "@/components/dotfiles/Aliases"; -import Vars from "@/components/dotfiles/Vars"; - -enum Section { - Aliases, - Vars, - Snippets, -} - -function renderDotfiles(current: Section) { - switch (current) { - case Section.Aliases: - return <Aliases />; - case Section.Vars: - return <Vars />; - case Section.Snippets: - return <div />; - } -} - -interface HeaderProps { - current: Section; - setCurrent: (section: Section) => void; -} - -interface TabsProps { - current: Section; - setCurrent: (section: Section) => void; -} - -function Header({ current, setCurrent }: HeaderProps) { - return ( - <div className="md:flex md:items-center md:justify-between"> - <div className="min-w-0 flex-1"> - <h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight"> - Dotfiles - </h2> - </div> - - <Tabs current={current} setCurrent={setCurrent} /> - </div> - ); -} - -function classNames(...classes: any[]) { - return classes.filter(Boolean).join(" "); -} - -function Tabs({ current, setCurrent }: TabsProps) { - let tabs = [ - { - name: "Aliases", - isCurrent: () => current === Section.Aliases, - section: Section.Aliases, - }, - { - name: "Vars", - isCurrent: () => current === Section.Vars, - section: Section.Vars, - }, - { - name: "Snippets", - isCurrent: () => current === Section.Snippets, - section: Section.Snippets, - }, - ]; - - return ( - <div> - <div> - <nav className="flex space-x-4" aria-label="Tabs"> - {tabs.map((tab) => ( - <button - onClick={() => { - setCurrent(tab.section); - }} - key={tab.name} - className={classNames( - tab.isCurrent() - ? "bg-gray-100 text-gray-700" - : "text-gray-500 hover:text-gray-700", - "rounded-md px-3 py-2 text-sm font-medium", - )} - aria-current={tab.isCurrent() ? "page" : undefined} - > - {tab.name} - </button> - ))} - </nav> - </div> - </div> - ); -} - -export default function Dotfiles() { - let [current, setCurrent] = useState(Section.Aliases); - console.log(current); - - return ( - <div className="w-full flex-1 flex-col p-4 overflow-y-auto"> - <div className="p-10"> - <Header current={current} setCurrent={setCurrent} /> - Manage your shell aliases, variables and paths - {renderDotfiles(current)} - </div> - </div> - ); -} diff --git a/ui/src/pages/History.tsx b/ui/src/pages/History.tsx deleted file mode 100644 index 32f5217e..00000000 --- a/ui/src/pages/History.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useEffect, useState, useRef } from "react"; -import { useVirtualizer } from "@tanstack/react-virtual"; - -import HistoryList from "@/components/HistoryList.tsx"; -import HistorySearch from "@/components/HistorySearch.tsx"; - -import { AtuinState, useStore } from "@/state/store"; - -export default function Search() { - const history = useStore((state: AtuinState) => state.shellHistory); - const refreshHistory = useStore( - (state: AtuinState) => state.refreshShellHistory, - ); - const historyNextPage = useStore( - (state: AtuinState) => state.historyNextPage, - ); - - let [query, setQuery] = useState(""); - - useEffect(() => { - (async () => { - // nothing rn - })(); - - refreshHistory(); - }, []); - - const parentRef = useRef<HTMLElement | null>(null); - - const rowVirtualizer = useVirtualizer({ - count: history.length, - getScrollElement: () => parentRef.current, - estimateSize: () => 90, - overscan: 5, - }); - - useEffect(() => { - const [lastItem] = rowVirtualizer.getVirtualItems().slice(-1); - - if (!lastItem) return; // no undefined plz - if (lastItem.index < history.length - 1) return; // if we're not at the end yet, bail - - // we're at the end! more rows plz! - historyNextPage(query); - }, [rowVirtualizer.getVirtualItems()]); - - return ( - <> - <div className="w-full flex-1 flex-col"> - <div className="flex h-16 shrink-0 items-center gap-x-4 border-b border-t border-gray-200 bg-white px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8 history-search"> - <HistorySearch - query={query} - setQuery={(q) => { - setQuery(q); - refreshHistory(q); - }} - refresh={() => { - refreshHistory(query); - }} - /> - </div> - - <main className="overflow-y-scroll history-list" ref={parentRef}> - <HistoryList - history={history} - items={rowVirtualizer.getVirtualItems()} - height={rowVirtualizer.getTotalSize()} - /> - </main> - </div> - </> - ); -} diff --git a/ui/src/pages/Home.tsx b/ui/src/pages/Home.tsx deleted file mode 100644 index 2e93a893..00000000 --- a/ui/src/pages/Home.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import React, { useEffect } from "react"; -import { formatRelative } from "date-fns"; -import { Tooltip as ReactTooltip } from "react-tooltip"; - -import { AtuinState, useStore } from "@/state/store"; -import { useToast } from "@/components/ui/use-toast"; -import { ToastAction } from "@/components/ui/toast"; -import { invoke } from "@tauri-apps/api/core"; -import { - Card, - CardHeader, - CardBody, - Listbox, - ListboxItem, -} from "@nextui-org/react"; - -import { - Bar, - BarChart, - CartesianGrid, - LabelList, - XAxis, - YAxis, -} from "recharts"; -import { ChartConfig, ChartContainer } from "@/components/ui/chart"; - -import { Clock, Terminal } from "lucide-react"; - -import ActivityCalendar from "react-activity-calendar"; -import HistoryRow from "@/components/history/HistoryRow"; -import { ShellHistory } from "@/state/models"; - -function StatCard({ name, stat }: any) { - return ( - <Card shadow="sm"> - <CardHeader> - <h3 className="uppercase text-gray-500">{name}</h3> - </CardHeader> - <CardBody> - <h2 className="font-bold text-xl">{stat}</h2> - </CardBody> - </Card> - ); -} - -function TopChart({ chartData }: any) { - const chartConfig = { - command: { - label: "Command", - color: "#c4edde", - }, - } satisfies ChartConfig; - - return ( - <ChartContainer config={chartConfig} className="max-h-72"> - <BarChart - accessibilityLayer - data={chartData} - layout="vertical" - margin={{ - right: 16, - }} - > - <CartesianGrid horizontal={false} /> - <YAxis - dataKey="command" - type="category" - tickLine={false} - tickMargin={10} - axisLine={false} - tickFormatter={(value) => value.slice(0, 3)} - hide - /> - <XAxis dataKey="count" type="number" hide /> - <Bar dataKey="count" layout="vertical" fill="#c4edde" radius={4}> - <LabelList - dataKey="command" - position="insideLeft" - offset={8} - className="fill-[--color-label]" - fontSize={12} - /> - <LabelList - dataKey="count" - position="right" - offset={8} - className="fill-foreground" - fontSize={12} - /> - </Bar> - </BarChart> - </ChartContainer> - ); -} - -function Header({ name }: any) { - let greeting = name && name.length > 0 ? "Hey, " + name + "!" : "Hey!"; - - return ( - <div className="md:flex md:items-center md:justify-between"> - <div className="flex-1"> - <h2 className="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight"> - {greeting} - </h2> - <h3 className="text-xl leading-7 text-gray-900 pt-4"> - Welcome to{" "} - <a - href="https://atuin.sh" - target="_blank" - rel="noopener noreferrer nofollow" - > - Atuin - </a> - . - </h3> - </div> - </div> - ); -} - -const explicitTheme = { - light: ["#f0f0f0", "#c4edde", "#7ac7c4", "#f73859", "#384259"], - dark: ["#f0f0f0", "#c4edde", "#7ac7c4", "#f73859", "#384259"], -}; - -export default function Home() { - const homeInfo = useStore((state: AtuinState) => state.homeInfo); - const user = useStore((state: AtuinState) => state.user); - const calendar = useStore((state: AtuinState) => state.calendar); - const runbooks = useStore((state: AtuinState) => state.runbooks); - const weekStart = useStore((state: AtuinState) => state.weekStart); - - const refreshHomeInfo = useStore( - (state: AtuinState) => state.refreshHomeInfo, - ); - const refreshUser = useStore((state: AtuinState) => state.refreshUser); - const refreshCalendar = useStore( - (state: AtuinState) => state.refreshCalendar, - ); - const refreshRunbooks = useStore( - (state: AtuinState) => state.refreshRunbooks, - ); - - const { toast } = useToast(); - - useEffect(() => { - refreshHomeInfo(); - refreshUser(); - refreshCalendar(); - refreshRunbooks(); - - console.log(homeInfo); - - let setup = async () => { - let installed = await invoke("is_cli_installed"); - console.log("CLI installation status:", installed); - - if (!installed) { - toast({ - title: "Atuin CLI", - description: "CLI not detected - install?", - action: ( - <ToastAction - altText="Install" - onClick={() => { - let install = async () => { - toast({ - title: "Atuin CLI", - description: "Install in progress...", - }); - - console.log("Installing CLI..."); - await invoke("install_cli"); - - console.log("Setting up plugin..."); - await invoke("setup_cli"); - - toast({ - title: "Atuin CLI", - description: "Installation complete", - }); - }; - install(); - }} - > - Install - </ToastAction> - ), - }); - } - }; - - setup(); - }, []); - - if (!homeInfo) { - return <div>Loading...</div>; - } - - return ( - <div className="w-full flex-1 flex-col p-4 overflow-y-auto"> - <div className="pl-10"> - <Header name={user.username} /> - </div> - <div className="p-10 grid grid-cols-4 gap-4"> - <StatCard - name="Last Sync" - stat={ - (homeInfo.lastSyncTime && - formatRelative(homeInfo.lastSyncTime, new Date())) || - "Never" - } - /> - <StatCard - name="Total Commands" - stat={homeInfo.historyCount.toLocaleString()} - /> - <StatCard - name="Total Runbooks" - stat={runbooks.length.toLocaleString()} - /> - <StatCard - name="Other Records" - stat={homeInfo.recordCount - homeInfo.historyCount} - /> - - <Card shadow="sm" className="col-span-3"> - <CardHeader> - <h2 className="uppercase text-gray-500">Activity graph</h2> - </CardHeader> - <CardBody> - <ActivityCalendar - hideTotalCount - theme={explicitTheme} - data={calendar} - weekStart={weekStart as any} - renderBlock={(block, activity) => - React.cloneElement(block, { - "data-tooltip-id": "react-tooltip", - "data-tooltip-html": `${activity.count} commands on ${activity.date}`, - }) - } - /> - <ReactTooltip id="react-tooltip" /> - </CardBody> - </Card> - - <Card shadow="sm"> - <CardHeader> - <h2 className="uppercase text-gray-500">Quick actions </h2> - </CardHeader> - - <CardBody> - <Listbox variant="flat" aria-label="Quick actions"> - <ListboxItem - key="new-runbook" - description="Create an executable runbook" - startContent={<Terminal />} - > - New runbook - </ListboxItem> - <ListboxItem - key="shell-history" - description="Search and explore shell history" - startContent={<Clock />} - > - Shell History - </ListboxItem> - </Listbox> - </CardBody> - </Card> - - <Card shadow="sm" className="col-span-2"> - <CardHeader> - <h2 className="uppercase text-gray-500">Recent commands</h2> - </CardHeader> - <CardBody> - {homeInfo.recentCommands?.map((i: ShellHistory) => { - return <HistoryRow compact h={i} />; - })} - </CardBody> - </Card> - - <Card shadow="sm" className="col-span-2"> - <CardHeader> - <h2 className="uppercase text-gray-500">Top commands</h2> - </CardHeader> - <CardBody> - <TopChart chartData={homeInfo.topCommands} /> - </CardBody> - </Card> - </div> - </div> - ); -} diff --git a/ui/src/pages/Runbooks.tsx b/ui/src/pages/Runbooks.tsx deleted file mode 100644 index a0b844a6..00000000 --- a/ui/src/pages/Runbooks.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Editor from "@/components/runbooks/editor/Editor"; -import List from "@/components/runbooks/List"; - -import { useStore } from "@/state/store"; - -export default function Runbooks() { - const currentRunbook = useStore((store) => store.currentRunbook); - - return ( - <div className="flex w-full !max-w-full flex-row "> - <List /> - {currentRunbook && ( - <div className="flex w-full"> - <Editor /> - </div> - )} - - {!currentRunbook && ( - <div className="flex align-middle justify-center flex-col h-screen w-full"> - <h1 className="text-center">Select or create a runbook</h1> - </div> - )} - </div> - ); -} diff --git a/ui/src/state/client.ts b/ui/src/state/client.ts deleted file mode 100644 index c46fc4e6..00000000 --- a/ui/src/state/client.ts +++ /dev/null @@ -1,33 +0,0 @@ -// At some point, I'd like to replace some of the Atuin calls -// with separate state handling here - -import { invoke } from "@tauri-apps/api/core"; -import { Settings } from "@/state/models"; - -export async function sessionToken(): Promise<String> { - return await invoke("session"); -} - -export async function settings(): Promise<Settings> { - return await invoke("config"); -} - -export async function login( - username: string, - password: string, - key: string, -): Promise<string> { - return await invoke("login", { username, password, key }); -} - -export async function logout(): Promise<string> { - return await invoke("logout"); -} - -export async function register( - username: string, - email: string, - password: string, -): Promise<string> { - return await invoke("register", { username, email, password }); -} diff --git a/ui/src/state/models.ts b/ui/src/state/models.ts deleted file mode 100644 index 891f7a55..00000000 --- a/ui/src/state/models.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { invoke } from "@tauri-apps/api/core"; -import Database from "@tauri-apps/plugin-sql"; - -export class User { - username: string | null; - - constructor(username: string) { - this.username = username; - } - - isLoggedIn(): boolean { - return this.username !== "" && this.username !== null; - } -} - -export const DefaultUser: User = new User(""); - -export interface HomeInfo { - historyCount: number; - recordCount: number; - lastSyncTime: Date | null; - recentCommands: ShellHistory[]; - topCommands: ShellHistory[]; -} - -export const DefaultHomeInfo: HomeInfo = { - historyCount: 0, - recordCount: 0, - lastSyncTime: new Date(), - recentCommands: [], - topCommands: [], -}; - -export class ShellHistory { - id: string; - timestamp: number; - command: string; - user: string; - host: string; - cwd: string; - duration: number; - exit: number; - - /// Pass a row straight from the database to this - constructor( - id: string, - timestamp: number, - command: string, - hostuser: string, - cwd: string, - duration: number, - exit: number, - ) { - this.id = id; - this.timestamp = timestamp; - this.command = command; - - let [host, user] = hostuser.split(":"); - this.user = user; - this.host = host; - - this.cwd = cwd; - this.duration = duration; - this.exit = exit; - } -} - -export interface Alias { - name: string; - value: string; -} - -export interface Var { - name: string; - value: string; - export: boolean; -} - -export interface InspectHistory { - other: ShellHistory[]; -} - -// Not yet complete. Not all types are defined here. -// Gonna hold off until the settings refactoring. -export interface Settings { - auto_sync: boolean; - update_check: boolean; - sync_address: string; - sync_frequency: string; - db_path: string; - record_store_path: string; - key_path: string; - session_path: string; - shell_up_key_binding: boolean; - inline_height: number; - invert: boolean; - show_preview: boolean; - max_preview_height: number; - show_help: boolean; - show_tabs: boolean; - word_chars: string; - scroll_context_lines: number; - history_format: string; - prefers_reduced_motion: boolean; - store_failed: boolean; - secrets_filter: boolean; - workspaces: boolean; - ctrl_n_shortcuts: boolean; - network_connect_timeout: number; - network_timeout: number; - local_timeout: number; - enter_accept: boolean; - smart_sort: boolean; - sync: Sync; -} - -interface Sync { - records: boolean; -} - -// Define other interfaces (Dialect, Timezone, Style, SearchMode, FilterMode, ExitMode, KeymapMode, CursorStyle, WordJumpMode, RegexSet, Stats) accordingly. - -export async function inspectCommandHistory( - h: ShellHistory, -): Promise<InspectHistory> { - const settings: Settings = await invoke("cli_settings"); - const db = await Database.load("sqlite:" + settings.db_path); - - let other: any[] = await db.select( - "select * from history where command=?1 order by timestamp desc", - [h.command], - ); - console.log(other); - - return { - other: other.map( - (i) => - new ShellHistory( - i.id, - i.timestamp, - i.command, - i.hostname, - i.cwd, - i.duration, - i.exit, - ), - ), - }; -} - -export async function inspectDirectoryHistory( - h: ShellHistory, -): Promise<InspectHistory> { - const settings: Settings = await invoke("cli_settings"); - const db = await Database.load("sqlite:" + settings.db_path); - - let other: any[] = await db.select( - "select * from history where cwd=?1 order by timestamp desc", - [h.cwd], - ); - console.log(other); - - return { - other: other.map( - (i) => - new ShellHistory( - i.id, - i.timestamp, - i.command, - i.hostname, - i.cwd, - i.duration, - i.exit, - ), - ), - }; -} diff --git a/ui/src/state/runbooks/runbook.ts b/ui/src/state/runbooks/runbook.ts deleted file mode 100644 index 8555f4ea..00000000 --- a/ui/src/state/runbooks/runbook.ts +++ /dev/null @@ -1,124 +0,0 @@ -import Database from "@tauri-apps/plugin-sql"; -import { uuidv7 } from "uuidv7"; - -export default class Runbook { - id: string; - created: Date; - updated: Date; - - private _name: string; - private _content: string; - - set name(value: string) { - this.updated = new Date(); - this._name = value; - } - - set content(value: string) { - this.updated = new Date(); - this._content = value; - } - - get content() { - return this._content; - } - - get name() { - return this._name; - } - - constructor( - id: string, - name: string, - content: string, - created: Date, - updated: Date, - ) { - this.id = id; - this._name = name; - this._content = content; - this.created = created; - this.updated = updated; - } - - /// Create a new Runbook, and automatically generate an ID. - public static async create(): Promise<Runbook> { - let now = new Date(); - - // Initialize with the same value for created/updated, to avoid needing null. - let runbook = new Runbook(uuidv7(), "", "", now, now); - await runbook.save(); - - return runbook; - } - - public static async load(id: String): Promise<Runbook | null> { - const db = await Database.load("sqlite:runbooks.db"); - - let res = await db.select<any[]>("select * from runbooks where id = $1", [ - id, - ]); - - if (res.length == 0) return null; - - let rb = res[0]; - - return new Runbook( - rb.id, - rb.name, - rb.content, - new Date(rb.created / 1000000), - new Date(rb.updated / 1000000), - ); - } - - static async all(): Promise<Runbook[]> { - const db = await Database.load("sqlite:runbooks.db"); - - let res = await db.select<any[]>( - "select * from runbooks order by updated desc", - ); - - return res.map((i) => { - return new Runbook( - i.id, - i.name, - i.content, - new Date(i.created / 1000000), - new Date(i.updated / 1000000), - ); - }); - } - - public async save() { - const db = await Database.load("sqlite:runbooks.db"); - - await db.execute( - `insert into runbooks(id, name, content, created, updated) - values ($1, $2, $3, $4, $5) - - on conflict(id) do update - set - name=$2, - content=$3, - updated=$5`, - - // getTime returns a timestamp as unix milliseconds - // we won't need or use the resolution here, but elsewhere Atuin stores timestamps in sqlite as nanoseconds since epoch - // let's do that across the board to avoid mistakes - [ - this.id, - this._name, - this._content, - this.created.getTime() * 1000000, - this.updated.getTime() * 1000000, - ], - ); - } - - public static async delete(id: string) { - const db = await Database.load("sqlite:runbooks.db"); - - await db.execute("delete from runbooks where id=$1", [id]); - } -} diff --git a/ui/src/state/store.ts b/ui/src/state/store.ts deleted file mode 100644 index 39ee0096..00000000 --- a/ui/src/state/store.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { create } from "zustand"; -import { persist } from "zustand/middleware"; - -import { parseISO } from "date-fns"; - -import { fetch } from "@tauri-apps/plugin-http"; - -import { - User, - DefaultUser, - HomeInfo, - DefaultHomeInfo, - Alias, - ShellHistory, - Var, -} from "./models"; - -import { invoke } from "@tauri-apps/api/core"; -import { sessionToken, settings } from "./client"; -import { getWeekInfo } from "@/lib/utils"; -import Runbook from "./runbooks/runbook"; -import { Terminal } from "@xterm/xterm"; -import { FitAddon } from "@xterm/addon-fit"; -import { WebglAddon } from "@xterm/addon-webgl"; - -export class TerminalData { - terminal: Terminal; - fitAddon: FitAddon; - - constructor(terminal: Terminal, fit: FitAddon) { - this.terminal = terminal; - this.fitAddon = fit; - } -} - -// I'll probs want to slice this up at some point, but for now a -// big blobby lump of state is fine. -// Totally just hoping that structure will be emergent in the future. -export interface AtuinState { - user: User; - homeInfo: HomeInfo; - aliases: Alias[]; - vars: Var[]; - shellHistory: ShellHistory[]; - calendar: any[]; - weekStart: number; - runbooks: Runbook[]; - currentRunbook: string | null; - - refreshHomeInfo: () => void; - refreshCalendar: () => void; - refreshAliases: () => void; - refreshVars: () => void; - refreshUser: () => void; - refreshRunbooks: () => void; - refreshShellHistory: (query?: string) => void; - historyNextPage: (query?: string) => void; - - setCurrentRunbook: (id: String) => void; - setPtyTerm: (pty: string, terminal: any) => void; - newPtyTerm: (pty: string) => TerminalData; - cleanupPtyTerm: (pty: string) => void; - - terminals: { [pty: string]: TerminalData }; - - // Store ephemeral state for runbooks, that is not persisted to the database - runbookInfo: { [runbook: string]: { ptys: number } }; - incRunbookPty: (runbook: string) => void; - decRunbookPty: (runbook: string) => void; -} - -let state = (set: any, get: any): AtuinState => ({ - user: DefaultUser, - homeInfo: DefaultHomeInfo, - aliases: [], - vars: [], - shellHistory: [], - calendar: [], - runbooks: [], - currentRunbook: "", - terminals: {}, - runbookInfo: {}, - - weekStart: getWeekInfo().firstDay, - - refreshAliases: () => { - invoke("aliases").then((aliases: any) => { - set({ aliases: aliases }); - }); - }, - - refreshCalendar: () => { - invoke("history_calendar").then((calendar: any) => { - set({ calendar: calendar }); - }); - }, - - refreshVars: () => { - invoke("vars").then((vars: any) => { - set({ vars: vars }); - }); - }, - - refreshRunbooks: async () => { - let runbooks = await Runbook.all(); - set({ runbooks }); - }, - - refreshShellHistory: (query?: string) => { - if (query) { - invoke("search", { query: query }) - .then((res: any) => { - set({ shellHistory: res }); - }) - .catch((e) => { - console.log(e); - }); - } else { - invoke("list").then((res: any) => { - set({ shellHistory: res }); - }); - } - }, - - refreshHomeInfo: () => { - invoke("home_info") - .then((res: any) => { - console.log(res); - set({ - homeInfo: { - historyCount: res.history_count, - recordCount: res.record_count, - lastSyncTime: (res.last_sync && parseISO(res.last_sync)) || null, - recentCommands: res.recent_commands, - topCommands: res.top_commands.map((top: any) => ({ - command: top[0], - count: top[1], - })), - }, - }); - }) - .catch((e) => { - console.log(e); - }); - }, - - refreshUser: async () => { - let config = await settings(); - let session; - - try { - session = await sessionToken(); - } catch (e) { - console.log("Not logged in, so not refreshing user"); - set({ user: DefaultUser }); - return; - } - let url = config.sync_address + "/api/v0/me"; - - let res = await fetch(url, { - headers: { - Authorization: `Token ${session}`, - }, - }); - let me = await res.json(); - - set({ user: new User(me.username) }); - }, - - historyNextPage: (query?: string) => { - let history = get().shellHistory; - let offset = history.length - 1; - - if (query) { - invoke("search", { query: query, offset: offset }) - .then((res: any) => { - set({ shellHistory: [...history, ...res] }); - }) - .catch((e) => { - console.log(e); - }); - } else { - invoke("list", { offset: offset }).then((res: any) => { - set({ shellHistory: [...history, ...res] }); - }); - } - }, - - setCurrentRunbook: (id: String) => { - set({ currentRunbook: id }); - }, - - setPtyTerm: (pty: string, terminal: TerminalData) => { - set({ - terminals: { ...get().terminals, [pty]: terminal }, - }); - }, - - cleanupPtyTerm: (pty: string) => { - set((state: AtuinState) => { - const terminals = Object.keys(state.terminals).reduce( - (terms: { [pty: string]: TerminalData }, key) => { - if (key !== pty) { - terms[key] = state.terminals[key]; - } - return terms; - }, - {}, - ); - - return { terminals }; - }); - }, - - newPtyTerm: (pty: string) => { - let terminal = new Terminal(); - - // TODO: fallback to canvas, also some sort of setting to allow disabling webgl usage - // probs fine for now though, it's widely supported. maybe issues on linux. - terminal.loadAddon(new WebglAddon()); - - let fitAddon = new FitAddon(); - terminal.loadAddon(fitAddon); - - const onResize = (size: { cols: number; rows: number }) => { - invoke("pty_resize", { - pid: pty, - cols: size.cols, - rows: size.rows, - }); - }; - - terminal.onResize(onResize); - - let td = new TerminalData(terminal, fitAddon); - - set({ - terminals: { ...get().terminals, [pty]: td }, - }); - - return td; - }, - - incRunbookPty: (runbook: string) => { - set((state: AtuinState) => { - let oldVal = state.runbookInfo[runbook] || { ptys: 0 }; - let newVal = { ptys: oldVal.ptys + 1 }; - console.log(newVal); - - return { - runbookInfo: { - ...state.runbookInfo, - [runbook]: newVal, - }, - }; - }); - }, - - decRunbookPty: (runbook: string) => { - set((state: AtuinState) => { - let newVal = state.runbookInfo[runbook]; - if (!newVal) { - return; - } - - newVal.ptys--; - - return { - runbookInfo: { - ...state.runbookInfo, - [runbook]: newVal, - }, - }; - }); - }, -}); - -export const useStore = create<AtuinState>()( - persist(state, { - name: "atuin-storage", - - // don't serialize the terminals map - // it won't work as JSON. too cyclical - partialize: (state) => - Object.fromEntries( - Object.entries(state).filter(([key]) => !["terminals"].includes(key)), - ), - }), -); diff --git a/ui/src/styles.css b/ui/src/styles.css deleted file mode 100644 index 27e12ec6..00000000 --- a/ui/src/styles.css +++ /dev/null @@ -1,76 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/ui/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// <reference types="vite/client" /> |
