From 988a6c92ffa272f40e8963e3fa14c3403c90db42 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 2 Jan 2023 14:52:53 -0800 Subject: [PATCH] context menu working cut/copy/paste with some smarts about where it is displayed --- src/emain.ts | 27 ++++++++++++++------------- src/main.tsx | 31 +++++++++++++++++++++++++++++-- src/model.ts | 8 ++++---- src/preload.js | 2 +- src/term.ts | 4 ---- src/types.ts | 6 +++++- 6 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/emain.ts b/src/emain.ts index 7f023097..c2c08939 100644 --- a/src/emain.ts +++ b/src/emain.ts @@ -354,17 +354,6 @@ function getContextMenu() : any { return menu; } -function getCutCopyPasteMenu() : any { - let menu = new electron.Menu(); - let menuItem = new electron.MenuItem({label: "Cut", role: "cut"}); - menu.append(menuItem); - menuItem = new electron.MenuItem({label: "Copy", role: "copy"}); - menu.append(menuItem); - menuItem = new electron.MenuItem({label: "Paste", role: "paste"}); - menu.append(menuItem); - return menu; -} - function getFetchHeaders() { return { "x-authkey": GlobalAuthKey, @@ -448,9 +437,21 @@ electron.ipcMain.on("context-screen", (event, {screenId}, {x, y}) => { menu.popup({x, y}); }); -electron.ipcMain.on("context-editmenu", (event, {x, y}) => { +electron.ipcMain.on("context-editmenu", (event, {x, y}, opts) => { + if (opts == null) { + opts = {}; + } console.log("context-editmenu"); - let menu = getCutCopyPasteMenu(); + let menu = new electron.Menu(); + let menuItem = null; + if (opts.showCut) { + menuItem = new electron.MenuItem({label: "Cut", role: "cut"}); + menu.append(menuItem); + } + menuItem = new electron.MenuItem({label: "Copy", role: "copy"}); + menu.append(menuItem); + menuItem = new electron.MenuItem({label: "Paste", role: "paste"}); + menu.append(menuItem); menu.popup({x, y}); }); diff --git a/src/main.tsx b/src/main.tsx index 19a9c491..308d9f7e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,7 +9,7 @@ import dayjs from "dayjs"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; import {TermWrap} from "./term"; -import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType} from "./types"; +import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts} from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, widthToCols, termWidthFromCols, termHeightFromRows, termRowsFromHeight} from "./model"; import {isModKeyPress} from "./util"; @@ -2672,9 +2672,36 @@ class Main extends React.Component<{}, {}> { super(props); } + @boundMethod + handleContextMenu(e : any) { + let isInNonTermInput = false; + let 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; + } + let opts : ContextMenuOpts = {}; + if (isInNonTermInput) { + opts.showCut = true; + } + let sel = window.getSelection(); + if (!isBlank(sel.toString())) { + GlobalModel.contextEditMenu(e, opts); + } + else { + if (isInNonTermInput) { + GlobalModel.contextEditMenu(e, opts); + } + } + } + render() { return ( -
+
diff --git a/src/model.ts b/src/model.ts index 327858f9..e43c1dfc 100644 --- a/src/model.ts +++ b/src/model.ts @@ -5,7 +5,7 @@ import {debounce} from "throttle-debounce"; import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeSimpleData, boundInt, isModKeyPress} from "./util"; import {TermWrap} from "./term"; import {v4 as uuidv4} from "uuid"; -import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType} from "./types"; +import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenWindowType, ScreenOptsType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts} from "./types"; import {WSControl} from "./ws"; var GlobalUser = "sawka"; @@ -107,7 +107,7 @@ type ElectronApi = { onBracketCmd : (callback : (event : any, arg : {relative : number}, mods : KeyModsType) => void) => void, onDigitCmd : (callback : (event : any, arg : {digit : number}, mods : KeyModsType) => void) => void, contextScreen : (screenOpts : {screenId : string}, position : {x : number, y : number}) => void, - contextEditMenu : (position : {x : number, y : number}) => void, + contextEditMenu : (position : {x : number, y : number}, opts : ContextMenuOpts) => void, onLocalServerStatusChange : (callback : (status : boolean, pid : number) => void) => void, }; @@ -1658,8 +1658,8 @@ class Model { getApi().contextScreen({screenId: screenId}, {x: e.x, y: e.y}); } - contextEditMenu(e : any) { - getApi().contextEditMenu({x: e.x, y: e.y}); + contextEditMenu(e : any, opts : ContextMenuOpts) { + getApi().contextEditMenu({x: e.x, y: e.y}, opts); } getUIContext() : UIContextType { diff --git a/src/preload.js b/src/preload.js index e398d4f2..75f6b8b3 100644 --- a/src/preload.js +++ b/src/preload.js @@ -18,6 +18,6 @@ contextBridge.exposeInMainWorld("api", { onBracketCmd: (callback) => ipcRenderer.on("bracket-cmd", callback), onDigitCmd: (callback) => ipcRenderer.on("digit-cmd", callback), contextScreen: (screenOpts, position) => ipcRenderer.send("context-screen", screenOpts, position), - contextEditMenu: (position) => ipcRenderer.send("context-editmenu", position), + contextEditMenu: (position, opts) => ipcRenderer.send("context-editmenu", position, opts), onLocalServerStatusChange: (callback) => ipcRenderer.on("local-server-status-change", callback), }); diff --git a/src/term.ts b/src/term.ts index e3e8af92..232fe72f 100644 --- a/src/term.ts +++ b/src/term.ts @@ -108,10 +108,6 @@ class TermWrap { this.terminal.attachCustomKeyEventHandler((e) => opts.customKeyHandler(e, this)); } this.reloadTerminal(0); - elem.addEventListener("contextmenu", (e) => { - console.log("elem-contextmenu", elem, e); - GlobalModel.contextEditMenu(e); - }); } @boundMethod diff --git a/src/types.ts b/src/types.ts index 04befdf0..e2f7905c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -327,6 +327,10 @@ type HistoryQueryOpts = { fromTs : number, }; +type ContextMenuOpts = { + showCut? : boolean, +}; + type UpdateMessage = PtyDataUpdateType | ModelUpdateType; -export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType}; +export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, WindowDataType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, ScreenWindowType, LayoutType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts};