From 75c3542ba0cdd9cc59a296916d7d08a63fc649d7 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Mon, 28 Sep 2026 20:13:08 -0500 Subject: [PATCH] feat(snippets): redesign panel to match the host list --- plugins/snippets/locales/en.json | 54 +- plugins/snippets/manifest.json | 14 + .../snippets/src/frontend/SnippetEditor.tsx | 187 +++ .../src/frontend/SnippetFolderDialog.tsx | 141 +++ .../snippets/src/frontend/SnippetSettings.tsx | 77 ++ plugins/snippets/src/frontend/SnippetTree.tsx | 334 +++++ .../snippets/src/frontend/SnippetsPanel.tsx | 1097 ++++++++--------- .../snippets/src/frontend/folder-icons.tsx | 41 + plugins/snippets/src/frontend/settings.ts | 15 + .../src/frontend/use-snippet-runner.tsx | 8 +- .../snippets/tests/frontend/settings.test.ts | 26 + .../tests/frontend/snippets-panel.test.tsx | 139 +++ 12 files changed, 1520 insertions(+), 613 deletions(-) create mode 100644 plugins/snippets/src/frontend/SnippetEditor.tsx create mode 100644 plugins/snippets/src/frontend/SnippetFolderDialog.tsx create mode 100644 plugins/snippets/src/frontend/SnippetSettings.tsx create mode 100644 plugins/snippets/src/frontend/SnippetTree.tsx create mode 100644 plugins/snippets/src/frontend/folder-icons.tsx create mode 100644 plugins/snippets/src/frontend/settings.ts create mode 100644 plugins/snippets/tests/frontend/settings.test.ts create mode 100644 plugins/snippets/tests/frontend/snippets-panel.test.tsx diff --git a/plugins/snippets/locales/en.json b/plugins/snippets/locales/en.json index 85f1d2e36..d452244fa 100644 --- a/plugins/snippets/locales/en.json +++ b/plugins/snippets/locales/en.json @@ -32,6 +32,14 @@ "foldersCollapsed": { "label": "Collapse folders by default", "description": "Start each snippet folder collapsed when you open the Snippets panel." + }, + "showCommands": { + "label": "Show commands", + "description": "Show the first line of each snippet's command under its name." + }, + "confirmExecution": { + "label": "Confirm before running", + "description": "Ask before a command snippet runs in a terminal." } }, "cancel": "Cancel", @@ -45,7 +53,6 @@ "run": "Run", "pasteToTerminal": "Paste to terminal", "copySuccess": "Copied to clipboard", - "shareTitle": "Shared", "importExport": "Import / Export", "importFailed": "Failed to import snippets", "nameLabel": "Name", @@ -61,12 +68,10 @@ "noteLabel": "Note", "notePlaceholder": "Text to paste into the terminal", "createSnippetTitle": "Create snippet", - "createSnippetDescription": "Save a command or note you can run or paste into a terminal.", "createSnippetButton": "Create snippet", "createSuccess": "Snippet created", "createFailed": "Failed to create snippet", "editSnippetTitle": "Edit snippet", - "editSnippetDescription": "Change this snippet's name, content or folder.", "saveSnippetButton": "Save snippet", "updateSuccess": "Snippet updated", "updateFailed": "Failed to update snippet", @@ -88,7 +93,6 @@ "confirmRunButton": "Run", "runSuccess": "Ran \"{{name}}\" on {{count}} terminal(s)", "pasteSuccess": "Pasted \"{{name}}\" into {{count}} terminal(s)", - "foldersCollapsedLabel": "Collapse folders by default", "admin": { "tabTitle": "Snippets", "count": "{{count}} snippets", @@ -105,5 +109,45 @@ "saveFailed": "Failed to save snippet", "saving": "Saving...", "save": "Save" - } + }, + "backToSnippets": "Back to snippets", + "settingsTitle": "Snippet settings", + "settingsDisplayTitle": "Display", + "settingsBehaviorTitle": "Behavior", + "typeCommandHint": "Runs in the active terminal.", + "typeNoteHint": "Pastes into the active terminal without running.", + "variablesHint": "Variables: $HOST, $USER, $PORT, $NAME, ${INPUT_1:Label}", + "doubleClickToRun": "Double click to run", + "sharedBadge": "Shared", + "copyToClipboard": "Copy to clipboard", + "copyFailed": "Failed to copy to clipboard", + "moreOptions": "More options", + "moreActions": "More actions", + "moveToFolder": "Move to folder", + "deleteSnippet": "Delete snippet", + "deleteSnippetConfirm": "Delete snippet \"{{name}}\"?", + "deleteSuccess": "Snippet deleted", + "delete": "Delete", + "folderActions": "Folder actions", + "addSnippetHere": "New snippet here", + "deleteFolder": "Delete folder", + "deleteFolderConfirm": "Delete folder \"{{name}}\"? Its snippets move out of the folder.", + "createFolderDescription": "Group related snippets together.", + "editFolderDescription": "Change this folder's name, color or icon.", + "folderColorLabel": "Color", + "loadFailed": "Failed to load snippets", + "importSkipExisting": "Import (skip existing)", + "importOverwrite": "Import (overwrite existing)", + "importInvalidFile": "That file is not a valid snippets export", + "importSuccess": "Imported {{imported}}, updated {{updated}}, skipped {{skipped}}", + "exportSnippets": "Export snippets", + "exportSuccess": "Snippets exported", + "exportFailed": "Failed to export snippets", + "expandAll": "Expand all folders", + "collapseAll": "Collapse all folders", + "docsLink": "Documentation", + "clearSearch": "Clear search", + "noSearchResults": "No snippets match your search", + "emptyTitle": "No snippets yet", + "emptyDescription": "Save commands you run often, then run them in a terminal with one click." } diff --git a/plugins/snippets/manifest.json b/plugins/snippets/manifest.json index cae197067..6b257870f 100644 --- a/plugins/snippets/manifest.json +++ b/plugins/snippets/manifest.json @@ -79,6 +79,20 @@ "labelKey": "settings.foldersCollapsed.label", "descriptionKey": "settings.foldersCollapsed.description", "default": true + }, + { + "key": "showCommands", + "type": "boolean", + "labelKey": "settings.showCommands.label", + "descriptionKey": "settings.showCommands.description", + "default": true + }, + { + "key": "confirmExecution", + "type": "boolean", + "labelKey": "settings.confirmExecution.label", + "descriptionKey": "settings.confirmExecution.description", + "default": false } ] }, diff --git a/plugins/snippets/src/frontend/SnippetEditor.tsx b/plugins/snippets/src/frontend/SnippetEditor.tsx new file mode 100644 index 000000000..29f4b086e --- /dev/null +++ b/plugins/snippets/src/frontend/SnippetEditor.tsx @@ -0,0 +1,187 @@ +import { useState } from "react"; +import { useTranslation } from "@termix/plugin-sdk/frontend"; +import { ArrowLeft, FileText } from "lucide-react"; +import { Button, Input, SectionCard, Select2 } from "@termix/plugin-sdk/ui"; +import type { Snippet, SnippetFolder } from "./types"; + +export interface SnippetFormValues { + name: string; + content: string; + description: string; + folder: string; + isNote: boolean; +} + +const labelClass = + "text-[10px] font-bold uppercase tracking-widest text-muted-foreground"; + +export function SnippetEditor({ + snippet, + defaultFolder, + folders, + onBack, + onSave, +}: { + /** The snippet being edited, or null to create one. */ + snippet: Snippet | null; + defaultFolder?: string; + folders: SnippetFolder[]; + onBack: () => void; + onSave: (values: SnippetFormValues) => Promise; +}) { + const { t } = useTranslation(); + const [values, setValues] = useState(() => ({ + name: snippet?.name ?? "", + content: snippet?.content ?? "", + description: snippet?.description ?? "", + folder: snippet?.folder ?? defaultFolder ?? "", + isNote: snippet?.isNote ?? false, + })); + const [saving, setSaving] = useState(false); + + const set = ( + key: K, + value: SnippetFormValues[K], + ) => setValues((prev) => ({ ...prev, [key]: value })); + + const canSave = !!values.name.trim() && !!values.content.trim() && !saving; + // A snippet can point at a folder that has no metadata row yet. + const folderNames = Array.from( + new Set([ + ...folders.map((f) => f.name), + ...(values.folder ? [values.folder] : []), + ]), + ).sort((a, b) => a.localeCompare(b)); + + async function handleSave() { + if (!canSave) return; + setSaving(true); + try { + await onSave(values); + } finally { + setSaving(false); + } + } + + return ( +
+ + +
+ } + > +
+
+ + set("name", e.target.value)} + /> +
+
+ + set("description", e.target.value)} + /> +
+
+ + set("folder", e.target.value)} + > + + {folderNames.map((name) => ( + + ))} + +
+
+ +
+ {[false, true].map((isNote) => ( + + ))} +
+ + {t(values.isNote ? "typeNoteHint" : "typeCommandHint")} + +
+
+ +