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
483 changes: 232 additions & 251 deletions src/components/FileTreeContent/FileExplorerMenu.tsx

Large diffs are not rendered by default.

48 changes: 19 additions & 29 deletions src/components/WindowChrome/WindowsTopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import { LogicalPosition } from "@tauri-apps/api/dpi";
import {
MenuItem,
PredefinedMenuItem,
Menu as TauriMenu,
} from "@tauri-apps/api/menu";
import { open } from "@tauri-apps/plugin-shell";
import type { TFunction } from "i18next";
import { Minus, Square, X } from "lucide-react";
Expand All @@ -16,6 +11,10 @@ import {
maxWindow,
minWindow,
} from "@src/util/platform/ipcRenderer";
import {
type NativeMenuItemOptions,
popupNativeMenu,
} from "@src/util/platform/tauri/nativeMenuPopup";

import { NoDragRegion } from "./NoDragRegion";

Expand Down Expand Up @@ -252,31 +251,22 @@ async function showNativeStyleMenu(
anchor: HTMLElement,
t: TFunction
) {
const menuItems = await Promise.all(
getMenuItems(menuKey, t).map(async (item) => {
if (item.type === "separator") {
return PredefinedMenuItem.new({ item: "Separator" });
}

return MenuItem.new({
text: item.text,
enabled: item.enabled ?? true,
accelerator: item.accelerator,
action: item.action,
});
})
);

const menu = await TauriMenu.new({ items: menuItems });
const rect = anchor.getBoundingClientRect();

try {
await menu.popup(
new LogicalPosition(Math.round(rect.left), Math.round(rect.bottom))
);
} catch {
await menu.popup();
}
await popupNativeMenu({
source: `windows-top-bar:${menuKey}`,
buildItems: () =>
getMenuItems(menuKey, t).map<NativeMenuItemOptions>((item) => {
if (item.type === "separator") return { item: "Separator" };
return {
text: item.text,
enabled: item.enabled ?? true,
accelerator: item.accelerator,
action: item.action,
};
}),
at: new LogicalPosition(Math.round(rect.left), Math.round(rect.bottom)),
fallbackToCursor: true,
});
}

