From cea48a1545250429b78235b2ad00b8243923e2b2 Mon Sep 17 00:00:00 2001 From: Ellie Huxtable Date: Mon, 29 Apr 2024 14:59:59 +0100 Subject: [PATCH] feat(ui/dotfiles): add vars (#1989) --- ui/backend/Cargo.lock | 8 +- ui/backend/Cargo.toml | 6 +- ui/backend/src/dotfiles/mod.rs | 1 + ui/backend/src/dotfiles/vars.rs | 57 ++++++++ ui/backend/src/main.rs | 3 + ui/src/components/dotfiles/Vars.tsx | 194 ++++++++++++++++++++++++++++ ui/src/pages/Dotfiles.tsx | 90 ++++++++++++- ui/src/state/models.ts | 6 + ui/src/state/store.ts | 9 ++ 9 files changed, 364 insertions(+), 10 deletions(-) create mode 100644 ui/backend/src/dotfiles/vars.rs create mode 100644 ui/src/components/dotfiles/Vars.tsx diff --git a/ui/backend/Cargo.lock b/ui/backend/Cargo.lock index abb72ce2..85736e2a 100644 --- a/ui/backend/Cargo.lock +++ b/ui/backend/Cargo.lock @@ -5285,18 +5285,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-builder" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", diff --git a/ui/backend/Cargo.toml b/ui/backend/Cargo.toml index 4e0019d3..9cf47436 100644 --- a/ui/backend/Cargo.toml +++ b/ui/backend/Cargo.toml @@ -12,10 +12,10 @@ edition = "2021" tauri-build = { version = "2.0.0-beta", features = [] } [dependencies] -atuin-client = { path = "../../atuin-client", version = "18.2.0" } -atuin-common = { path = "../../atuin-common", version = "18.2.0" } +atuin-client = { path = "../../crates/atuin-client", version = "18.2.0" } +atuin-common = { path = "../../crates/atuin-common", version = "18.2.0" } -atuin-dotfiles = { path = "../../atuin-dotfiles", version = "0.2.0" } +atuin-dotfiles = { path = "../../crates/atuin-dotfiles", version = "0.2.0" } eyre = "0.6" tauri = { version = "2.0.0-beta", features = ["tray-icon"] } diff --git a/ui/backend/src/dotfiles/mod.rs b/ui/backend/src/dotfiles/mod.rs index d293a01b..feafe783 100644 --- a/ui/backend/src/dotfiles/mod.rs +++ b/ui/backend/src/dotfiles/mod.rs @@ -1 +1,2 @@ pub mod aliases; +pub mod vars; diff --git a/ui/backend/src/dotfiles/vars.rs b/ui/backend/src/dotfiles/vars.rs new file mode 100644 index 00000000..d8d5bd75 --- /dev/null +++ b/ui/backend/src/dotfiles/vars.rs @@ -0,0 +1,57 @@ +use std::path::PathBuf; + +use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; +use atuin_common::shell::Shell; +use atuin_dotfiles::{ + shell::{existing_aliases, Alias, Var}, + store::var::VarStore, +}; + +async fn var_store() -> eyre::Result { + let settings = Settings::new()?; + + let record_store_path = PathBuf::from(settings.record_store_path.as_str()); + let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?; + + let encryption_key: [u8; 32] = encryption::load_key(&settings)?.into(); + + let host_id = Settings::host_id().expect("failed to get host_id"); + + Ok(VarStore::new(sqlite_store, host_id, encryption_key)) +} + +#[tauri::command] +pub async fn vars() -> Result, String> { + let var_store = var_store().await.map_err(|e| e.to_string())?; + + let vars = var_store + .vars() + .await + .map_err(|e| format!("failed to load aliases: {}", e))?; + + Ok(vars) +} + +#[tauri::command] +pub async fn delete_var(name: String) -> Result<(), String> { + let var_store = var_store().await.map_err(|e| e.to_string())?; + + var_store + .delete(name.as_str()) + .await + .map_err(|e| e.to_string())?; + + Ok(()) +} + +#[tauri::command] +pub async fn set_var(name: String, value: String, export: bool) -> Result<(), String> { + let var_store = var_store().await.map_err(|e| e.to_string())?; + + var_store + .set(name.as_str(), value.as_str(), export) + .await + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/ui/backend/src/main.rs b/ui/backend/src/main.rs index fbcf9481..fe6271b8 100644 --- a/ui/backend/src/main.rs +++ b/ui/backend/src/main.rs @@ -118,6 +118,9 @@ fn main() { dotfiles::aliases::import_aliases, dotfiles::aliases::delete_alias, dotfiles::aliases::set_alias, + dotfiles::vars::vars, + dotfiles::vars::delete_var, + dotfiles::vars::set_var, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/ui/src/components/dotfiles/Vars.tsx b/ui/src/components/dotfiles/Vars.tsx new file mode 100644 index 00000000..00317b23 --- /dev/null +++ b/ui/src/components/dotfiles/Vars.tsx @@ -0,0 +1,194 @@ +import { useEffect, useState } from "react"; + +import DataTable from "@/components/ui/data-table"; +import { Button } from "@/components/ui/button"; +import { MoreHorizontal } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import { ColumnDef } from "@tanstack/react-table"; + +import { invoke } from "@tauri-apps/api/core"; +import Drawer from "@/components/Drawer"; + +import { Var } from "@/state/models"; +import { useStore } from "@/state/store"; + +function deleteVar(name: string, refreshVars: () => void) { + invoke("delete_var", { name: name }) + .then(() => { + refreshVars(); + }) + .catch(() => { + console.error("Failed to delete var"); + }); +} + +function AddVar({ onAdd: onAdd }: { onAdd?: () => void }) { + let [name, setName] = useState(""); + let [value, setValue] = useState(""); + let [exp, setExport] = useState(false); + + // simple form to add vars + return ( +
+

Add var

+

Add a new var to your shell

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

+ Vars +

+

+ Configure environment variables here +

+
+
+ + Add + + } + > + { + refreshVars(); + setVarDrawerOpen(false); + }} + /> + +
+
+
+
+
+ +
+
+
+
+ ); +} diff --git a/ui/src/pages/Dotfiles.tsx b/ui/src/pages/Dotfiles.tsx index 6b0870b3..29b6b54a 100644 --- a/ui/src/pages/Dotfiles.tsx +++ b/ui/src/pages/Dotfiles.tsx @@ -1,6 +1,35 @@ +import { useState } from "react"; import Aliases from "@/components/dotfiles/Aliases"; +import Vars from "@/components/dotfiles/Vars"; -function Header() { +enum Section { + Aliases, + Vars, + Scripts, +} + +function renderDotfiles(current: Section) { + switch (current) { + case Section.Aliases: + return ; + case Section.Vars: + return ; + case Section.Scripts: + return
; + } +} + +interface HeaderProps { + current: Section; + setCurrent: (section: Section) => void; +} + +interface TabsProps { + current: Section; + setCurrent: (section: Section) => void; +} + +function Header({ current, setCurrent }: HeaderProps) { return (
@@ -8,17 +37,72 @@ function Header() { Dotfiles
+ + +
+ ); +} + +function classNames(...classes) { + 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: "Scripts", + isCurrent: () => current === Section.Scripts, + section: Section.Scripts, + }, + ]; + + return ( +
+
+ +
); } export default function Dotfiles() { + let [current, setCurrent] = useState(Section.Aliases); + console.log(current); + return (
-
+
Manage your shell aliases, variables and paths - + {renderDotfiles(current)}
); diff --git a/ui/src/state/models.ts b/ui/src/state/models.ts index f11ce651..5afcb804 100644 --- a/ui/src/state/models.ts +++ b/ui/src/state/models.ts @@ -32,3 +32,9 @@ export interface Alias { name: string; value: string; } + +export interface Var { + name: string; + value: string; + export: bool; +} diff --git a/ui/src/state/store.ts b/ui/src/state/store.ts index 08410ba8..7e237d70 100644 --- a/ui/src/state/store.ts +++ b/ui/src/state/store.ts @@ -19,10 +19,12 @@ interface AtuinState { user: User; homeInfo: HomeInfo; aliases: Alias[]; + vars: Var[]; shellHistory: ShellHistory[]; refreshHomeInfo: () => void; refreshAliases: () => void; + refreshVars: () => void; refreshShellHistory: (query?: string) => void; } @@ -30,6 +32,7 @@ export const useStore = create()((set) => ({ user: DefaultUser, homeInfo: DefaultHomeInfo, aliases: [], + vars: [], shellHistory: [], refreshAliases: () => { @@ -38,6 +41,12 @@ export const useStore = create()((set) => ({ }); }, + refreshVars: () => { + invoke("vars").then((vars: any) => { + set({ vars: vars }); + }); + }, + refreshShellHistory: (query?: string) => { if (query) { invoke("search", { query: query })