mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
terminal context menu
This commit is contained in:
parent
58684744b0
commit
5c6cfbc112
@ -21,6 +21,8 @@ const AuthKeyFile = "waveterm.authkey";
|
||||
const DevServerEndpoint = "http://127.0.0.1:8190";
|
||||
const ProdServerEndpoint = "http://127.0.0.1:1719";
|
||||
|
||||
type WaveBrowserWindow = Electron.BrowserWindow & { waveWindowId: string };
|
||||
|
||||
let waveSrvReadyResolve = (value: boolean) => {};
|
||||
let waveSrvReady: Promise<boolean> = new Promise((resolve, _) => {
|
||||
waveSrvReadyResolve = resolve;
|
||||
@ -130,7 +132,7 @@ function runWaveSrv(): Promise<boolean> {
|
||||
return rtnPromise;
|
||||
}
|
||||
|
||||
async function mainResizeHandler(_: any, windowId: string, win: Electron.BrowserWindow) {
|
||||
async function mainResizeHandler(_: any, windowId: string, win: WaveBrowserWindow) {
|
||||
if (win == null || win.isDestroyed() || win.fullScreen) {
|
||||
return;
|
||||
}
|
||||
@ -187,7 +189,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
console.log("frame navigation canceled");
|
||||
}
|
||||
|
||||
function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserWindow {
|
||||
function createWindow(client: Client, waveWindow: WaveWindow): WaveBrowserWindow {
|
||||
const primaryDisplay = electron.screen.getPrimaryDisplay();
|
||||
let winHeight = waveWindow.winsize.height;
|
||||
let winWidth = waveWindow.winsize.width;
|
||||
@ -205,7 +207,7 @@ function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserW
|
||||
if (winY + winHeight > primaryDisplay.workAreaSize.height) {
|
||||
winY = Math.floor((primaryDisplay.workAreaSize.height - winHeight) / 2);
|
||||
}
|
||||
const win = new electron.BrowserWindow({
|
||||
const bwin = new electron.BrowserWindow({
|
||||
x: winX,
|
||||
y: winY,
|
||||
titleBarStyle: "hiddenInset",
|
||||
@ -224,6 +226,8 @@ function createWindow(client: Client, waveWindow: WaveWindow): Electron.BrowserW
|
||||
autoHideMenuBar: true,
|
||||
backgroundColor: "#000000",
|
||||
});
|
||||
(bwin as any).waveWindowId = waveWindow.oid;
|
||||
const win: WaveBrowserWindow = bwin as WaveBrowserWindow;
|
||||
win.once("ready-to-show", () => {
|
||||
win.show();
|
||||
});
|
||||
@ -283,6 +287,56 @@ electron.ipcMain.on("getCursorPoint", (event) => {
|
||||
event.returnValue = retVal;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("openNewWindow", (event) => {});
|
||||
|
||||
electron.ipcMain.on("context-editmenu", (_, { x, y }, opts) => {
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
console.log("context-editmenu");
|
||||
const menu = new electron.Menu();
|
||||
if (!opts.onlyPaste) {
|
||||
if (opts.showCut) {
|
||||
const menuItem = new electron.MenuItem({ label: "Cut", role: "cut" });
|
||||
menu.append(menuItem);
|
||||
}
|
||||
const menuItem = new electron.MenuItem({ label: "Copy", role: "copy" });
|
||||
menu.append(menuItem);
|
||||
}
|
||||
const menuItem = new electron.MenuItem({ label: "Paste", role: "paste" });
|
||||
menu.append(menuItem);
|
||||
menu.popup({ x, y });
|
||||
});
|
||||
|
||||
electron.ipcMain.on("contextmenu-show", (event, menuDefArr: ElectronContextMenuItem[], { x, y }) => {
|
||||
if (menuDefArr == null || menuDefArr.length == 0) {
|
||||
return;
|
||||
}
|
||||
const menu = convertMenuDefArrToMenu(menuDefArr);
|
||||
menu.popup({ x, y });
|
||||
event.returnValue = true;
|
||||
});
|
||||
|
||||
function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electron.Menu {
|
||||
const menuItems: electron.MenuItem[] = [];
|
||||
for (const menuDef of menuDefArr) {
|
||||
const menuItemTemplate: electron.MenuItemConstructorOptions = {
|
||||
role: menuDef.role as any,
|
||||
label: menuDef.label,
|
||||
type: menuDef.type,
|
||||
click: (_, window) => {
|
||||
window?.webContents.send("contextmenu-click", menuDef.id);
|
||||
},
|
||||
};
|
||||
if (menuDef.submenu != null) {
|
||||
menuItemTemplate.submenu = convertMenuDefArrToMenu(menuDef.submenu);
|
||||
}
|
||||
const menuItem = new electron.MenuItem(menuItemTemplate);
|
||||
menuItems.push(menuItem);
|
||||
}
|
||||
return electron.Menu.buildFromTemplate(menuItems);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const startTs = Date.now();
|
||||
const instanceLock = electronApp.requestSingleInstanceLock();
|
||||
|
@ -7,4 +7,8 @@ contextBridge.exposeInMainWorld("api", {
|
||||
isDev: () => ipcRenderer.sendSync("isDev"),
|
||||
isDevServer: () => ipcRenderer.sendSync("isDevServer"),
|
||||
getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"),
|
||||
openNewWindow: () => ipcRenderer.send("openNewWindow"),
|
||||
contextEditMenu: (position, opts) => ipcRenderer.send("context-editmenu", position, opts),
|
||||
showContextMenu: (menu, position) => ipcRenderer.send("contextmenu-show", menu, position),
|
||||
onContextMenuClick: (callback) => ipcRenderer.on("contextmenu-click", callback),
|
||||
});
|
||||
|
@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Workspace } from "@/app/workspace/workspace";
|
||||
import { atoms, globalStore } from "@/store/global";
|
||||
import { atoms, getApi, globalStore } from "@/store/global";
|
||||
import * as util from "@/util/util";
|
||||
import * as jotai from "jotai";
|
||||
import { Provider } from "jotai";
|
||||
|
||||
@ -19,6 +20,29 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function handleContextMenu(e: React.MouseEvent<HTMLDivElement>) {
|
||||
let isInNonTermInput = false;
|
||||
const activeElem = document.activeElement;
|
||||
if (activeElem != null && activeElem.nodeName == "TEXTAREA") {
|
||||
if (!activeElem.classList.contains("xterm-helper-textarea")) {
|
||||
isInNonTermInput = true;
|
||||
}
|
||||
}
|
||||
if (activeElem != null && activeElem.nodeName == "INPUT" && activeElem.getAttribute("type") == "text") {
|
||||
isInNonTermInput = true;
|
||||
}
|
||||
const opts: ContextMenuOpts = {};
|
||||
if (isInNonTermInput) {
|
||||
opts.showCut = true;
|
||||
}
|
||||
const sel = window.getSelection();
|
||||
if (!util.isBlank(sel?.toString()) || isInNonTermInput) {
|
||||
getApi().contextEditMenu({ x: e.clientX, y: e.clientY }, opts);
|
||||
} else {
|
||||
getApi().contextEditMenu({ x: e.clientX, y: e.clientY }, { onlyPaste: true });
|
||||
}
|
||||
}
|
||||
|
||||
const AppInner = () => {
|
||||
const client = jotai.useAtomValue(atoms.client);
|
||||
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
||||
@ -31,7 +55,7 @@ const AppInner = () => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mainapp">
|
||||
<div className="mainapp" onContextMenu={handleContextMenu}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="titlebar"></div>
|
||||
<Workspace />
|
||||
|
25
frontend/types/custom.d.ts
vendored
25
frontend/types/custom.d.ts
vendored
@ -6,6 +6,11 @@ declare global {
|
||||
blockId: string;
|
||||
};
|
||||
|
||||
type ContextMenuOpts = {
|
||||
showCut?: boolean;
|
||||
onlyPaste?: boolean;
|
||||
};
|
||||
|
||||
type ElectronApi = {
|
||||
/**
|
||||
* Determines whether the current app instance is a development build.
|
||||
@ -22,6 +27,26 @@ declare global {
|
||||
* @returns A point value.
|
||||
*/
|
||||
getCursorPoint: () => Electron.Point;
|
||||
|
||||
contextEditMenu: (position: { x: number; y: number }, opts: ContextMenuOpts) => void;
|
||||
showContextMenu: (menu: ElectronContextMenuItem[], position: { x: number; y: number }) => void;
|
||||
onContextMenuClick: (callback: (id: string) => void) => void;
|
||||
};
|
||||
|
||||
type ElectronContextMenuItem = {
|
||||
id: string; // unique id, used for communication
|
||||
label: string;
|
||||
role?: string; // electron role (optional)
|
||||
type?: "separator" | "normal" | "submenu";
|
||||
submenu?: ElectronContextMenuItem[];
|
||||
};
|
||||
|
||||
type ContextMenuItem = {
|
||||
label?: string;
|
||||
type?: "separator" | "normal" | "submenu";
|
||||
role?: string; // electron role (optional)
|
||||
click?: () => void; // not required if role is set
|
||||
submenu?: ContextMenuItem[];
|
||||
};
|
||||
|
||||
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
||||
|
Loading…
Reference in New Issue
Block a user