aboutsummaryrefslogtreecommitdiffstats
path: root/ui/src/components/runbooks
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/components/runbooks')
-rw-r--r--ui/src/components/runbooks/List.tsx141
-rw-r--r--ui/src/components/runbooks/editor/Editor.tsx200
-rw-r--r--ui/src/components/runbooks/editor/blocks/Directory/index.tsx89
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/extensions.ts158
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/index.css9
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/index.tsx229
-rw-r--r--ui/src/components/runbooks/editor/blocks/Run/terminal.tsx113
-rw-r--r--ui/src/components/runbooks/editor/index.css7
-rw-r--r--ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx28
9 files changed, 0 insertions, 974 deletions
diff --git a/ui/src/components/runbooks/List.tsx b/ui/src/components/runbooks/List.tsx
deleted file mode 100644
index 42da3885..00000000
--- a/ui/src/components/runbooks/List.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import { useEffect } from "react";
-import {
- Button,
- ButtonGroup,
- Tooltip,
- Listbox,
- ListboxItem,
- Dropdown,
- DropdownTrigger,
- DropdownMenu,
- DropdownItem,
- Badge,
-} from "@nextui-org/react";
-
-import { EllipsisVerticalIcon } from "lucide-react";
-
-import { DateTime } from "luxon";
-
-import { NotebookPenIcon } from "lucide-react";
-import Runbook from "@/state/runbooks/runbook";
-import { AtuinState, useStore } from "@/state/store";
-
-const NoteSidebar = () => {
- const runbooks = useStore((state: AtuinState) => state.runbooks);
- const refreshRunbooks = useStore(
- (state: AtuinState) => state.refreshRunbooks,
- );
-
- const currentRunbook = useStore((state: AtuinState) => state.currentRunbook);
- const setCurrentRunbook = useStore(
- (state: AtuinState) => state.setCurrentRunbook,
- );
- const runbookInfo = useStore((state: AtuinState) => state.runbookInfo);
-
- useEffect(() => {
- refreshRunbooks();
- }, []);
-
- return (
- <div className="w-48 flex flex-col border-r-1">
- <div className="overflow-y-auto flex-grow">
- <Listbox
- hideSelectedIcon
- items={runbooks.map((runbook: any): any => {
- return [runbook, runbookInfo[runbook.id]];
- })}
- variant="flat"
- aria-label="Runbook list"
- selectionMode="single"
- selectedKeys={currentRunbook ? [currentRunbook] : []}
- itemClasses={{ base: "data-[selected=true]:bg-gray-200" }}
- topContent={
- <ButtonGroup className="z-20">
- <Tooltip showArrow content="New Runbook" closeDelay={50}>
- <Button
- isIconOnly
- aria-label="New note"
- variant="light"
- size="sm"
- onPress={async () => {
- // otherwise the cursor is weirdly positioned in the new document
- window.getSelection()?.removeAllRanges();
-
- let runbook = await Runbook.create();
- setCurrentRunbook(runbook.id);
- refreshRunbooks();
- }}
- >
- <NotebookPenIcon className="p-[0.15rem]" />
- </Button>
- </Tooltip>
- </ButtonGroup>
- }
- >
- {([runbook, info]: [Runbook, { ptys: number }]) => (
- <ListboxItem
- key={runbook.id}
- onPress={() => {
- setCurrentRunbook(runbook.id);
- }}
- textValue={runbook.name || "Untitled"}
- endContent={
- <Dropdown>
- <Badge
- content={info?.ptys}
- color="primary"
- style={
- info && info?.ptys > 0
- ? {}
- : {
- display: "none",
- }
- }
- >
- <DropdownTrigger className="bg-transparent">
- <Button isIconOnly>
- <EllipsisVerticalIcon
- size="16px"
- className="bg-transparent"
- />
- </Button>
- </DropdownTrigger>
- </Badge>
- <DropdownMenu aria-label="Dynamic Actions">
- <DropdownItem
- key={"delete"}
- color="danger"
- className="text-danger"
- onPress={async () => {
- await Runbook.delete(runbook.id);
-
- if (runbook.id == currentRunbook) setCurrentRunbook("");
-
- refreshRunbooks();
- }}
- >
- Delete
- </DropdownItem>
- </DropdownMenu>
- </Dropdown>
- }
- >
- <div className="flex flex-col">
- <div className="text-md">{runbook.name || "Untitled"}</div>
- <div className="text-xs text-gray-500">
- <em>
- {DateTime.fromJSDate(runbook.updated).toLocaleString(
- DateTime.DATETIME_SHORT,
- )}
- </em>
- </div>
- </div>
- </ListboxItem>
- )}
- </Listbox>
- </div>
- </div>
- );
-};
-
-export default NoteSidebar;
diff --git a/ui/src/components/runbooks/editor/Editor.tsx b/ui/src/components/runbooks/editor/Editor.tsx
deleted file mode 100644
index 6b0522f5..00000000
--- a/ui/src/components/runbooks/editor/Editor.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-
-import "./index.css";
-
-import { Spinner } from "@nextui-org/react";
-
-// Errors, but it all works fine and is there. Maybe missing ts defs?
-// I'll figure it out later
-import {
- // @ts-ignore
- BlockNoteSchema,
- // @ts-ignore
- BlockNoteEditor,
- // @ts-ignore
- defaultBlockSpecs,
- // @ts-ignore
- filterSuggestionItems,
- // @ts-ignore
- insertOrUpdateBlock,
-} from "@blocknote/core";
-
-import {
- //@ts-ignore
- SuggestionMenuController,
- // @ts-ignore
- AddBlockButton,
- // @ts-ignore
- getDefaultReactSlashMenuItems,
- // @ts-ignore
- SideMenu,
- // @ts-ignore
- SideMenuController,
-} from "@blocknote/react";
-import { BlockNoteView } from "@blocknote/mantine";
-
-import "@blocknote/core/fonts/inter.css";
-import "@blocknote/mantine/style.css";
-
-import { CodeIcon, FolderOpenIcon } from "lucide-react";
-import { useDebounceCallback } from "usehooks-ts";
-
-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";
-
-// Our schema with block specs, which contain the configs and implementations for blocks
-// that we want our editor to use.
-const schema = BlockNoteSchema.create({
- blockSpecs: {
- // Adds all default blocks.
- ...defaultBlockSpecs,
-
- // Adds the code block.
- run: Run,
- directory: Directory,
- },
-});
-
-// Slash menu item to insert an Alert block
-const insertRun = (editor: typeof schema.BlockNoteEditor) => ({
- title: "Code",
- onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "run",
- });
- },
- icon: <CodeIcon size={18} />,
- aliases: ["code", "run"],
- 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() {
- const runbookId = useStore((store: AtuinState) => store.currentRunbook);
- const refreshRunbooks = useStore(
- (store: AtuinState) => store.refreshRunbooks,
- );
- let [runbook, setRunbook] = useState<Runbook | null>(null);
-
- useEffect(() => {
- if (!runbookId) return;
-
- const fetchRunbook = async () => {
- let rb = await Runbook.load(runbookId);
-
- setRunbook(rb);
- };
-
- fetchRunbook();
- }, [runbookId]);
-
- const onChange = async () => {
- if (!runbook) return;
-
- console.log("saved!");
- runbook.name = fetchName();
- if (editor) runbook.content = JSON.stringify(editor.document);
-
- await runbook.save();
- refreshRunbooks();
- };
-
- const debouncedOnChange = useDebounceCallback(onChange, 1000);
-
- const editor = useMemo(() => {
- if (!runbook) return undefined;
- if (runbook.content) {
- return BlockNoteEditor.create({
- initialContent: JSON.parse(runbook.content),
- schema,
- });
- }
-
- return BlockNoteEditor.create({ schema });
- }, [runbook]);
-
- const fetchName = (): string => {
- // Infer the title from the first text block
- if (!editor) return "Untitled";
-
- let blocks = editor.document;
- for (const block of blocks) {
- if (block.type == "heading" || block.type == "paragraph") {
- if (block.content.length == 0) continue;
- // @ts-ignore
- if (block.content[0].text.length == 0) continue;
-
- // @ts-ignore
- return block.content[0].text;
- }
- }
-
- return "Untitled";
- };
-
- if (!runbook) {
- return (
- <div className="flex w-full h-full flex-col justify-center items-center">
- <Spinner />
- </div>
- );
- }
-
- if (editor === undefined) {
- return (
- <div className="flex w-full h-full flex-col justify-center items-center">
- <Spinner />
- </div>
- );
- }
-
- // Renders the editor instance.
- return (
- <div className="overflow-y-scroll w-full">
- <BlockNoteView
- editor={editor}
- slashMenu={false}
- sideMenu={false}
- onChange={debouncedOnChange}
- >
- <SuggestionMenuController
- triggerCharacter={"/"}
- getItems={async (query: any) =>
- filterSuggestionItems(
- [
- ...getDefaultReactSlashMenuItems(editor),
- insertRun(editor),
- insertDirectory(editor),
- ],
- query,
- )
- }
- />
-
- <SideMenuController
- sideMenu={(props: any) => (
- <SideMenu {...props}>
- <AddBlockButton {...props} />
- <DeleteBlock {...props} />
- </SideMenu>
- )}
- />
- </BlockNoteView>
- </div>
- );
-}
diff --git a/ui/src/components/runbooks/editor/blocks/Directory/index.tsx b/ui/src/components/runbooks/editor/blocks/Directory/index.tsx
deleted file mode 100644
index 3e4f93d9..00000000
--- a/ui/src/components/runbooks/editor/blocks/Directory/index.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { useState } from "react";
-import { Input, Tooltip, Button } from "@nextui-org/react";
-import { FolderInputIcon } from "lucide-react";
-
-// @ts-ignore
-import { createReactBlockSpec } from "@blocknote/react";
-
-import { open } from "@tauri-apps/plugin-dialog";
-
-interface DirectoryProps {
- path: string;
- onInputChange: (val: string) => void;
-}
-
-const Directory = ({ path, onInputChange }: DirectoryProps) => {
- const [value, setValue] = useState(path);
-
- const selectFolder = async () => {
- const path = await open({
- multiple: false,
- directory: true,
- });
-
- setValue(path || "");
- onInputChange(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}
- >
- <div className="flex flex-row">
- <div className="mr-2">
- <Button
- isIconOnly
- variant="flat"
- aria-label="Select folder"
- onPress={selectFolder}
- >
- <FolderInputIcon />
- </Button>
- </div>
-
- <div className="w-full">
- <Input
- placeholder="~"
- value={value}
- autoComplete="off"
- autoCapitalize="off"
- autoCorrect="off"
- spellCheck="false"
- onValueChange={(val) => {
- setValue(val);
- onInputChange(val);
- }}
- />
- </div>
- </div>
- </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/Run/extensions.ts b/ui/src/components/runbooks/editor/blocks/Run/extensions.ts
deleted file mode 100644
index 76fc4343..00000000
--- a/ui/src/components/runbooks/editor/blocks/Run/extensions.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-// Based on the basicSetup extension, as suggested by the source. Customized for Atuin.
-
-import {
- KeyBinding,
- lineNumbers,
- highlightActiveLineGutter,
- highlightSpecialChars,
- drawSelection,
- dropCursor,
- rectangularSelection,
- crosshairCursor,
- highlightActiveLine,
- keymap,
-} from "@codemirror/view";
-import { EditorState, Extension } from "@codemirror/state";
-import { history, defaultKeymap, historyKeymap } from "@codemirror/commands";
-import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
-
-import {
- closeBrackets,
- autocompletion,
- closeBracketsKeymap,
- completionKeymap,
- CompletionContext,
-} from "@codemirror/autocomplete";
-
-import {
- foldGutter,
- indentOnInput,
- syntaxHighlighting,
- defaultHighlightStyle,
- bracketMatching,
- indentUnit,
- foldKeymap,
-} from "@codemirror/language";
-
-import { lintKeymap } from "@codemirror/lint";
-import { invoke } from "@tauri-apps/api/core";
-
-export interface MinimalSetupOptions {
- highlightSpecialChars?: boolean;
- history?: boolean;
- drawSelection?: boolean;
- syntaxHighlighting?: boolean;
-
- defaultKeymap?: boolean;
- historyKeymap?: boolean;
-}
-
-export interface BasicSetupOptions extends MinimalSetupOptions {
- lineNumbers?: boolean;
- highlightActiveLineGutter?: boolean;
- foldGutter?: boolean;
- dropCursor?: boolean;
- allowMultipleSelections?: boolean;
- indentOnInput?: boolean;
- bracketMatching?: boolean;
- closeBrackets?: boolean;
- autocompletion?: boolean;
- rectangularSelection?: boolean;
- crosshairCursor?: boolean;
- highlightActiveLine?: boolean;
- highlightSelectionMatches?: boolean;
-
- closeBracketsKeymap?: boolean;
- searchKeymap?: boolean;
- foldKeymap?: boolean;
- completionKeymap?: boolean;
- lintKeymap?: boolean;
- tabSize?: number;
-}
-
-function myCompletions(context: CompletionContext) {
- let word = context.matchBefore(/^.*/);
-
- if (!word) return null;
- if (word.from == word.to && !context.explicit) return null;
-
- return invoke("prefix_search", { query: word.text }).then(
- // @ts-ignore
- (results: string[]) => {
- let options = results.map((i) => {
- return { label: i, type: "text" };
- });
-
- return {
- from: word.from,
- options,
- };
- },
- );
-}
-
-const buildAutocomplete = (): Extension => {
- let ac = autocompletion({ override: [myCompletions] });
-
- return ac;
-};
-
-export const extensions = (options: BasicSetupOptions = {}): Extension[] => {
- const { crosshairCursor: initCrosshairCursor = false } = options;
-
- let keymaps: KeyBinding[] = [];
- if (options.closeBracketsKeymap !== false) {
- keymaps = keymaps.concat(closeBracketsKeymap);
- }
- if (options.defaultKeymap !== false) {
- keymaps = keymaps.concat(defaultKeymap);
- }
- if (options.searchKeymap !== false) {
- keymaps = keymaps.concat(searchKeymap);
- }
- if (options.historyKeymap !== false) {
- keymaps = keymaps.concat(historyKeymap);
- }
- if (options.foldKeymap !== false) {
- keymaps = keymaps.concat(foldKeymap);
- }
- if (options.completionKeymap !== false) {
- keymaps = keymaps.concat(completionKeymap);
- }
- if (options.lintKeymap !== false) {
- keymaps = keymaps.concat(lintKeymap);
- }
- const extensions: Extension[] = [];
- if (options.lineNumbers !== false) extensions.push(lineNumbers());
- if (options.highlightActiveLineGutter !== false)
- extensions.push(highlightActiveLineGutter());
- if (options.highlightSpecialChars !== false)
- extensions.push(highlightSpecialChars());
- if (options.history !== false) extensions.push(history());
- if (options.foldGutter !== false) extensions.push(foldGutter());
- if (options.drawSelection !== false) extensions.push(drawSelection());
- if (options.dropCursor !== false) extensions.push(dropCursor());
- if (options.allowMultipleSelections !== false)
- extensions.push(EditorState.allowMultipleSelections.of(true));
- if (options.indentOnInput !== false) extensions.push(indentOnInput());
- if (options.syntaxHighlighting !== false)
- extensions.push(
- syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
- );
-
- if (options.bracketMatching !== false) extensions.push(bracketMatching());
- if (options.closeBrackets !== false) extensions.push(closeBrackets());
- if (options.autocompletion !== false) extensions.push(buildAutocomplete());
-
- if (options.rectangularSelection !== false)
- extensions.push(rectangularSelection());
- if (initCrosshairCursor !== false) extensions.push(crosshairCursor());
- if (options.highlightActiveLine !== false)
- extensions.push(highlightActiveLine());
- if (options.highlightSelectionMatches !== false)
- extensions.push(highlightSelectionMatches());
- if (options.tabSize && typeof options.tabSize === "number")
- extensions.push(indentUnit.of(" ".repeat(options.tabSize)));
-
- return extensions.concat([keymap.of(keymaps.flat())]).filter(Boolean);
-};
diff --git a/ui/src/components/runbooks/editor/blocks/Run/index.css b/ui/src/components/runbooks/editor/blocks/Run/index.css
deleted file mode 100644
index e854c03b..00000000
--- a/ui/src/components/runbooks/editor/blocks/Run/index.css
+++ /dev/null
@@ -1,9 +0,0 @@
-ProseMirror-focused {
- outline: none !important;
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1) !important;
-}
-
-.cm-editor.cm-focused {
- outline: none !important;
- box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1) !important;
-}
diff --git a/ui/src/components/runbooks/editor/blocks/Run/index.tsx b/ui/src/components/runbooks/editor/blocks/Run/index.tsx
deleted file mode 100644
index bef083ba..00000000
--- a/ui/src/components/runbooks/editor/blocks/Run/index.tsx
+++ /dev/null
@@ -1,229 +0,0 @@
-// @ts-ignore
-import { createReactBlockSpec } from "@blocknote/react";
-
-import "./index.css";
-
-import CodeMirror from "@uiw/react-codemirror";
-import { keymap } from "@codemirror/view";
-import { langs } from "@uiw/codemirror-extensions-langs";
-
-import { Play, Square } from "lucide-react";
-import { useState } from "react";
-
-import { extensions } from "./extensions";
-import { platform } from "@tauri-apps/plugin-os";
-import { invoke } from "@tauri-apps/api/core";
-import Terminal from "./terminal.tsx";
-
-import "@xterm/xterm/css/xterm.css";
-import { AtuinState, useStore } from "@/state/store.ts";
-
-interface RunBlockProps {
- onChange: (val: string) => void;
- onRun?: (pty: string) => void;
- onStop?: (pty: string) => void;
- id: string;
- code: string;
- 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,
- code,
- isEditable,
- onRun,
- onStop,
- pty,
- editor,
-}: RunBlockProps) => {
- const [value, setValue] = useState<String>(code);
- const cleanupPtyTerm = useStore((store: AtuinState) => store.cleanupPtyTerm);
- const terminals = useStore((store: AtuinState) => store.terminals);
-
- const [currentRunbook, incRunbookPty, decRunbookPty] = useStore(
- (store: AtuinState) => [
- store.currentRunbook,
- store.incRunbookPty,
- store.decRunbookPty,
- ],
- );
-
- const isRunning = pty !== null && pty !== "";
-
- const handleToggle = async (event: any | null) => {
- if (event) event.stopPropagation();
-
- // If there's no code, don't do anything
- if (!value) return;
-
- if (isRunning) {
- await invoke("pty_kill", { pid: pty });
-
- terminals[pty].terminal.dispose();
- cleanupPtyTerm(pty);
-
- if (onStop) onStop(pty);
- if (currentRunbook) decRunbookPty(currentRunbook);
- }
-
- if (!isRunning) {
- let cwd = findFirstParentOfType(editor, id, "directory");
-
- if (cwd) {
- cwd = cwd.props.path;
- } else {
- cwd = "~";
- }
-
- let pty = await invoke<string>("pty_open", { cwd });
- if (onRun) onRun(pty);
-
- if (currentRunbook) incRunbookPty(currentRunbook);
-
- let isWindows = platform() == "windows";
- let cmdEnd = isWindows ? "\r\n" : "\n";
-
- let val = !value.endsWith("\n") ? value + cmdEnd : value;
- await invoke("pty_write", { pid: pty, data: val });
- }
- };
-
- const handleCmdEnter = () => {
- handleToggle(null);
- return true;
- };
-
- const customKeymap = keymap.of([
- {
- key: "Mod-Enter",
- run: handleCmdEnter,
- },
- ]);
-
- return (
- <div className="w-full !max-w-full !outline-none overflow-none">
- <div className="flex flex-row items-start">
- <div className="flex">
- <button
- onClick={handleToggle}
- className={`flex items-center justify-center flex-shrink-0 w-8 h-8 mr-2 rounded border focus:outline-none focus:ring-2 transition-all duration-300 ease-in-out ${
- isRunning
- ? "border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:border-red-300 focus:ring-red-300"
- : "border-green-200 bg-green-50 text-green-600 hover:bg-green-100 hover:border-green-300 focus:ring-green-300"
- }`}
- aria-label={isRunning ? "Stop code" : "Run code"}
- >
- <span
- className={`inline-block transition-transform duration-300 ease-in-out ${isRunning ? "rotate-180" : ""}`}
- >
- {isRunning ? <Square size={16} /> : <Play size={16} />}
- </span>
- </button>
- </div>
- <div className="flex-1 min-w-0 w-40">
- <CodeMirror
- id={id}
- placeholder={"Write your code here..."}
- className="!pt-0 max-w-full border border-gray-300 rounded"
- value={code}
- editable={isEditable}
- autoFocus
- onChange={(val) => {
- setValue(val);
- onChange(val);
- }}
- extensions={[customKeymap, ...extensions(), langs.shell()]}
- basicSetup={false}
- />
- <div
- className={`overflow-hidden transition-all duration-300 ease-in-out min-w-0 ${
- isRunning ? "block" : "hidden"
- }`}
- >
- {pty && <Terminal pty={pty} />}
- </div>
- </div>
- </div>
- </div>
- );
-};
-
-export default createReactBlockSpec(
- {
- type: "run",
- propSchema: {
- type: {
- default: "bash",
- },
- code: { default: "" },
- pty: { default: "" },
- global: { default: false },
- },
- content: "none",
- },
- {
- // @ts-ignore
- render: ({ block, editor, code, type }) => {
- const onInputChange = (val: string) => {
- editor.updateBlock(block, {
- // @ts-ignore
- props: { ...block.props, code: val },
- });
- };
-
- const onRun = (pty: string) => {
- editor.updateBlock(block, {
- // @ts-ignore
- props: { ...block.props, pty: pty },
- });
- };
-
- const onStop = (_pty: string) => {
- editor?.updateBlock(block, {
- props: { ...block.props, pty: "" },
- });
- };
-
- return (
- <RunBlock
- onChange={onInputChange}
- id={block?.id}
- code={block.props.code}
- type={block.props.type}
- pty={block.props.pty}
- isEditable={editor.isEditable}
- onRun={onRun}
- onStop={onStop}
- editor={editor}
- />
- );
- },
- toExternalHTML: ({ block }) => {
- return (
- <pre lang="beep boop">
- <code lang="bash">{block?.props?.code}</code>
- </pre>
- );
- },
- },
-);
diff --git a/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx b/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx
deleted file mode 100644
index a6dc589f..00000000
--- a/ui/src/components/runbooks/editor/blocks/Run/terminal.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import { useState, useEffect, useRef } from "react";
-import { listen } from "@tauri-apps/api/event";
-import "@xterm/xterm/css/xterm.css";
-import { useStore } from "@/state/store";
-import { invoke } from "@tauri-apps/api/core";
-import { IDisposable } from "@xterm/xterm";
-
-const usePersistentTerminal = (pty: string) => {
- const newPtyTerm = useStore((store) => store.newPtyTerm);
- const terminals = useStore((store) => store.terminals);
- const [isReady, setIsReady] = useState(false);
-
- useEffect(() => {
- if (!terminals.hasOwnProperty(pty)) {
- // create a new terminal and store it in the store.
- // this means we can resume the same instance even across mount/dismount
- newPtyTerm(pty);
- }
-
- setIsReady(true);
-
- return () => {
- // We don't dispose of the terminal when the component unmounts
- };
- }, [pty, terminals, newPtyTerm]);
-
- return { terminalData: terminals[pty], isReady };
-};
-
-const TerminalComponent = ({ pty }: any) => {
- const terminalRef = useRef(null);
- const { terminalData, isReady } = usePersistentTerminal(pty);
- const [isAttached, setIsAttached] = useState(false);
- const cleanupListenerRef = useRef<(() => void) | null>(null);
- const keyDispose = useRef<IDisposable | null>(null);
-
- useEffect(() => {
- // no pty? no terminal
- if (pty == null) return;
-
- // the terminal may still be being created so hold off
- if (!isReady) return;
-
- const windowResize = () => {
- if (!terminalData || !terminalData.fitAddon) return;
-
- terminalData.fitAddon.fit();
- };
-
- // terminal object needs attaching to a ref to a div
- if (!isAttached && terminalData && terminalData.terminal) {
- // If it's never been attached, attach it
- if (!terminalData.terminal.element && terminalRef.current) {
- terminalData.terminal.open(terminalRef.current);
-
- // it might have been previously attached, but need moving elsewhere
- } else if (terminalData && terminalRef.current) {
- // @ts-ignore
- terminalRef.current.appendChild(terminalData.terminal.element);
- }
-
- terminalData.fitAddon.fit();
- setIsAttached(true);
-
- window.addEventListener("resize", windowResize);
-
- const disposeOnKey = terminalData.terminal.onKey(async (event) => {
- await invoke("pty_write", { pid: pty, data: event.key });
- });
-
- keyDispose.current = disposeOnKey;
- }
-
- listen(`pty-${pty}`, (event: any) => {
- terminalData.terminal.write(event.payload);
- }).then((ul) => {
- cleanupListenerRef.current = ul;
- });
-
- // Customize further as needed
- return () => {
- if (
- terminalData &&
- terminalData.terminal &&
- terminalData.terminal.element
- ) {
- // Instead of removing, we just detach
- if (terminalData.terminal.element.parentElement) {
- terminalData.terminal.element.parentElement.removeChild(
- terminalData.terminal.element,
- );
- }
- setIsAttached(false);
- }
-
- if (cleanupListenerRef.current) {
- cleanupListenerRef.current();
- }
-
- if (keyDispose.current) keyDispose.current.dispose();
-
- window.removeEventListener("resize", windowResize);
- };
- }, [terminalData, isReady]);
-
- if (!isReady) return null;
-
- return (
- <div className="!max-w-full min-w-0 overflow-hidden" ref={terminalRef} />
- );
-};
-
-export default TerminalComponent;
diff --git a/ui/src/components/runbooks/editor/index.css b/ui/src/components/runbooks/editor/index.css
deleted file mode 100644
index 067cc500..00000000
--- a/ui/src/components/runbooks/editor/index.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.editor a {
- color: #0000ee;
-}
-
-.editor a:hover {
- cursor: pointer;
-}
diff --git a/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx b/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx
deleted file mode 100644
index 84a9f5c8..00000000
--- a/ui/src/components/runbooks/editor/ui/DeleteBlockButton.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import {
- SideMenuProps,
- useBlockNoteEditor,
- useComponentsContext,
-} from "@blocknote/react";
-import { TrashIcon } from "lucide-react";
-
-// Custom Side Menu button to remove the hovered block.
-export function DeleteBlock(props: SideMenuProps) {
- const editor = useBlockNoteEditor();
-
- const Components = useComponentsContext()!;
-
- return (
- <Components.SideMenu.Button
- label="Remove block"
- className="mx-1"
- icon={
- <TrashIcon
- size={24}
- onClick={() => {
- editor.removeBlocks([props.block]);
- }}
- />
- }
- />
- );
-}