Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/desktop/src/lib/trpc/routers/settings/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
EXECUTION_MODES,
settings,
TERMINAL_LINK_BEHAVIORS,
type TerminalPreset,
Expand Down Expand Up @@ -43,6 +44,7 @@ export const createSettingsRouter = () => {
description: z.string().optional(),
cwd: z.string(),
commands: z.array(z.string()),
executionMode: z.enum(EXECUTION_MODES).optional(),
}),
)
.mutation(({ input }) => {
Expand Down Expand Up @@ -76,6 +78,7 @@ export const createSettingsRouter = () => {
description: z.string().optional(),
cwd: z.string().optional(),
commands: z.array(z.string()).optional(),
executionMode: z.enum(EXECUTION_MODES).optional(),
}),
}),
)
Expand All @@ -97,6 +100,8 @@ export const createSettingsRouter = () => {
if (input.patch.cwd !== undefined) preset.cwd = input.patch.cwd;
if (input.patch.commands !== undefined)
preset.commands = input.patch.commands;
if (input.patch.executionMode !== undefined)
preset.executionMode = input.patch.executionMode;

localDb
.insert(settings)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { TerminalLinkBehavior, TerminalPreset } from "@superset/local-db";
import type {
ExecutionMode,
TerminalLinkBehavior,
TerminalPreset,
} from "@superset/local-db";
import {
AlertDialog,
AlertDialogContent,
Expand All @@ -20,7 +24,11 @@ import { toast } from "@superset/ui/sonner";
import { Switch } from "@superset/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { useEffect, useMemo, useRef, useState } from "react";
import { HiOutlineCheck, HiOutlinePlus } from "react-icons/hi2";
import {
HiOutlineCheck,
HiOutlinePlus,
HiOutlineQuestionMarkCircle,
} from "react-icons/hi2";
import {
getPresetIcon,
useIsDarkTheme,
Expand Down Expand Up @@ -219,6 +227,20 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
});
};

const handleExecutionModeChange = (rowIndex: number, mode: ExecutionMode) => {
const preset = localPresets[rowIndex];
if (!preset) return;

setLocalPresets((prev) =>
prev.map((p, i) => (i === rowIndex ? { ...p, executionMode: mode } : p)),
);

updatePreset.mutate({
id: preset.id,
patch: { executionMode: mode },
});
};