const WindowsTopBarComponent: React.FC = () => {
Expand Down
87 changes: 47 additions & 40 deletions src/engines/ChatPanel/ChatPanelTabContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { MenuItem, Menu as TauriMenu } from "@tauri-apps/api/menu";
import i18next from "i18next";
import { useEffect, useRef } from "react";

import { createLogger } from "@src/hooks/logger";
import type { SessionReferenceOpen } from "@src/shared/dnd/sessionTabDrag";
import {
type NativeMenuItemOptions,
popupNativeMenu,
} from "@src/util/platform/tauri/nativeMenuPopup";

const logger = createLogger("ChatPanelTabContextMenu");

Expand Down Expand Up @@ -33,47 +36,51 @@ export function ChatPanelTabContextMenu(

async function showNativeMenu(): Promise<void> {
try {
const translate = i18next.t.bind(i18next);
const [closeItem, closeOthersItem] = await Promise.all([
MenuItem.new({
text: translate("actions.close"),
action: () => {
const current = propsRef.current;
void current.onCloseTab(current.tabId);
current.onDismiss();
},
}),
MenuItem.new({
text: translate("actions.closeOthers"),
action: () => {
const current = propsRef.current;
void current.onCloseOtherTabs(current.tabId);
current.onDismiss();
},
}),
]);
const items: MenuItem[] = [];
const sessionReference = propsRef.current.sessionReference;
if (sessionReference) {
items.push(
await MenuItem.new({
text: translate("teamInbox.handoff.createFromSession", {
defaultValue: "Create team Work Item…",
}),
action: () => {
const current = propsRef.current;
if (current.sessionReference) {
current.onCreateWorkItem?.(current.sessionReference);
}
current.onDismiss();
const result = await popupNativeMenu({
source: "chat-panel-tab",
onBusy: () => propsRef.current.onDismiss(),
buildItems: () => {
const translate = i18next.t.bind(i18next);
const items: NativeMenuItemOptions[] = [];
const sessionReference = propsRef.current.sessionReference;
if (sessionReference) {
items.push({
text: translate("teamInbox.handoff.createFromSession", {
defaultValue: "Create team Work Item…",
}),
action: () => {
const current = propsRef.current;
if (current.sessionReference) {
current.onCreateWorkItem?.(current.sessionReference);
}
current.onDismiss();
},
});
}
items.push(
{
text: translate("actions.close"),
action: () => {
const current = propsRef.current;
void current.onCloseTab(current.tabId);
current.onDismiss();
},
},
})
);
{
text: translate("actions.closeOthers"),
action: () => {
const current = propsRef.current;
void current.onCloseOtherTabs(current.tabId);
current.onDismiss();
},
}
);
return items;
},
});
if (result.status !== "busy") {
setTimeout(() => propsRef.current.onDismiss(), 50);
}
items.push(closeItem, closeOthersItem);
const menu = await TauriMenu.new({ items });
await menu.popup();
setTimeout(() => propsRef.current.onDismiss(), 50);
} catch (error) {
logger.error("Failed to show native context menu:", error);
propsRef.current.onDismiss();
Expand Down
48 changes: 25 additions & 23 deletions src/features/TaskKanban/hooks/useKanbanCardContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* a Tauri menu popped at the cursor, so a right-click on a card offers the two
* open surfaces instead of the WebView's default Reload / Inspect menu.
*/
import { MenuItem, Menu as TauriMenu } from "@tauri-apps/api/menu";
import { useSetAtom } from "jotai";
import { type MouseEvent, useCallback } from "react";
import { useTranslation } from "react-i18next";
Expand All @@ -14,6 +13,10 @@ import type { KanbanTask } from "@src/features/KanbanBoard";
import { createLogger } from "@src/hooks/logger";
import { openOrFocusSessionInChatPanelTabAtom } from "@src/store/chatPanel/chatPanelTabsAtom";
import { activeStationChatVisibleAtom } from "@src/store/ui/chatPanelAtom";
import {
type NativeMenuItemOptions,
popupNativeMenu,
} from "@src/util/platform/tauri/nativeMenuPopup";

import {
KANBAN_CARD_CONTEXT_ACTION,
Expand Down Expand Up @@ -62,28 +65,27 @@ export function useKanbanCardContextMenu({
});
if (actions.length === 0) return;

const items: MenuItem[] = [];
for (const action of actions) {
if (action === KANBAN_CARD_CONTEXT_ACTION.OpenFloatingPane) {
items.push(
await MenuItem.new({
text: t("kanban.card.openAsFloatingPane"),
action: () => onOpenFloatingPane(task),
})
);
continue;
}
if (!sessionId) continue;
items.push(
await MenuItem.new({
text: tCommon("actions.openInNewTab"),
action: () => openInNewTabPane(sessionId, task.title),
})
);
}

const menu = await TauriMenu.new({ items });
await menu.popup();
await popupNativeMenu({
source: "kanban-card",
buildItems: () => {
const items: NativeMenuItemOptions[] = [];
for (const action of actions) {
if (action === KANBAN_CARD_CONTEXT_ACTION.OpenFloatingPane) {
items.push({
text: t("kanban.card.openAsFloatingPane"),
action: () => onOpenFloatingPane(task),
});
continue;
}
if (!sessionId) continue;
items.push({
text: tCommon("actions.openInNewTab"),
action: () => openInNewTabPane(sessionId, task.title),
});
}
return items;
},
});
},
[onOpenFloatingPane, openInNewTabPane, remoteSessionsByTaskId, t, tCommon]
);
Expand Down
98 changes: 46 additions & 52 deletions src/hooks/ui/useResizeContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
* - ResizableSplitPanel (Code Editor sidebar)
* - EditorBottomPanel (bottom panel height)
*/
import {
MenuItem,
PredefinedMenuItem,
Menu as TauriMenu,
} from "@tauri-apps/api/menu";
import i18next from "i18next";
import { type MouseEvent, useCallback } from "react";

import { createLogger } from "@src/hooks/logger";
import {
type NativeMenuItemOptions,
popupNativeMenu,
} from "@src/util/platform/tauri/nativeMenuPopup";

const log = createLogger("useResizeContextMenu");

Expand Down Expand Up @@ -79,62 +78,57 @@ export function useResizeContextMenu({
const interpolateMin =
dimension === "width" ? { width: minSize } : { height: minSize };

(async () => {
try {
const resizeDefaultItem = await MenuItem.new({
text: i18next.t(keys.resizeToDefault, interpolate),
enabled: !isAlreadyDefault,
action: () => {
onSizeChange(defaultSize);
void popupNativeMenu({
source: "resize-handle",
buildItems: () => {
const items: NativeMenuItemOptions[] = [
{
text: i18next.t(keys.resizeToDefault, interpolate),
enabled: !isAlreadyDefault,
action: () => {
onSizeChange(defaultSize);
},
},
});
const minimizeItem = await MenuItem.new({
text: i18next.t(keys.minimize, interpolateMin),
enabled: !isAlreadyMin,
action: () => {
onSizeChange(minSize);
{
text: i18next.t(keys.minimize, interpolateMin),
enabled: !isAlreadyMin,
action: () => {
onSizeChange(minSize);
},
},
});

const items: Array<
| Awaited<ReturnType<typeof MenuItem.new>>
| Awaited<ReturnType<typeof PredefinedMenuItem.new>>
> = [resizeDefaultItem, minimizeItem];
];

if (positionAction) {
const positionSeparator = await PredefinedMenuItem.new({
item: "Separator",
});
const positionItem = await MenuItem.new({
text: i18next.t(
positionAction.target === "left"
? "spotlightActions.moveWorkstationSidebarLeft"
: "spotlightActions.moveWorkstationSidebarRight"
),
action: positionAction.onSelect,
});
items.push(positionSeparator, positionItem);
items.push(
{ item: "Separator" },
{
text: i18next.t(
positionAction.target === "left"
? "spotlightActions.moveWorkstationSidebarLeft"
: "spotlightActions.moveWorkstationSidebarRight"
),
action: positionAction.onSelect,
}
);
}

if (onClose) {
const closeSeparator = await PredefinedMenuItem.new({
item: "Separator",
});
const closeItem = await MenuItem.new({
text: i18next.t("tooltips.closePanel"),
action: () => {
onClose();
},
});
items.push(closeSeparator, closeItem);
items.push(
{ item: "Separator" },
{
text: i18next.t("tooltips.closePanel"),
action: () => {
onClose();
},
}
);
}

const menu = await TauriMenu.new({ items });
await menu.popup();
} catch (error) {
log.error("Failed to show resize context menu:", error);
}
})();
return items;
},
}).catch((error) => {
log.error("Failed to show resize context menu:", error);
});
},
[
dimension,
Expand Down
Loading
Loading