mirror of
https://github.com/atuinsh/atuin.git
synced 2024-11-21 15:53:30 +01:00
feat(gui): directory block, re-org of some code (#2314)
This commit is contained in:
parent
5ba185e7d8
commit
c32bbcc7ed
1
ui/backend/Cargo.lock
generated
1
ui/backend/Cargo.lock
generated
@ -6482,6 +6482,7 @@ dependencies = [
|
||||
"portable-pty",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"sqlx",
|
||||
"syntect",
|
||||
"tauri",
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -19,7 +19,7 @@ html {
|
||||
}
|
||||
|
||||
.history-list {
|
||||
height: calc(100vh - 150px - 64px);
|
||||
height: calc(100dvh - 4rem - 2rem);
|
||||
}
|
||||
|
||||
.history-item {
|
||||
|
@ -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();
|
||||
}}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
65
ui/src/components/runbooks/editor/blocks/Directory/index.tsx
Normal file
65
ui/src/components/runbooks/editor/blocks/Directory/index.tsx
Normal file
@ -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} />
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
@ -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}
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user