diff options
Diffstat (limited to '')
| -rw-r--r-- | ui/src/App.tsx | 257 | ||||
| -rw-r--r-- | ui/src/assets/icon.svg | 1 | ||||
| -rw-r--r-- | ui/src/components/LoginOrRegister.tsx | 14 | ||||
| -rw-r--r-- | ui/src/components/Sidebar/Sidebar.tsx | 328 | ||||
| -rw-r--r-- | ui/src/components/Sidebar/index.tsx | 4 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/Editor.tsx | 4 | ||||
| -rw-r--r-- | ui/src/lib/utils.ts | 31 | ||||
| -rw-r--r-- | ui/src/main.tsx | 6 | ||||
| -rw-r--r-- | ui/src/pages/Dotfiles.tsx | 2 | ||||
| -rw-r--r-- | ui/src/pages/History.tsx | 2 | ||||
| -rw-r--r-- | ui/src/pages/Home.tsx | 2 | ||||
| -rw-r--r-- | ui/src/pages/Runbooks.tsx | 2 | ||||
| -rw-r--r-- | ui/src/state/client.ts | 4 |
13 files changed, 569 insertions, 88 deletions
diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 6dba3d71..7a9ac395 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,10 +1,17 @@ import "./App.css"; +import { open } from "@tauri-apps/plugin-shell"; -import { useState, ReactElement } from "react"; +import { useState, ReactElement, useEffect } from "react"; import { useStore } from "@/state/store"; -import Button, { ButtonStyle } from "@/components/Button"; import { Toaster } from "@/components/ui/toaster"; +import { + SettingsIcon, + CircleHelpIcon, + KeyRoundIcon, + LogOutIcon, +} from "lucide-react"; +import { Icon } from "@iconify/react"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; @@ -28,6 +35,35 @@ import Dotfiles from "./pages/Dotfiles.tsx"; import LoginOrRegister from "./components/LoginOrRegister.tsx"; import Runbooks from "./pages/Runbooks.tsx"; +import { + Avatar, + User, + Button, + ScrollShadow, + Spacer, + Tooltip, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + useDisclosure, + Checkbox, + Input, + Link, +} from "@nextui-org/react"; +import { cn } from "@/lib/utils"; +import { sectionItems } from "@/components/Sidebar/sidebar-items"; +import Sidebar, { SidebarItem } from "@/components/Sidebar"; +import icon from "@/assets/icon.svg"; +import iconText from "@/assets/logo-light.svg"; +import { logout } from "./state/client.ts"; + enum Section { Home, History, @@ -54,90 +90,165 @@ function App() { // pages const [section, setSection] = useState(Section.Home); const user = useStore((state) => state.user); - console.log(user); + const refreshUser = useStore((state) => state.refreshUser); + const { isOpen, onOpen, onOpenChange } = useDisclosure(); - const navigation = [ - { - name: "Home", - icon: HomeIcon, - section: Section.Home, - }, - { - name: "History", - icon: ClockIcon, - section: Section.History, - }, - { - name: "Dotfiles", - icon: WrenchScrewdriverIcon, - section: Section.Dotfiles, - }, + const navigation: SidebarItem[] = [ { - name: "Runbooks", - icon: ChevronRightSquare, - section: Section.Runbooks, + 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> - <div className="fixed inset-y-0 z-50 flex w-60 flex-col"> - <div className="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-200 bg-white px-6 pb-4"> - <div className="flex h-16 shrink-0 items-center"> - <img className="h-8 w-auto" src={Logo} alt="Atuin" /> + <div className="flex h-dvh w-full select-none"> + <div className="relative flex h-full flex-col !border-r-small border-divider p-6 transition-width px-2 pb-6 pt-9 w-16 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> - <nav className="flex flex-1 flex-col"> - <ul role="list" className="flex flex-1 flex-col gap-y-7"> - <li> - <ul role="list" className="-mx-2 space-y-1 w-full"> - {navigation.map((item) => ( - <li key={item.name}> - <button - onClick={() => setSection(item.section)} - className={classNames( - section == item.section - ? "bg-gray-50 text-green-600" - : "text-gray-700 hover:text-green-600 hover:bg-gray-50", - "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold w-full", - )} - > - <item.icon - className={classNames( - section == item.section - ? "text-green-600" - : "text-gray-400 group-hover:text-green-600", - "h-6 w-6 shrink-0", - )} - aria-hidden="true" - /> - {item.name} - </button> - </li> - ))} - </ul> - </li> - <li className="mt-auto"> - {user && user.username === "" && !user.username && ( - <Dialog> - <DialogTrigger className="w-full"> - <Button - text={"Login or Register"} - style={ButtonStyle.PrimarySmFill} - /> - </DialogTrigger> - <DialogContent> - <LoginOrRegister /> - </DialogContent> - </Dialog> + </div> + + <ScrollShadow className="-mr-6 h-full max-h-full py-6 pr-6"> + <Sidebar + defaultSelectedKey="home" + isCompact={true} + items={navigation} + /> + </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> )} - </li> - </ul> - </nav> + </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> ); } diff --git a/ui/src/assets/icon.svg b/ui/src/assets/icon.svg new file mode 100644 index 00000000..0e4dd607 --- /dev/null +++ b/ui/src/assets/icon.svg @@ -0,0 +1 @@ +<?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/components/LoginOrRegister.tsx b/ui/src/components/LoginOrRegister.tsx index f05a9a24..97f8a790 100644 --- a/ui/src/components/LoginOrRegister.tsx +++ b/ui/src/components/LoginOrRegister.tsx @@ -6,6 +6,7 @@ import { useStore } from "@/state/store"; interface LoginProps { toggleRegister: () => void; + onClose: () => void; } function Login(props: LoginProps) { @@ -24,7 +25,7 @@ function Login(props: LoginProps) { try { await login(username, password, key); refreshUser(); - console.log("Logged in"); + props.onClose(); } catch (e: any) { console.error(e); setErrors(e); @@ -171,6 +172,7 @@ function Login(props: LoginProps) { interface RegisterProps { toggleLogin: () => void; + onClose: () => void; } function Register(props: RegisterProps) { @@ -185,13 +187,11 @@ function Register(props: RegisterProps) { const email = form.email.value; const password = form.password.value; - console.log("Logging in..."); try { await register(username, email, password); refreshUser(); - console.log("Logged in"); + props.onClose(); } catch (e: any) { - console.error(e); setErrors(e); } }; @@ -330,12 +330,12 @@ function Register(props: RegisterProps) { ); } -export default function LoginOrRegister() { +export default function LoginOrRegister({ onClose }: { onClose: () => void }) { let [login, setLogin] = useState<boolean>(false); if (login) { - return <Login toggleRegister={() => setLogin(false)} />; + return <Login onClose={onClose} toggleRegister={() => setLogin(false)} />; } - return <Register toggleLogin={() => setLogin(true)} />; + return <Register onClose={onClose} toggleLogin={() => setLogin(true)} />; } diff --git a/ui/src/components/Sidebar/Sidebar.tsx b/ui/src/components/Sidebar/Sidebar.tsx new file mode 100644 index 00000000..99e2bf82 --- /dev/null +++ b/ui/src/components/Sidebar/Sidebar.tsx @@ -0,0 +1,328 @@ +"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 new file mode 100644 index 00000000..10020952 --- /dev/null +++ b/ui/src/components/Sidebar/index.tsx @@ -0,0 +1,4 @@ +import Sidebar, { SidebarItem } from "./Sidebar"; + +export type { SidebarItem }; +export default Sidebar; diff --git a/ui/src/components/runbooks/editor/Editor.tsx b/ui/src/components/runbooks/editor/Editor.tsx index 81ea84cf..3f05d9f3 100644 --- a/ui/src/components/runbooks/editor/Editor.tsx +++ b/ui/src/components/runbooks/editor/Editor.tsx @@ -60,6 +60,10 @@ export default function Editor() { content: "Atuin runbooks", id: "foo", }, + { + type: "run", + id: "bar", + }, ], }); diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts index d084ccad..c56d3687 100644 --- a/ui/src/lib/utils.ts +++ b/ui/src/lib/utils.ts @@ -1,6 +1,31 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" +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)) + return twMerge(clsx(inputs)); } diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 81da3460..dc4c24a3 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -1,10 +1,14 @@ import React from "react"; import ReactDOM from "react-dom/client"; +import { NextUIProvider, Spacer } from "@nextui-org/react"; import App from "./App"; import "./styles.css"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( <React.StrictMode> - <App /> + <NextUIProvider> + <div data-tauri-drag-region className="w-full h-8 absolute" /> + <App /> + </NextUIProvider> </React.StrictMode>, ); diff --git a/ui/src/pages/Dotfiles.tsx b/ui/src/pages/Dotfiles.tsx index cd80be66..56f570be 100644 --- a/ui/src/pages/Dotfiles.tsx +++ b/ui/src/pages/Dotfiles.tsx @@ -98,7 +98,7 @@ export default function Dotfiles() { console.log(current); return ( - <div className="pl-60"> + <div className="w-full flex-1 flex-col p-4"> <div className="p-10"> <Header current={current} setCurrent={setCurrent} /> Manage your shell aliases, variables and paths diff --git a/ui/src/pages/History.tsx b/ui/src/pages/History.tsx index 81c83f99..64c2fca6 100644 --- a/ui/src/pages/History.tsx +++ b/ui/src/pages/History.tsx @@ -84,7 +84,7 @@ export default function Search() { return ( <> - <div className="pl-60"> + <div className="w-full flex-1 flex-col p-4"> <div className="p-10 history-header"> <Header /> <p>A history of all the commands you run in your shell.</p> diff --git a/ui/src/pages/Home.tsx b/ui/src/pages/Home.tsx index 9b1c0976..b7c4503d 100644 --- a/ui/src/pages/Home.tsx +++ b/ui/src/pages/Home.tsx @@ -125,7 +125,7 @@ export default function Home() { } return ( - <div className="pl-60"> + <div className="w-full flex-1 flex-col p-4"> <div className="p-10"> <Header name={user.username} /> diff --git a/ui/src/pages/Runbooks.tsx b/ui/src/pages/Runbooks.tsx index 71887310..4237e065 100644 --- a/ui/src/pages/Runbooks.tsx +++ b/ui/src/pages/Runbooks.tsx @@ -2,7 +2,7 @@ import Editor from "@/components/runbooks/editor/Editor"; export default function Runbooks() { return ( - <div className="pl-60 p-4 "> + <div className="w-full flex-1 flex-col p-4"> <Editor /> </div> ); diff --git a/ui/src/state/client.ts b/ui/src/state/client.ts index 5ec0d8a7..c46fc4e6 100644 --- a/ui/src/state/client.ts +++ b/ui/src/state/client.ts @@ -20,6 +20,10 @@ export async function login( 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, |
