mirror of
https://github.com/atuinsh/atuin.git
synced 2024-11-22 00:03:49 +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",
|
"portable-pty",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"shellexpand",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"syntect",
|
"syntect",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
@ -24,17 +24,19 @@ serde_json = "1.0"
|
|||||||
time = "0.3.36"
|
time = "0.3.36"
|
||||||
uuid = "1.7.0"
|
uuid = "1.7.0"
|
||||||
syntect = "5.2.0"
|
syntect = "5.2.0"
|
||||||
tauri-plugin-http = "2.0.0-beta"
|
|
||||||
tauri-plugin-single-instance = "2.0.0-beta"
|
|
||||||
tokio = "1.38.0"
|
tokio = "1.38.0"
|
||||||
tauri-plugin-shell = "2.0.0-beta.7"
|
|
||||||
comrak = "0.22"
|
comrak = "0.22"
|
||||||
portable-pty = "0.8.1"
|
portable-pty = "0.8.1"
|
||||||
vt100 = "0.15.2"
|
vt100 = "0.15.2"
|
||||||
bytes = "1.6.0"
|
bytes = "1.6.0"
|
||||||
nix = "0.29.0"
|
nix = "0.29.0"
|
||||||
lazy_static = "1.5.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-os = "2.0.0-beta.8"
|
||||||
|
tauri-plugin-shell = "2.0.0-beta.7"
|
||||||
|
|
||||||
[target."cfg(target_os = \"macos\")".dependencies]
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
cocoa = "0.25"
|
cocoa = "0.25"
|
||||||
|
@ -16,7 +16,7 @@ pub struct Pty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 sys = portable_pty::native_pty_system();
|
||||||
|
|
||||||
let pair = sys
|
let pair = sys
|
||||||
@ -28,7 +28,11 @@ impl Pty {
|
|||||||
})
|
})
|
||||||
.map_err(|e| eyre!("Failed to open pty: {}", e))?;
|
.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();
|
let child = pair.slave.spawn_command(cmd).unwrap();
|
||||||
drop(pair.slave);
|
drop(pair.slave);
|
||||||
|
@ -11,9 +11,12 @@ use atuin_client::{database::Sqlite, record::sqlite_store::SqliteStore, settings
|
|||||||
pub async fn pty_open<'a>(
|
pub async fn pty_open<'a>(
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
state: State<'a, AtuinState>,
|
state: State<'a, AtuinState>,
|
||||||
|
cwd: Option<String>,
|
||||||
) -> Result<uuid::Uuid, String> {
|
) -> Result<uuid::Uuid, String> {
|
||||||
let id = uuid::Uuid::new_v4();
|
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();
|
let reader = pty.reader.clone();
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.history-list {
|
.history-list {
|
||||||
height: calc(100vh - 150px - 64px);
|
height: calc(100dvh - 4rem - 2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-item {
|
.history-item {
|
||||||
|
@ -109,8 +109,7 @@ const NoteSidebar = () => {
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
await Runbook.delete(runbook.id);
|
await Runbook.delete(runbook.id);
|
||||||
|
|
||||||
if (runbook.id == currentRunbook)
|
if (runbook.id == currentRunbook) setCurrentRunbook("");
|
||||||
setCurrentRunbook(null);
|
|
||||||
|
|
||||||
refreshRunbooks();
|
refreshRunbooks();
|
||||||
}}
|
}}
|
||||||
|
@ -36,10 +36,12 @@ import { BlockNoteView } from "@blocknote/mantine";
|
|||||||
import "@blocknote/core/fonts/inter.css";
|
import "@blocknote/core/fonts/inter.css";
|
||||||
import "@blocknote/mantine/style.css";
|
import "@blocknote/mantine/style.css";
|
||||||
|
|
||||||
import { Code } from "lucide-react";
|
import { CodeIcon, FolderOpenIcon } from "lucide-react";
|
||||||
import { useDebounceCallback } from "usehooks-ts";
|
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 { DeleteBlock } from "@/components/runbooks/editor/ui/DeleteBlockButton";
|
||||||
import { AtuinState, useStore } from "@/state/store";
|
import { AtuinState, useStore } from "@/state/store";
|
||||||
import Runbook from "@/state/runbooks/runbook";
|
import Runbook from "@/state/runbooks/runbook";
|
||||||
@ -52,21 +54,34 @@ const schema = BlockNoteSchema.create({
|
|||||||
...defaultBlockSpecs,
|
...defaultBlockSpecs,
|
||||||
|
|
||||||
// Adds the code block.
|
// Adds the code block.
|
||||||
run: RunBlock,
|
run: Run,
|
||||||
|
directory: Directory,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Slash menu item to insert an Alert block
|
// Slash menu item to insert an Alert block
|
||||||
const insertRun = (editor: typeof schema.BlockNoteEditor) => ({
|
const insertRun = (editor: typeof schema.BlockNoteEditor) => ({
|
||||||
title: "Code block",
|
title: "Code",
|
||||||
onItemClick: () => {
|
onItemClick: () => {
|
||||||
insertOrUpdateBlock(editor, {
|
insertOrUpdateBlock(editor, {
|
||||||
type: "run",
|
type: "run",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
icon: <Code size={18} />,
|
icon: <CodeIcon size={18} />,
|
||||||
aliases: ["code", "run"],
|
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() {
|
export default function Editor() {
|
||||||
@ -161,7 +176,11 @@ export default function Editor() {
|
|||||||
triggerCharacter={"/"}
|
triggerCharacter={"/"}
|
||||||
getItems={async (query: any) =>
|
getItems={async (query: any) =>
|
||||||
filterSuggestionItems(
|
filterSuggestionItems(
|
||||||
[...getDefaultReactSlashMenuItems(editor), insertRun(editor)],
|
[
|
||||||
|
...getDefaultReactSlashMenuItems(editor),
|
||||||
|
insertRun(editor),
|
||||||
|
insertDirectory(editor),
|
||||||
|
],
|
||||||
query,
|
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
|
// @ts-ignore
|
||||||
import { createReactBlockSpec } from "@blocknote/react";
|
import { createReactBlockSpec } from "@blocknote/react";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import CodeMirror from "@uiw/react-codemirror";
|
import CodeMirror from "@uiw/react-codemirror";
|
||||||
@ -26,8 +27,25 @@ interface RunBlockProps {
|
|||||||
type: string;
|
type: string;
|
||||||
pty: string;
|
pty: string;
|
||||||
isEditable: boolean;
|
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 = ({
|
const RunBlock = ({
|
||||||
onChange,
|
onChange,
|
||||||
id,
|
id,
|
||||||
@ -36,6 +54,7 @@ const RunBlock = ({
|
|||||||
onRun,
|
onRun,
|
||||||
onStop,
|
onStop,
|
||||||
pty,
|
pty,
|
||||||
|
editor,
|
||||||
}: RunBlockProps) => {
|
}: RunBlockProps) => {
|
||||||
const [value, setValue] = useState<String>(code);
|
const [value, setValue] = useState<String>(code);
|
||||||
const cleanupPtyTerm = useStore((store: AtuinState) => store.cleanupPtyTerm);
|
const cleanupPtyTerm = useStore((store: AtuinState) => store.cleanupPtyTerm);
|
||||||
@ -68,7 +87,9 @@ const RunBlock = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isRunning) {
|
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 (onRun) onRun(pty);
|
||||||
|
|
||||||
if (currentRunbook) incRunbookPty(currentRunbook);
|
if (currentRunbook) incRunbookPty(currentRunbook);
|
||||||
@ -150,6 +171,7 @@ export default createReactBlockSpec(
|
|||||||
},
|
},
|
||||||
code: { default: "" },
|
code: { default: "" },
|
||||||
pty: { default: "" },
|
pty: { default: "" },
|
||||||
|
global: { default: false },
|
||||||
},
|
},
|
||||||
content: "none",
|
content: "none",
|
||||||
},
|
},
|
||||||
@ -186,6 +208,7 @@ export default createReactBlockSpec(
|
|||||||
isEditable={editor.isEditable}
|
isEditable={editor.isEditable}
|
||||||
onRun={onRun}
|
onRun={onRun}
|
||||||
onStop={onStop}
|
onStop={onStop}
|
||||||
|
editor={editor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
@ -6,7 +6,7 @@ import HistorySearch from "@/components/HistorySearch.tsx";
|
|||||||
import Stats from "@/components/history/Stats.tsx";
|
import Stats from "@/components/history/Stats.tsx";
|
||||||
import Drawer from "@/components/Drawer.tsx";
|
import Drawer from "@/components/Drawer.tsx";
|
||||||
|
|
||||||
import { useStore } from "@/state/store";
|
import { AtuinState, useStore } from "@/state/store";
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
return (
|
return (
|
||||||
@ -49,9 +49,13 @@ function Header() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const history = useStore((state) => state.shellHistory);
|
const history = useStore((state: AtuinState) => state.shellHistory);
|
||||||
const refreshHistory = useStore((state) => state.refreshShellHistory);
|
const refreshHistory = useStore(
|
||||||
const historyNextPage = useStore((state) => state.historyNextPage);
|
(state: AtuinState) => state.refreshShellHistory,
|
||||||
|
);
|
||||||
|
const historyNextPage = useStore(
|
||||||
|
(state: AtuinState) => state.historyNextPage,
|
||||||
|
);
|
||||||
|
|
||||||
let [query, setQuery] = useState("");
|
let [query, setQuery] = useState("");
|
||||||
|
|
||||||
@ -84,12 +88,7 @@ export default function Search() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex-1 flex-col p-4">
|
<div className="w-full flex-1 flex-col">
|
||||||
<div className="p-10 history-header">
|
|
||||||
<Header />
|
|
||||||
<p>A history of all the commands you run in your shell.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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">
|
<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
|
<HistorySearch
|
||||||
query={query}
|
query={query}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import Editor from "@/components/runbooks/editor/Editor";
|
import Editor from "@/components/runbooks/editor/Editor";
|
||||||
import List from "@/components/runbooks/List";
|
import List from "@/components/runbooks/List";
|
||||||
|
|
||||||
|
import { Checkbox } from "@nextui-org/react";
|
||||||
|
|
||||||
import { useStore } from "@/state/store";
|
import { useStore } from "@/state/store";
|
||||||
|
|
||||||
export default function Runbooks() {
|
export default function Runbooks() {
|
||||||
@ -8,7 +11,11 @@ export default function Runbooks() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full !max-w-full flex-row ">
|
<div className="flex w-full !max-w-full flex-row ">
|
||||||
<List />
|
<List />
|
||||||
{currentRunbook && <Editor />}
|
{currentRunbook && (
|
||||||
|
<div className="flex w-full">
|
||||||
|
<Editor />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{!currentRunbook && (
|
{!currentRunbook && (
|
||||||
<div className="flex align-middle justify-center flex-col h-screen w-full">
|
<div className="flex align-middle justify-center flex-col h-screen w-full">
|
||||||
|
Loading…
Reference in New Issue
Block a user