diff options
| author | Ellie Huxtable <ellie@atuin.sh> | 2024-07-25 23:31:38 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-25 23:31:38 +0100 |
| commit | c32bbcc7edc2cf99da52b1407c90238bc781a804 (patch) | |
| tree | 4a8b307f69fb444f5c8c15a65e6f89170d9d4bbf /ui | |
| parent | feat(gui): allow interacting with the embedded terminal (#2312) (diff) | |
| download | atuin-c32bbcc7edc2cf99da52b1407c90238bc781a804.zip | |
feat(gui): directory block, re-org of some code (#2314)
Diffstat (limited to 'ui')
| -rw-r--r-- | ui/backend/Cargo.lock | 1 | ||||
| -rw-r--r-- | ui/backend/Cargo.toml | 8 | ||||
| -rw-r--r-- | ui/backend/src/pty.rs | 8 | ||||
| -rw-r--r-- | ui/backend/src/run/pty.rs | 5 | ||||
| -rw-r--r-- | ui/src/App.css | 2 | ||||
| -rw-r--r-- | ui/src/components/runbooks/List.tsx | 3 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/Editor.tsx | 33 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/blocks/Directory/index.tsx | 65 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/blocks/Run/extensions.ts (renamed from ui/src/components/runbooks/editor/blocks/RunBlock/extensions.ts) | 0 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/blocks/Run/index.css (renamed from ui/src/components/runbooks/editor/blocks/RunBlock/index.css) | 0 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/blocks/Run/index.tsx (renamed from ui/src/components/runbooks/editor/blocks/RunBlock/index.tsx) | 25 | ||||
| -rw-r--r-- | ui/src/components/runbooks/editor/blocks/Run/terminal.tsx (renamed from ui/src/components/runbooks/editor/blocks/RunBlock/terminal.tsx) | 0 | ||||
| -rw-r--r-- | ui/src/pages/History.tsx | 19 | ||||
| -rw-r--r-- | ui/src/pages/Runbooks.tsx | 9 |
14 files changed, 150 insertions, 28 deletions
diff --git a/ui/backend/Cargo.lock b/ui/backend/Cargo.lock index 57de51ef..11659c11 100644 --- a/ui/backend/Cargo.lock +++ b/ui/backend/Cargo.lock @@ -6482,6 +6482,7 @@ dependencies = [ "portable-pty", "serde", "serde_json", + "shellexpand", "sqlx", "syntect", "tauri", diff --git a/ui/backend/Cargo.toml b/ui/backend/Cargo.toml index 4d4646f8..96a9b1c6 100644 --- a/ui/backend/Cargo.toml +++ b/ui/backend/Cargo.toml @@ -24,17 +24,19 @@ serde_json = "1.0" time = "0.3.36" uuid = "1.7.0" syntect = "5.2.0" -tauri-plugin-http = "2.0.0-beta" -tauri-plugin-single-instance = "2.0.0-beta" tokio = "1.38.0" -tauri-plugin-shell = "2.0.0-beta.7" comrak = "0.22" portable-pty = "0.8.1" vt100 = "0.15.2" bytes = "1.6.0" nix = "0.29.0" lazy_static = "1.5.0" +shellexpand = "3.1.0" + +tauri-plugin-http = "2.0.0-beta" +tauri-plugin-single-instance = "2.0.0-beta" tauri-plugin-os = "2.0.0-beta.8" +tauri-plugin-shell = "2.0.0-beta.7" [target."cfg(target_os = \"macos\")".dependencies] cocoa = "0.25" diff --git a/ui/backend/src/pty.rs b/ui/backend/src/pty.rs index 45502892..af394d95 100644 --- a/ui/backend/src/pty.rs +++ b/ui/backend/src/pty.rs @@ -16,7 +16,7 @@ pub struct Pty { } impl Pty { - pub async fn open<'a>(rows: u16, cols: u16) -> Result<Self> { + pub async fn open<'a>(rows: u16, cols: u16, cwd: Option<String>) -> Result<Self> { let sys = portable_pty::native_pty_system(); let pair = sys @@ -28,7 +28,11 @@ impl Pty { }) .map_err(|e| eyre!("Failed to open pty: {}", e))?; - let cmd = CommandBuilder::new_default_prog(); + let mut cmd = CommandBuilder::new_default_prog(); + + if let Some(cwd) = cwd { + cmd.cwd(cwd); + } let child = pair.slave.spawn_command(cmd).unwrap(); drop(pair.slave); diff --git a/ui/backend/src/run/pty.rs b/ui/backend/src/run/pty.rs index 58bfccb7..0ca5ece0 100644 --- a/ui/backend/src/run/pty.rs +++ b/ui/backend/src/run/pty.rs @@ -11,9 +11,12 @@ use atuin_client::{database::Sqlite, record::sqlite_store::SqliteStore, settings pub async fn pty_open<'a>( app: tauri::AppHandle, state: State<'a, AtuinState>, + cwd: Option<String>, ) -> Result<uuid::Uuid, String> { let id = uuid::Uuid::new_v4(); - let pty = crate::pty::Pty::open(24, 80).await.unwrap(); + + let cwd = cwd.map(|c|shellexpand::tilde(c.as_str()).to_string()); + let pty = crate::pty::Pty::open(24, 80, cwd).await.unwrap(); let reader = pty.reader.clone(); diff --git a/ui/src/App.css b/ui/src/App.css index cf6d3123..29ca80f1 100644 --- a/ui/src/App.css +++ b/ui/src/App.css @@ -19,7 +19,7 @@ html { } .history-list { - height: calc(100vh - 150px - 64px); + height: calc(100dvh - 4rem - 2rem); } .history-item { diff --git a/ui/src/components/runbooks/List.tsx b/ui/src/components/runbooks/List.tsx index d4591e6f..42da3885 100644 --- a/ui/src/components/runbooks/List.tsx +++ b/ui/src/components/runbooks/List.tsx @@ -109,8 +109,7 @@ const NoteSidebar = () => { onPress={async () => { await Runbook.delete(runbook.id); - if (runbook.id == currentRunbook) - setCurrentRunbook(null); + if (runbook.id == currentRunbook) setCurrentRunbook(""); refreshRunbooks(); }} diff --git a/ui/src/components/runbooks/editor/Editor.tsx b/ui/src/components/runbooks/editor/Editor.tsx index bbf594d8..6b0522f5 100644 --- a/ui/src/components/runbooks/editor/Editor.tsx +++ b/ui/src/components/runbooks/editor/Editor.tsx @@ -36,10 +36,12 @@ import { BlockNoteView } from "@blocknote/mantine"; import "@blocknote/core/fonts/inter.css"; import "@blocknote/mantine/style.css"; -import { Code } from "lucide-react"; +import { CodeIcon, FolderOpenIcon } from "lucide-react"; import { useDebounceCallback } from "usehooks-ts"; -import RunBlock from "@/components/runbooks/editor/blocks/RunBlock"; +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"; @@ -52,21 +54,34 @@ const schema = BlockNoteSchema.create({ ...defaultBlockSpecs, // Adds the code block. - run: RunBlock, + run: Run, + directory: Directory, }, }); // Slash menu item to insert an Alert block const insertRun = (editor: typeof schema.BlockNoteEditor) => ({ - title: "Code block", + title: "Code", onItemClick: () => { insertOrUpdateBlock(editor, { type: "run", }); }, - icon: <Code size={18} />, + icon: <CodeIcon size={18} />, aliases: ["code", "run"], - group: "Code", + 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() { @@ -161,7 +176,11 @@ export default function Editor() { triggerCharacter={"/"} getItems={async (query: any) => filterSuggestionItems( - [...getDefaultReactSlashMenuItems(editor), insertRun(editor)], + [ + ...getDefaultReactSlashMenuItems(editor), + insertRun(editor), + insertDirectory(editor), + ], query, ) } diff --git a/ui/src/components/runbooks/editor/blocks/Directory/index.tsx b/ui/src/components/runbooks/editor/blocks/Directory/index.tsx new file mode 100644 index 00000000..38e974ff --- /dev/null +++ b/ui/src/components/runbooks/editor/blocks/Directory/index.tsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import { Input, Tooltip } from "@nextui-org/react"; +import { FolderInputIcon, HelpCircleIcon } from "lucide-react"; + +// @ts-ignore +import { createReactBlockSpec } from "@blocknote/react"; + +interface DirectoryProps { + path: string; + onInputChange: (string) => void; +} + +const Directory = ({ path, onInputChange }: DirectoryProps) => { + const [value, setValue] = useState(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} + > + <Input + label="Directory" + placeholder="~" + labelPlacement="outside" + value={value} + autoComplete="off" + autoCapitalize="off" + autoCorrect="off" + spellCheck="false" + onValueChange={(val) => { + setValue(val); + onInputChange(val); + }} + startContent={<FolderInputIcon />} + /> + </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/RunBlock/extensions.ts b/ui/src/components/runbooks/editor/blocks/Run/extensions.ts index 76fc4343..76fc4343 100644 --- a/ui/src/components/runbooks/editor/blocks/RunBlock/extensions.ts +++ b/ui/src/components/runbooks/editor/blocks/Run/extensions.ts diff --git a/ui/src/components/runbooks/editor/blocks/RunBlock/index.css b/ui/src/components/runbooks/editor/blocks/Run/index.css index e854c03b..e854c03b 100644 --- a/ui/src/components/runbooks/editor/blocks/RunBlock/index.css +++ b/ui/src/components/runbooks/editor/blocks/Run/index.css diff --git a/ui/src/components/runbooks/editor/blocks/RunBlock/index.tsx b/ui/src/components/runbooks/editor/blocks/Run/index.tsx index 25faa211..e0989f47 100644 --- a/ui/src/components/runbooks/editor/blocks/RunBlock/index.tsx +++ b/ui/src/components/runbooks/editor/blocks/Run/index.tsx @@ -1,5 +1,6 @@ // @ts-ignore import { createReactBlockSpec } from "@blocknote/react"; + import "./index.css"; import CodeMirror from "@uiw/react-codemirror"; @@ -26,8 +27,25 @@ interface RunBlockProps { 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, @@ -36,6 +54,7 @@ const RunBlock = ({ onRun, onStop, pty, + editor, }: RunBlockProps) => { const [value, setValue] = useState<String>(code); const cleanupPtyTerm = useStore((store: AtuinState) => store.cleanupPtyTerm); @@ -68,7 +87,9 @@ const RunBlock = ({ } if (!isRunning) { - let pty = await invoke<string>("pty_open"); + const cwd = findFirstParentOfType(editor, id, "directory"); + console.log(cwd.props.path); + let pty = await invoke<string>("pty_open", { cwd: cwd.props.path }); if (onRun) onRun(pty); if (currentRunbook) incRunbookPty(currentRunbook); @@ -150,6 +171,7 @@ export default createReactBlockSpec( }, code: { default: "" }, pty: { default: "" }, + global: { default: false }, }, content: "none", }, @@ -186,6 +208,7 @@ export default createReactBlockSpec( isEditable={editor.isEditable} onRun={onRun} onStop={onStop} + editor={editor} /> ); }, diff --git a/ui/src/components/runbooks/editor/blocks/RunBlock/terminal.tsx b/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx index a6dc589f..a6dc589f 100644 --- a/ui/src/components/runbooks/editor/blocks/RunBlock/terminal.tsx +++ b/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx diff --git a/ui/src/pages/History.tsx b/ui/src/pages/History.tsx index 64c2fca6..7002f3c4 100644 --- a/ui/src/pages/History.tsx +++ b/ui/src/pages/History.tsx @@ -6,7 +6,7 @@ import HistorySearch from "@/components/HistorySearch.tsx"; import Stats from "@/components/history/Stats.tsx"; import Drawer from "@/components/Drawer.tsx"; -import { useStore } from "@/state/store"; +import { AtuinState, useStore } from "@/state/store"; function Header() { return ( @@ -49,9 +49,13 @@ function Header() { } export default function Search() { - const history = useStore((state) => state.shellHistory); - const refreshHistory = useStore((state) => state.refreshShellHistory); - const historyNextPage = useStore((state) => state.historyNextPage); + 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(""); @@ -84,12 +88,7 @@ export default function Search() { return ( <> - <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> - </div> - + <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} diff --git a/ui/src/pages/Runbooks.tsx b/ui/src/pages/Runbooks.tsx index 6b517553..d0efbc1c 100644 --- a/ui/src/pages/Runbooks.tsx +++ b/ui/src/pages/Runbooks.tsx @@ -1,5 +1,8 @@ import Editor from "@/components/runbooks/editor/Editor"; import List from "@/components/runbooks/List"; + +import { Checkbox } from "@nextui-org/react"; + import { useStore } from "@/state/store"; export default function Runbooks() { @@ -8,7 +11,11 @@ export default function Runbooks() { return ( <div className="flex w-full !max-w-full flex-row "> <List /> - {currentRunbook && <Editor />} + {currentRunbook && ( + <div className="flex w-full"> + <Editor /> + </div> + )} {!currentRunbook && ( <div className="flex align-middle justify-center flex-col h-screen w-full"> |
