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
38 changes: 28 additions & 10 deletions apps/desktop/src/renderer/hotkeys/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,27 +354,45 @@ export const HOTKEYS_REGISTRY = {
category: "Terminal",
description: "Focus the next tab in the active workspace",
},
// FORK NOTE: upstream renamed PREV_PANE → FOCUS_PANE_LEFT in #3403 (PR#3).
// Keep PREV_PANE until that cherry-pick is applied.
PREV_PANE: {
FOCUS_PANE_LEFT: {
key: {
mac: "meta+shift+left",
mac: "meta+alt+left",
windows: "ctrl+shift+alt+left",
linux: "ctrl+shift+alt+left",
},
label: "Previous Pane",
label: "Focus Pane Left",
category: "Terminal",
description: "Focus the previous pane in the current tab",
description: "Focus the pane to the left of the active pane",
},
NEXT_PANE: {
FOCUS_PANE_RIGHT: {
key: {
mac: "meta+shift+right",
mac: "meta+alt+right",
windows: "ctrl+shift+alt+right",
linux: "ctrl+shift+alt+right",
},
label: "Next Pane",
label: "Focus Pane Right",
category: "Terminal",
description: "Focus the next pane in the current tab",
description: "Focus the pane to the right of the active pane",
},
FOCUS_PANE_UP: {
key: {
mac: "meta+alt+up",
windows: "ctrl+shift+alt+up",
linux: "ctrl+shift+alt+up",
},
label: "Focus Pane Up",
category: "Terminal",
description: "Focus the pane above the active pane",
},
FOCUS_PANE_DOWN: {
key: {
mac: "meta+alt+down",
windows: "ctrl+shift+alt+down",
linux: "ctrl+shift+alt+down",
},
label: "Focus Pane Down",
category: "Terminal",
description: "Focus the pane below the active pane",
},
JUMP_TO_TAB_1: {
key: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ export function buildSetupPaneLayout(
const tabId = `tab-${crypto.randomUUID()}`;
return {
id: tabId,
titleOverride: t.label,
createdAt: Date.now(),
activePaneId: paneId,
layout: { type: "pane" as const, paneId },
panes: {
[paneId]: {
id: paneId,
kind: "terminal",
titleOverride: t.label,
data: { terminalId: t.id } as TerminalPaneData,
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { ContextMenuActionConfig, RendererContext } from "@superset/panes";
import {
type ContextMenuActionConfig,
type PaneRegistry,
type RendererContext,
resolveTabTitle,
} from "@superset/panes";
import { useMemo } from "react";
import {
LuColumns2,
Expand All @@ -18,7 +23,9 @@ import type {
TerminalPaneData,
} from "../../types";

export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneViewerData>[] {
export function useDefaultContextMenuActions(
paneRegistry: PaneRegistry<PaneViewerData>,
): ContextMenuActionConfig<PaneViewerData>[] {
const splitDownShortcut = useHotkeyDisplay("SPLIT_DOWN").text;
const splitRightShortcut = useHotkeyDisplay("SPLIT_RIGHT").text;
const splitWithChatShortcut = useHotkeyDisplay("SPLIT_WITH_CHAT").text;
Expand Down Expand Up @@ -115,7 +122,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneView
const items: ContextMenuActionConfig<PaneViewerData>[] =
otherTabs.map((tab) => ({
key: `move-to-${tab.id}`,
label: tab.titleOverride ?? tab.id,
label: resolveTabTitle(tab, tabs, paneRegistry),
onSelect: () => {
ctx.store
.getState()
Expand Down Expand Up @@ -154,6 +161,7 @@ export function useDefaultContextMenuActions(): ContextMenuActionConfig<PaneView
splitWithBrowserShortcut,
equalizePaneSplitsShortcut,
closePaneShortcut,
paneRegistry,
],
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@ function getSingleBrowserPane(
return { id: pane.id, data: pane.data as BrowserPaneData };
}

export function getBrowserTabTitle(
tab: Tab<PaneViewerData>,
): string | undefined {
const browser = getSingleBrowserPane(tab);
if (!browser) return undefined;
if (browser.data.pageTitle) return browser.data.pageTitle;
if (browser.data.url && browser.data.url !== "about:blank") {
try {
return new URL(browser.data.url).hostname;
} catch {}
}
return undefined;
}

export function renderBrowserTabIcon(tab: Tab<PaneViewerData>) {
const browser = getSingleBrowserPane(tab);
if (!browser?.data.faviconUrl) return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export {
BrowserPane,
BrowserPaneToolbar,
getBrowserTabTitle,
renderBrowserTabIcon,
} from "./BrowserPane";
export { browserRuntimeRegistry } from "./browserRuntimeRegistry";
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { FilePane } from "./components/FilePane";
import { TerminalPane } from "./components/TerminalPane";

function getFileName(filePath: string): string {
return filePath.split("/").pop() ?? filePath;
return filePath.split(/[/\\]/).pop() || filePath;
}
Comment on lines 47 to 49
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

basename の解決をこの helper に寄せ切れていません

この helper 自体は直っていますが、同じファイルの onBeforeClose はまだ split("/") のままです。Windows では dirty-file の確認ダイアログだけフルパス表示が残るので、basename 解決は getFileName() に一本化した方が安全です。

💡 併せて置き換えておくと安全です
-					const name = data.filePath.split("/").pop();
+					const name = getFileName(data.filePath);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
around lines 47 - 49, The onBeforeClose handler still uses a hardcoded
split("/") to derive basenames while getFileName(filePath: string) already
handles both "/" and "\\"; update onBeforeClose to call getFileName(...)
wherever it currently does filePath.split("/") (or similar) so the dirty-file
confirmation dialog shows the basename consistently on Windows and POSIX
systems, and scan the same file for any other remaining split("/") usages and
replace them with getFileName.


const MOD_KEY = navigator.platform.toLowerCase().includes("mac")
Expand Down Expand Up @@ -120,7 +120,8 @@ export function usePaneRegistry(
const name = getFileName(data.filePath);
return <FileIcon fileName={name} className="size-4" />;
},
getTitle: (ctx: RendererContext<PaneViewerData>) => {
getTitle: (pane) => getFileName((pane.data as FilePaneData).filePath),
Comment thread
MocA-Love marked this conversation as resolved.
renderTitle: (ctx: RendererContext<PaneViewerData>) => {
const data = ctx.pane.data as FilePaneData;
const name = data.displayName ?? getFileName(data.filePath);
return (
Expand Down Expand Up @@ -256,9 +257,15 @@ export function usePaneRegistry(
},
browser: {
getIcon: () => <Globe className="size-4" />,
getTitle: (ctx: RendererContext<PaneViewerData>) => {
const data = ctx.pane.data as BrowserPaneData;
return data.pageTitle || data.url;
getTitle: (pane) => {
const data = pane.data as BrowserPaneData;
if (data.pageTitle) return data.pageTitle;
if (data.url && data.url !== "about:blank") {
try {
return new URL(data.url).host;
} catch {}
}
return "Browser";
},
renderPane: (ctx: RendererContext<PaneViewerData>) => (
<BrowserPane ctx={ctx} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import { filterMatchingPresetsForProject } from "shared/preset-project-targeting
import type { StoreApi } from "zustand/vanilla";
import type { PaneViewerData, TerminalPaneData } from "../../types";

function makeTerminalPane(terminalId: string): CreatePaneInput<PaneViewerData> {
function makeTerminalPane(
terminalId: string,
titleOverride?: string,
): CreatePaneInput<PaneViewerData> {
return {
kind: "terminal",
titleOverride,
data: { terminalId } as TerminalPaneData,
};
}
Expand Down Expand Up @@ -89,8 +93,7 @@ export function useV2PresetExecution({
preset.commands[0] as string,
);
state.addTab({
titleOverride: preset.name || "Terminal",
panes: [makeTerminalPane(id)],
panes: [makeTerminalPane(id, preset.name || undefined)],
});
break;
}
Expand All @@ -99,16 +102,22 @@ export function useV2PresetExecution({
const ids = await Promise.all(
preset.commands.map((cmd) => createSessionWithCommand(cmd)),
);
const panes = ids.map((id) => makeTerminalPane(id));
const panes = ids.map((id) =>
makeTerminalPane(id, preset.name || undefined),
);
state.addTab({
titleOverride: preset.name || "Terminal",
panes:
panes.length > 0
? (panes as [
CreatePaneInput<PaneViewerData>,
...CreatePaneInput<PaneViewerData>[],
])
: [makeTerminalPane(crypto.randomUUID())],
: [
makeTerminalPane(
crypto.randomUUID(),
preset.name || undefined,
),
],
});
break;
}
Expand All @@ -119,8 +128,9 @@ export function useV2PresetExecution({
);
for (let i = 0; i < ids.length; i++) {
state.addTab({
titleOverride: preset.name || "Terminal",
panes: [makeTerminalPane(ids[i] as string)],
panes: [
makeTerminalPane(ids[i] as string, preset.name || undefined),
],
});
}
break;
Expand All @@ -132,14 +142,13 @@ export function useV2PresetExecution({
);
if (!activeTabId) {
state.addTab({
titleOverride: preset.name || "Terminal",
panes: [makeTerminalPane(id)],
panes: [makeTerminalPane(id, preset.name || undefined)],
});
break;
}
state.addPane({
tabId: activeTabId,
pane: makeTerminalPane(id),
pane: makeTerminalPane(id, preset.name || undefined),
});
break;
}
Expand All @@ -149,23 +158,29 @@ export function useV2PresetExecution({
preset.commands.map((cmd) => createSessionWithCommand(cmd)),
);
if (!activeTabId) {
const panes = ids.map((id) => makeTerminalPane(id));
const panes = ids.map((id) =>
makeTerminalPane(id, preset.name || undefined),
);
state.addTab({
titleOverride: preset.name || "Terminal",
panes:
panes.length > 0
? (panes as [
CreatePaneInput<PaneViewerData>,
...CreatePaneInput<PaneViewerData>[],
])
: [makeTerminalPane(crypto.randomUUID())],
: [
makeTerminalPane(
crypto.randomUUID(),
preset.name || undefined,
),
],
});
break;
}
for (const id of ids) {
state.addPane({
tabId: activeTabId,
pane: makeTerminalPane(id),
pane: makeTerminalPane(id, preset.name || undefined),
});
}
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { WorkspaceStore } from "@superset/panes";
import {
type FocusDirection,
getSpatialNeighborPaneId,
type WorkspaceStore,
} from "@superset/panes";
import { useCallback } from "react";
import { useHotkey } from "renderer/hotkeys";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
Expand Down Expand Up @@ -35,7 +39,6 @@ export function useWorkspaceHotkeys({

useHotkey("NEW_GROUP", () => {
store.getState().addTab({
titleOverride: "Terminal",
panes: [
{
kind: "terminal",
Expand All @@ -47,14 +50,12 @@ export function useWorkspaceHotkeys({

useHotkey("NEW_CHAT", () => {
store.getState().addTab({
titleOverride: "Chat",
panes: [{ kind: "chat", data: { sessionId: null } as ChatPaneData }],
});
});

useHotkey("NEW_BROWSER", () => {
store.getState().addTab({
titleOverride: "Browser",
panes: [
{
kind: "browser",
Expand Down Expand Up @@ -138,25 +139,25 @@ export function useWorkspaceHotkeys({

// --- Pane management ---

useHotkey("PREV_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const prevIndex = index <= 0 ? paneIds.length - 1 : index - 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[prevIndex] });
});
const moveFocusDirectional = useCallback(
(dir: FocusDirection) => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const neighbor = getSpatialNeighborPaneId(
tab.layout,
tab.activePaneId,
dir,
);
if (neighbor) state.setActivePane({ tabId: tab.id, paneId: neighbor });
},
[store],
);

useHotkey("NEXT_PANE", () => {
const state = store.getState();
const tab = state.getActiveTab();
if (!tab || !tab.activePaneId) return;
const paneIds = Object.keys(tab.panes);
const index = paneIds.indexOf(tab.activePaneId);
const nextIndex = index >= paneIds.length - 1 ? 0 : index + 1;
state.setActivePane({ tabId: tab.id, paneId: paneIds[nextIndex] });
});
useHotkey("FOCUS_PANE_LEFT", () => moveFocusDirectional("left"));
useHotkey("FOCUS_PANE_RIGHT", () => moveFocusDirectional("right"));
useHotkey("FOCUS_PANE_UP", () => moveFocusDirectional("up"));
useHotkey("FOCUS_PANE_DOWN", () => moveFocusDirectional("down"));

useHotkey("SPLIT_AUTO", () => {
const state = store.getState();
Expand Down
Loading
Loading