const handleAddRow = () => {
createPreset.mutate({
name: "",
Expand Down Expand Up @@ -423,7 +445,7 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
};

return (
<div className="p-6 max-w-6xl w-full">
<div className="p-6 max-w-7xl w-full">
<div className="mb-8">
<h2 className="text-xl font-semibold">Terminal</h2>
<p className="text-sm text-muted-foreground mt-1">
Expand Down Expand Up @@ -508,6 +530,25 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
{column.label}
</div>
))}
<Tooltip>
<TooltipTrigger asChild>
<div className="w-28 text-xs font-medium text-muted-foreground uppercase tracking-wider shrink-0 cursor-help flex items-center gap-1">
Mode
<HiOutlineQuestionMarkCircle className="h-3.5 w-3.5" />
</div>
</TooltipTrigger>
<TooltipContent side="top" className="max-w-xs">
<p className="font-medium mb-1">Execution Mode</p>
<p className="text-xs">
<strong>Sequential:</strong> Commands run one after
another in a single terminal (joined with &&)
</p>
<p className="text-xs mt-1">
<strong>Parallel:</strong> Each command runs in its own
split pane within a single tab
</p>
</TooltipContent>
</Tooltip>
<div className="w-20 text-xs font-medium text-muted-foreground uppercase tracking-wider text-center shrink-0">
Actions
</div>
Expand All @@ -532,6 +573,7 @@ export function TerminalSettings({ visibleItems }: TerminalSettingsProps) {
onBlur={handleCellBlur}
onCommandsChange={handleCommandsChange}
onCommandsBlur={handleCommandsBlur}
onExecutionModeChange={handleExecutionModeChange}
onDelete={handleDeleteRow}
onSetDefault={handleSetDefault}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { EXECUTION_MODES, type ExecutionMode } from "@superset/local-db";
import { Button } from "@superset/ui/button";
import { Input } from "@superset/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@superset/ui/select";
import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip";
import { HiOutlineStar, HiStar } from "react-icons/hi2";
import { LuTrash } from "react-icons/lu";
Expand Down Expand Up @@ -63,6 +71,7 @@ interface PresetRowProps {
onBlur: (rowIndex: number, column: PresetColumnKey) => void;
onCommandsChange: (rowIndex: number, commands: string[]) => void;
onCommandsBlur: (rowIndex: number) => void;
onExecutionModeChange: (rowIndex: number, mode: ExecutionMode) => void;
onDelete: (rowIndex: number) => void;
onSetDefault: (presetId: string | null) => void;
}
Expand All @@ -75,6 +84,7 @@ export function PresetRow({
onBlur,
onCommandsChange,
onCommandsBlur,
onExecutionModeChange,
onDelete,
onSetDefault,
}: PresetRowProps) {
Expand Down Expand Up @@ -102,6 +112,24 @@ export function PresetRow({
/>
</div>
))}
<div className="w-28 shrink-0 pt-0.5">
<Select
value={preset.executionMode ?? "sequential"}
onValueChange={(value) => {
if (EXECUTION_MODES.includes(value as ExecutionMode)) {
onExecutionModeChange(rowIndex, value as ExecutionMode);
}
}}
>
<SelectTrigger className="h-8 w-full text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="sequential">Sequential</SelectItem>
<SelectItem value="parallel">Parallel</SelectItem>
</SelectContent>
</Select>
</div>
<div className="w-20 flex justify-center gap-1 shrink-0 pt-1">
<Tooltip>
<TooltipTrigger asChild>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,25 @@ export function GroupStrip() {
addTab(activeWorkspaceId);
};

const addTabWithMultiplePanes = useTabsStore(
(s) => s.addTabWithMultiplePanes,
);

const handleSelectPreset = (preset: TerminalPreset) => {
if (!activeWorkspaceId) return;

const { tabId } = addTab(activeWorkspaceId, {
initialCommands: preset.commands,
initialCwd: preset.cwd || undefined,
});
const isParallel =
preset.executionMode === "parallel" && preset.commands.length > 1;

const { tabId } = isParallel
? addTabWithMultiplePanes(activeWorkspaceId, {
commands: preset.commands,
initialCwd: preset.cwd || undefined,
})
: addTab(activeWorkspaceId, {
initialCommands: preset.commands,
initialCwd: preset.cwd || undefined,
});

if (preset.name) {
renameTab(tabId, preset.name);
Expand Down
72 changes: 71 additions & 1 deletion apps/desktop/src/renderer/stores/tabs/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ import { trpcTabsStorage } from "renderer/lib/trpc-storage";
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
import { movePaneToNewTab, movePaneToTab } from "./actions/move-pane";
import type { AddFileViewerPaneOptions, TabsState, TabsStore } from "./types";
import type {
AddFileViewerPaneOptions,
AddTabWithMultiplePanesOptions,
TabsState,
TabsStore,
} from "./types";
import {
buildMultiPaneLayout,
type CreatePaneOptions,
createFileViewerPane,
createPane,
createTabWithPane,
extractPaneIdsFromLayout,
generateId,
generateTabName,
getAdjacentPaneId,
getFirstPaneId,
getPaneIdsForTab,
Expand Down Expand Up @@ -121,6 +129,68 @@ export const useTabsStore = create<TabsStore>()(
return { tabId: tab.id, paneId: pane.id };
},

addTabWithMultiplePanes: (
workspaceId: string,
options: AddTabWithMultiplePanesOptions,
) => {
const state = get();
const tabId = generateId("tab");
const panes: ReturnType<typeof createPane>[] = options.commands.map(
(command) =>
createPane(tabId, "terminal", {
initialCommands: [command],
initialCwd: options.initialCwd,
}),
);

const paneIds = panes.map((p) => p.id);
const layout = buildMultiPaneLayout(paneIds);
const workspaceTabs = state.tabs.filter(
(t) => t.workspaceId === workspaceId,
);

const tab = {
id: tabId,
name: generateTabName(workspaceTabs),
workspaceId,
layout,
createdAt: Date.now(),
};

const panesRecord: Record<string, (typeof panes)[number]> = {};
for (const pane of panes) {
panesRecord[pane.id] = pane;
}

const currentActiveId = state.activeTabIds[workspaceId];
const historyStack = state.tabHistoryStacks[workspaceId] || [];
const newHistoryStack = currentActiveId
? [
currentActiveId,
...historyStack.filter((id) => id !== currentActiveId),
]
: historyStack;

set({
tabs: [...state.tabs, tab],
panes: { ...state.panes, ...panesRecord },
activeTabIds: {
...state.activeTabIds,
[workspaceId]: tab.id,
},
focusedPaneIds: {
...state.focusedPaneIds,
[tab.id]: paneIds[0],
},
tabHistoryStacks: {
...state.tabHistoryStacks,
[workspaceId]: newHistoryStack,
},
});

return { tabId: tab.id, paneIds };
},

removeTab: (tabId) => {
const state = get();
const tabToRemove = state.tabs.find((t) => t.id === tabId);
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/src/renderer/stores/tabs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export interface AddTabOptions {
initialCwd?: string;
}

export interface AddTabWithMultiplePanesOptions {
commands: string[];
initialCwd?: string;
}

/**
* Options for opening a file in a file-viewer pane
*/
Expand All @@ -60,6 +65,10 @@ export interface TabsStore extends TabsState {
workspaceId: string,
options?: AddTabOptions,
) => { tabId: string; paneId: string };
addTabWithMultiplePanes: (
workspaceId: string,
options: AddTabWithMultiplePanesOptions,
) => { tabId: string; paneIds: string[] };
removeTab: (tabId: string) => void;
renameTab: (tabId: string, newName: string) => void;
setTabAutoTitle: (tabId: string, title: string) => void;
Expand Down
Loading