mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] shortcuts: Cmd+T new tab or browser, Cmd+W closes the focused card undoably, routed through main like Cmd+R
This commit is contained in:
@@ -2234,9 +2234,26 @@ function swallowCloseWindowShortcut(event, input) {
|
||||
(input.key || '').toLowerCase() === 'w'
|
||||
) {
|
||||
event.preventDefault();
|
||||
// Arc semantics: the swallowed close becomes "close the focused card" in the renderer (undoable via Cmd+Z).
|
||||
if (!input.shift) {
|
||||
try {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('openswarm:close-shortcut');
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cmd/Ctrl+T: new tab in the last-interacted browser, or a new browser card (Arc muscle memory).
|
||||
function routeNewTabShortcut(event, input) {
|
||||
if (input.type !== 'keyDown') return;
|
||||
if (!(input.meta || input.control) || input.shift || input.alt) return;
|
||||
if ((input.key || '').toLowerCase() !== 't') return;
|
||||
event.preventDefault();
|
||||
try {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('openswarm:newtab-shortcut');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Cmd/Ctrl+R: the default menu's Reload accelerator reloads the WHOLE app even when a browser webview is focused (the "Ctrl+R reloads OpenSwarm, not the browser" complaint). preventDefault kills that accelerator (same electron#19279 path as Cmd+W, dispatched against whichever webContents is focused, hence both main window AND guests); the renderer then reloads the last-interacted browser, or the app if none. Shift+R (force reload) is left alone.
|
||||
function routeReloadShortcut(event, input) {
|
||||
if (input.type !== 'keyDown') return;
|
||||
@@ -2398,6 +2415,7 @@ app.on('web-contents-created', (_event, contents) => {
|
||||
if (isCreatingMainWindow || contents.getType() === 'webview') {
|
||||
contents.on('before-input-event', swallowCloseWindowShortcut);
|
||||
contents.on('before-input-event', routeReloadShortcut);
|
||||
contents.on('before-input-event', routeNewTabShortcut);
|
||||
}
|
||||
// The main app window (created while this flag is set) gets a text-focused native menu; OAuth
|
||||
// popups are 'window' contents created with the flag OFF, so they keep the OS default.
|
||||
|
||||
@@ -188,6 +188,20 @@ contextBridge.exposeInMainWorld('openswarm', {
|
||||
return () => ipcRenderer.removeListener('openswarm:reload-shortcut', listener);
|
||||
},
|
||||
|
||||
// Cmd/Ctrl+W with the window-close swallowed in main: the renderer closes the focused card instead.
|
||||
onCloseShortcut: (cb) => {
|
||||
const listener = () => cb();
|
||||
ipcRenderer.on('openswarm:close-shortcut', listener);
|
||||
return () => ipcRenderer.removeListener('openswarm:close-shortcut', listener);
|
||||
},
|
||||
|
||||
// Cmd/Ctrl+T: new tab in the last-interacted browser, else a new browser card.
|
||||
onNewTabShortcut: (cb) => {
|
||||
const listener = () => cb();
|
||||
ipcRenderer.on('openswarm:newtab-shortcut', listener);
|
||||
return () => ipcRenderer.removeListener('openswarm:newtab-shortcut', listener);
|
||||
},
|
||||
|
||||
// In-page browser shortcuts (zoom/find/tab-cycle) from a focused guest webview, carrying the guest's webContents id so the renderer targets that exact browser.
|
||||
onBrowserShortcut: (cb) => {
|
||||
const listener = (_event, payload) => cb(payload);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, type RefObject } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
|
||||
import { addViewCard, clearTiledCard, toggleMinimizeCard, selectFullscreenCardId } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { addViewCard, addBrowserTab, clearTiledCard, toggleMinimizeCard, selectFullscreenCardId } from '@/shared/state/dashboardLayoutSlice';
|
||||
import DashboardHeader from './DashboardHeader';
|
||||
import TetherLayerHost from './TetherLayerHost';
|
||||
import { useLiveMultiDrag } from '../hooks/interaction/useLiveMultiDrag';
|
||||
@@ -33,6 +33,7 @@ import type { CardType, useDashboardSelection } from '../hooks/state/useDashboar
|
||||
import type { useCanvasControls } from '../hooks/interaction/useCanvasControls';
|
||||
import { useWebviewSuspend } from '../hooks/interaction/useWebviewSuspend';
|
||||
import { deleteSelectedCards } from '../hooks/interaction/deleteSelectedCards';
|
||||
import { getLastInteractedBrowser } from '@/shared/browserFocus';
|
||||
import type { TetherInputs } from '../geometry/dashboardTethers';
|
||||
|
||||
type Selection = ReturnType<typeof useDashboardSelection>;
|
||||
@@ -291,6 +292,23 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
if (newestDeletable) deleteSelectedCards(new Map([[newestDeletable.id, newestDeletable.type]]), dispatch);
|
||||
}, [selection, dispatch, newestDeletable]);
|
||||
|
||||
// Cmd/Ctrl+W and Cmd/Ctrl+T arrive as IPC echoes: main preventDefaults both before any DOM keydown
|
||||
// (including from focused guests), so these bridges are the only firing path, no double-handling.
|
||||
const browserHomepage = useAppSelector((st) => st.settings.data.browser_homepage ?? 'https://www.google.com');
|
||||
React.useEffect(() => {
|
||||
const w = window as unknown as { openswarm?: { onCloseShortcut?: (cb: () => void) => () => void; onNewTabShortcut?: (cb: () => void) => () => void } };
|
||||
const offs: Array<() => void> = [];
|
||||
if (w.openswarm?.onCloseShortcut) offs.push(w.openswarm.onCloseShortcut(() => handleDeleteSelected()));
|
||||
if (w.openswarm?.onNewTabShortcut) {
|
||||
offs.push(w.openswarm.onNewTabShortcut(() => {
|
||||
const browserId = getLastInteractedBrowser();
|
||||
if (browserId && browserCards[browserId]) dispatch(addBrowserTab({ browserId, url: browserHomepage, makeActive: true }));
|
||||
else onAddBrowser();
|
||||
}));
|
||||
}
|
||||
return () => { offs.forEach((off) => off()); };
|
||||
}, [handleDeleteSelected, browserCards, browserHomepage, dispatch, onAddBrowser]);
|
||||
|
||||
// Gestures write the transform imperatively (no React commit per frame), so a foreign render mid-gesture would paint the stale committed transform for a frame. Re-applying live after EVERY render seals that; do not remove.
|
||||
React.useLayoutEffect(() => {
|
||||
canvas.actions.syncTransform();
|
||||
|
||||
Vendored
+2
@@ -76,6 +76,8 @@ declare global {
|
||||
onUpdateError: (cb: (message: string) => void) => () => void;
|
||||
onWebviewNewWindow: (cb: (url: string, webContentsId: number, disposition?: string) => void) => () => void;
|
||||
onReloadShortcut?: (cb: () => void) => () => void;
|
||||
onCloseShortcut?: (cb: () => void) => () => void;
|
||||
onNewTabShortcut?: (cb: () => void) => () => void;
|
||||
onBrowserShortcut?: (cb: (payload: { action: string; webContentsId: number }) => void) => () => void;
|
||||
openExternal: (url: string) => Promise<void>;
|
||||
harvestUsage?: (provider: string) => Promise<{ ok: boolean; total: number; titles: string[]; memories: string[] } | null>;
|
||||
|
||||
Reference in New Issue
Block a user