Repository navigation
feat(desktop): pane context menus + binary tree layout #3196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { useDefaultContextMenuActions } from "./useDefaultContextMenuActions"; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,160 @@ | ||||||
| import type { ContextMenuActionConfig, RendererContext } from "@superset/panes"; | ||||||
| import { useMemo } from "react"; | ||||||
| import { | ||||||
| LuColumns2, | ||||||
| LuEqual, | ||||||
| LuGlobe, | ||||||
| LuMessageSquare, | ||||||
| LuMoveRight, | ||||||
| LuPlus, | ||||||
| LuRows2, | ||||||
| LuX, | ||||||
| } from "react-icons/lu"; | ||||||
| import { useHotkeyDisplay } from "renderer/hotkeys"; | ||||||
| import type { | ||||||
| BrowserPaneData, | ||||||
| ChatPaneData, | ||||||
| PaneViewerData, | ||||||
| TerminalPaneData, | ||||||
| } from "../../types"; | ||||||
|
|
||||||
| export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneViewerData>[] { | ||||||
| const splitDownShortcut = useHotkeyDisplay("SPLIT_DOWN").text; | ||||||
| const splitRightShortcut = useHotkeyDisplay("SPLIT_RIGHT").text; | ||||||
| const splitWithChatShortcut = useHotkeyDisplay("SPLIT_WITH_CHAT").text; | ||||||
| const splitWithBrowserShortcut = useHotkeyDisplay("SPLIT_WITH_BROWSER").text; | ||||||
| const equalizePaneSplitsShortcut = useHotkeyDisplay( | ||||||
| "EQUALIZE_PANE_SPLITS", | ||||||
| ).text; | ||||||
| const closePaneShortcut = useHotkeyDisplay("CLOSE_TERMINAL").text; | ||||||
|
|
||||||
| return useMemo<ContextMenuActionConfig<PaneViewerData>[]>( | ||||||
| () => [ | ||||||
| { | ||||||
| key: "split-horizontal", | ||||||
| label: "Split Horizontally", | ||||||
| icon: <LuRows2 />, | ||||||
| shortcut: | ||||||
| splitDownShortcut !== "Unassigned" ? splitDownShortcut : undefined, | ||||||
| onSelect: (ctx) => { | ||||||
| ctx.actions.split("down", { | ||||||
| kind: "terminal", | ||||||
| data: { | ||||||
| terminalId: crypto.randomUUID(), | ||||||
| } as TerminalPaneData, | ||||||
| }); | ||||||
| }, | ||||||
| }, | ||||||
| { | ||||||
| key: "split-vertical", | ||||||
| label: "Split Vertically", | ||||||
| icon: <LuColumns2 />, | ||||||
| shortcut: | ||||||
| splitRightShortcut !== "Unassigned" ? splitRightShortcut : undefined, | ||||||
| onSelect: (ctx) => { | ||||||
| ctx.actions.split("right", { | ||||||
| kind: "terminal", | ||||||
| data: { | ||||||
| terminalId: crypto.randomUUID(), | ||||||
| } as TerminalPaneData, | ||||||
| }); | ||||||
| }, | ||||||
| }, | ||||||
| { | ||||||
| key: "split-with-chat", | ||||||
| label: "Split with New Chat", | ||||||
| icon: <LuMessageSquare />, | ||||||
| shortcut: | ||||||
| splitWithChatShortcut !== "Unassigned" | ||||||
| ? splitWithChatShortcut | ||||||
| : undefined, | ||||||
| onSelect: (ctx) => { | ||||||
| ctx.actions.split("right", { | ||||||
| kind: "chat", | ||||||
| data: { sessionId: null } as ChatPaneData, | ||||||
| }); | ||||||
| }, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| }, | ||||||
| { | ||||||
| key: "split-with-browser", | ||||||
| label: "Split with New Browser", | ||||||
| icon: <LuGlobe />, | ||||||
| shortcut: | ||||||
| splitWithBrowserShortcut !== "Unassigned" | ||||||
| ? splitWithBrowserShortcut | ||||||
| : undefined, | ||||||
| onSelect: (ctx) => { | ||||||
| ctx.actions.split("right", { | ||||||
| kind: "browser", | ||||||
| data: { | ||||||
| url: "http://localhost:3000", | ||||||
| mode: "preview", | ||||||
| } as BrowserPaneData, | ||||||
| }); | ||||||
| }, | ||||||
| }, | ||||||
| { | ||||||
| key: "equalize-splits", | ||||||
| label: "Equalize Pane Splits", | ||||||
| icon: <LuEqual />, | ||||||
| shortcut: | ||||||
| equalizePaneSplitsShortcut !== "Unassigned" | ||||||
| ? equalizePaneSplitsShortcut | ||||||
| : undefined, | ||||||
| onSelect: (ctx) => { | ||||||
| ctx.store.getState().equalizeTab({ tabId: ctx.tab.id }); | ||||||
| }, | ||||||
| }, | ||||||
| { key: "sep-move", type: "separator" }, | ||||||
| { | ||||||
| key: "move-to-tab", | ||||||
| label: "Move to Tab", | ||||||
| icon: <LuMoveRight />, | ||||||
| children: (ctx: RendererContext<PaneViewerData>) => { | ||||||
| const tabs = ctx.store.getState().tabs; | ||||||
| const otherTabs = tabs.filter((t) => t.id !== ctx.tab.id); | ||||||
| const items: ContextMenuActionConfig<PaneViewerData>[] = | ||||||
| otherTabs.map((tab) => ({ | ||||||
| key: `move-to-${tab.id}`, | ||||||
| label: tab.titleOverride ?? tab.id, | ||||||
| onSelect: () => { | ||||||
| ctx.store | ||||||
| .getState() | ||||||
| .movePaneToTab({ paneId: ctx.pane.id, targetTabId: tab.id }); | ||||||
| }, | ||||||
| })); | ||||||
| if (otherTabs.length > 0) { | ||||||
| items.push({ key: "sep-new-tab", type: "separator" }); | ||||||
| } | ||||||
| items.push({ | ||||||
| key: "move-to-new-tab", | ||||||
| label: "New Tab", | ||||||
| icon: <LuPlus />, | ||||||
| onSelect: () => { | ||||||
| ctx.store.getState().movePaneToNewTab({ paneId: ctx.pane.id }); | ||||||
| }, | ||||||
| }); | ||||||
| return items; | ||||||
| }, | ||||||
| }, | ||||||
| { key: "sep-close", type: "separator" }, | ||||||
| { | ||||||
| key: "close-pane", | ||||||
| label: "Close Pane", | ||||||
| icon: <LuX />, | ||||||
| variant: "destructive", | ||||||
| shortcut: | ||||||
| closePaneShortcut !== "Unassigned" ? closePaneShortcut : undefined, | ||||||
| onSelect: (ctx) => ctx.actions.close(), | ||||||
| }, | ||||||
| ], | ||||||
| [ | ||||||
| splitDownShortcut, | ||||||
| splitRightShortcut, | ||||||
| splitWithChatShortcut, | ||||||
| splitWithBrowserShortcut, | ||||||
| equalizePaneSplitsShortcut, | ||||||
| closePaneShortcut, | ||||||
| ], | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||
| import type { RendererContext } from "@superset/panes"; | ||||||||||||||||||||||||||||||
| import "@xterm/xterm/css/xterm.css"; | ||||||||||||||||||||||||||||||
| import { useEffect, useRef, useSyncExternalStore } from "react"; | ||||||||||||||||||||||||||||||
| import { useEffect, useMemo, useRef, useSyncExternalStore } from "react"; | ||||||||||||||||||||||||||||||
| import { useHotkey } from "renderer/hotkeys"; | ||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||
| type ConnectionState, | ||||||||||||||||||||||||||||||
| terminalRuntimeRegistry, | ||||||||||||||||||||||||||||||
|
|
@@ -10,6 +11,7 @@ import type { | |||||||||||||||||||||||||||||
| TerminalPaneData, | ||||||||||||||||||||||||||||||
| } from "renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types"; | ||||||||||||||||||||||||||||||
| import { useWorkspaceWsUrl } from "renderer/routes/_authenticated/_dashboard/v2-workspace/providers/WorkspaceTrpcProvider/WorkspaceTrpcProvider"; | ||||||||||||||||||||||||||||||
| import { ScrollToBottomButton } from "renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ScrollToBottomButton"; | ||||||||||||||||||||||||||||||
| import { useTheme } from "renderer/stores/theme"; | ||||||||||||||||||||||||||||||
| import { resolveTerminalThemeType } from "renderer/stores/theme/utils"; | ||||||||||||||||||||||||||||||
| import { useTerminalAppearance } from "./hooks/useTerminalAppearance"; | ||||||||||||||||||||||||||||||
|
|
@@ -76,13 +78,31 @@ export function TerminalPane({ ctx, workspaceId }: TerminalPaneProps) { | |||||||||||||||||||||||||||||
| terminalRuntimeRegistry.updateAppearance(terminalId, appearance); | ||||||||||||||||||||||||||||||
| }, [terminalId, appearance]); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useHotkey("CLEAR_TERMINAL", () => { | ||||||||||||||||||||||||||||||
| terminalRuntimeRegistry.clear(terminalId); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| useHotkey("SCROLL_TO_BOTTOM", () => { | ||||||||||||||||||||||||||||||
| terminalRuntimeRegistry.scrollToBottom(terminalId); | ||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // connectionState in deps ensures terminal ref re-derives after connect/disconnect | ||||||||||||||||||||||||||||||
| // biome-ignore lint/correctness/useExhaustiveDependencies: connectionState is intentionally included to trigger re-derive | ||||||||||||||||||||||||||||||
| const terminal = useMemo( | ||||||||||||||||||||||||||||||
| () => terminalRuntimeRegistry.getTerminal(terminalId), | ||||||||||||||||||||||||||||||
| [terminalId, connectionState], | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className="flex h-full w-full flex-col p-2"> | ||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||
| ref={containerRef} | ||||||||||||||||||||||||||||||
| className="min-h-0 flex-1 overflow-hidden" | ||||||||||||||||||||||||||||||
| style={{ backgroundColor: appearance.background }} | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| <div className="relative min-h-0 flex-1 overflow-hidden"> | ||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||
| ref={containerRef} | ||||||||||||||||||||||||||||||
|
Comment on lines
96
to
+100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The The biome-ignore comment claims “connectionState triggers re-derive after attach”, but this is incorrect:
Suggested change
|
||||||||||||||||||||||||||||||
| className="h-full w-full" | ||||||||||||||||||||||||||||||
| style={{ backgroundColor: appearance.background }} | ||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||
| <ScrollToBottomButton terminal={terminal} /> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| {connectionState === "closed" && ( | ||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2 border-t border-border px-3 py-1.5 text-xs text-muted-foreground"> | ||||||||||||||||||||||||||||||
| <span>Disconnected</span> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: superset-sh/superset
Length of output: 874
🏁 Script executed:
Repository: superset-sh/superset
Length of output: 437
Use
CLOSE_PANEhotkey instead ofCLOSE_TERMINALfor the generic close pane action.Line 29 uses
useHotkeyDisplay("CLOSE_TERMINAL")forclosePaneShortcut, but a dedicatedCLOSE_PANEhotkey is defined in the registry (with label "Close Pane"). The "Close Pane" action is generic and applies to all pane types, not just terminals, so it should referenceCLOSE_PANEto match the semantic intent.🤖 Prompt for AI Agents