diff --git a/emain/emain.ts b/emain/emain.ts index a43c55f1..be4fc697 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -1,6 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import * as keyutil from "@/util/keyutil"; import * as electron from "electron"; import fs from "fs"; import * as child_process from "node:child_process"; @@ -37,6 +38,7 @@ let unameArch: string = process.arch; if (unameArch == "x64") { unameArch = "amd64"; } +keyutil.setKeyUtilPlatform(unamePlatform); function getBaseHostPort(): string { if (isDev) { @@ -386,6 +388,10 @@ electron.ipcMain.on("isDevServer", (event) => { event.returnValue = isDevServer; }); +electron.ipcMain.on("getPlatform", (event) => { + event.returnValue = unamePlatform; +}); + electron.ipcMain.on("getCursorPoint", (event) => { const window = electron.BrowserWindow.fromWebContents(event.sender); const screenPoint = electron.screen.getCursorScreenPoint(); diff --git a/emain/preload.ts b/emain/preload.ts index 4814b571..5e1bf2b3 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -6,6 +6,7 @@ let { contextBridge, ipcRenderer } = require("electron"); contextBridge.exposeInMainWorld("api", { isDev: () => ipcRenderer.sendSync("isDev"), isDevServer: () => ipcRenderer.sendSync("isDevServer"), + getPlatform: () => ipcRenderer.sendSync("getPlatform"), getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"), openNewWindow: () => ipcRenderer.send("openNewWindow"), contextEditMenu: (position, opts) => ipcRenderer.send("context-editmenu", position, opts), diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 396cd586..38ec170c 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -2,11 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import { Workspace } from "@/app/workspace/workspace"; -import { atoms, getApi, globalStore } from "@/store/global"; +import { getLayoutStateAtomForTab, globalLayoutTransformsMap } from "@/faraday/lib/layoutAtom"; +import type { LayoutTreeState } from "@/faraday/lib/model"; +import { WOS, atoms, getApi, globalStore, setBlockFocus } from "@/store/global"; +import * as services from "@/store/services"; +import * as keyutil from "@/util/keyutil"; import * as util from "@/util/util"; import * as jotai from "jotai"; -import { Provider } from "jotai"; - +import * as React from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import { CenteredDiv } from "./element/quickelems"; @@ -15,6 +18,7 @@ import "overlayscrollbars/overlayscrollbars.css"; import "./app.less"; const App = () => { + let Provider = jotai.Provider; return ( @@ -45,9 +49,145 @@ function handleContextMenu(e: React.MouseEvent) { } } +function switchTab(offset: number) { + console.log("switch tab!", offset); + const ws = globalStore.get(atoms.workspace); + const activeTabId = globalStore.get(atoms.tabAtom).oid; + let tabIdx = -1; + for (let i = 0; i < ws.tabids.length; i++) { + if (ws.tabids[i] == activeTabId) { + tabIdx = i; + break; + } + } + if (tabIdx == -1) { + return; + } + tabIdx = (tabIdx + offset) % ws.tabids.length; + const newActiveTabId = ws.tabids[tabIdx]; + services.ObjectService.SetActiveTab(newActiveTabId); +} + +function findLeafIdFromBlockId(layoutTree: LayoutTreeState, blockId: string): string { + if (layoutTree?.leafs == null) { + return null; + } + for (let leaf of layoutTree.leafs) { + if (leaf.data.blockId == blockId) { + return leaf.id; + } + } + return null; +} + +var transformRegexp = /translate\(\s*([0-9.]+)px\s*,\s*([0-9.]+)px\)/; + +function parseFloatFromCSS(s: string | number): number { + if (typeof s == "number") { + return s; + } + return parseFloat(s); +} + +function readBoundsFromTransform(fullTransform: React.CSSProperties): Bounds { + const transformProp = fullTransform.transform; + if (transformProp == null || fullTransform.width == null || fullTransform.height == null) { + return null; + } + const m = transformRegexp.exec(transformProp); + if (m == null) { + return null; + } + return { + x: parseFloat(m[1]), + y: parseFloat(m[2]), + width: parseFloatFromCSS(fullTransform.width), + height: parseFloatFromCSS(fullTransform.height), + }; +} + +function boundsMapMaxX(m: Map): number { + let max = 0; + for (let p of m.values()) { + if (p.x + p.width > max) { + max = p.x + p.width; + } + } + return max; +} + +function boundsMapMaxY(m: Map): number { + let max = 0; + for (let p of m.values()) { + if (p.y + p.height > max) { + max = p.y + p.height; + } + } + return max; +} + +function findBlockAtPoint(m: Map, p: Point): string { + for (let [blockId, bounds] of m.entries()) { + if (p.x >= bounds.x && p.x <= bounds.x + bounds.width && p.y >= bounds.y && p.y <= bounds.y + bounds.height) { + return blockId; + } + } + return null; +} + +function switchBlock(tabId: string, offsetX: number, offsetY: number) { + console.log("switch block", offsetX, offsetY); + if (offsetY == 0 && offsetX == 0) { + return; + } + const tabAtom = WOS.getWaveObjectAtom(WOS.makeORef("tab", tabId)); + const transforms = globalLayoutTransformsMap.get(tabId); + if (transforms == null) { + return; + } + const layoutTreeState = globalStore.get(getLayoutStateAtomForTab(tabId, tabAtom)); + const curBlockId = globalStore.get(atoms.waveWindow).activeblockid; + const curBlockLeafId = findLeafIdFromBlockId(layoutTreeState, curBlockId); + if (curBlockLeafId == null) { + return; + } + const blockPos = readBoundsFromTransform(transforms[curBlockLeafId]); + if (blockPos == null) { + return; + } + var blockPositions: Map = new Map(); + for (let leaf of layoutTreeState.leafs) { + if (leaf.id == curBlockLeafId) { + continue; + } + const pos = readBoundsFromTransform(transforms[leaf.id]); + if (pos != null) { + blockPositions.set(leaf.data.blockId, pos); + } + } + const maxX = boundsMapMaxX(blockPositions); + const maxY = boundsMapMaxY(blockPositions); + const moveAmount = 10; + let curX = blockPos.x + 1; + let curY = blockPos.y + 1; + while (true) { + curX += offsetX * moveAmount; + curY += offsetY * moveAmount; + if (curX < 0 || curX > maxX || curY < 0 || curY > maxY) { + return; + } + const blockId = findBlockAtPoint(blockPositions, { x: curX, y: curY }); + if (blockId != null) { + setBlockFocus(blockId); + return; + } + } +} + const AppInner = () => { const client = jotai.useAtomValue(atoms.client); const windowData = jotai.useAtomValue(atoms.waveWindow); + const tabId = jotai.useAtomValue(atoms.activeTabId); if (client == null || windowData == null) { return (
@@ -56,6 +196,49 @@ const AppInner = () => {
); } + function handleKeyDown(ev: KeyboardEvent) { + let waveEvent = keyutil.adaptFromReactOrNativeKeyEvent(ev); + const rtn = handleKeyDownInternal(waveEvent); + if (rtn) { + ev.preventDefault(); + ev.stopPropagation(); + } + } + function handleKeyDownInternal(waveEvent: WaveKeyboardEvent): boolean { + // global key handler for now (refactor later) + if (keyutil.checkKeyPressed(waveEvent, "Cmd:]")) { + switchTab(1); + return true; + } + if (keyutil.checkKeyPressed(waveEvent, "Cmd:[")) { + switchTab(-1); + return true; + } + if (keyutil.checkKeyPressed(waveEvent, "Cmd:ArrowUp")) { + switchBlock(tabId, 0, -1); + return true; + } + if (keyutil.checkKeyPressed(waveEvent, "Cmd:ArrowDown")) { + switchBlock(tabId, 0, 1); + return true; + } + if (keyutil.checkKeyPressed(waveEvent, "Cmd:ArrowLeft")) { + switchBlock(tabId, -1, 0); + return true; + } + if (keyutil.checkKeyPressed(waveEvent, "Cmd:ArrowRight")) { + switchBlock(tabId, 1, 0); + return true; + } + return false; + } + React.useEffect(() => { + const staticKeyDownHandler = handleKeyDown; + document.addEventListener("keydown", staticKeyDownHandler); + return () => { + document.removeEventListener("keydown", staticKeyDownHandler); + }; + }, []); return (
diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index e7583f4f..b3200f99 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -8,10 +8,9 @@ import { TerminalView } from "@/app/view/term/term"; import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; import { ContextMenuModel } from "@/store/contextmenu"; -import { atoms, globalStore, useBlockAtom } from "@/store/global"; +import { atoms, setBlockFocus, useBlockAtom } from "@/store/global"; import * as WOS from "@/store/wos"; import clsx from "clsx"; -import { produce } from "immer"; import * as jotai from "jotai"; import * as React from "react"; @@ -208,17 +207,6 @@ const BlockFrame = (props: BlockFrameProps) => { return ; }; -function setBlockFocus(blockId: string) { - let winData = globalStore.get(atoms.waveWindow); - if (winData.activeblockid === blockId) { - return; - } - winData = produce(winData, (draft) => { - draft.activeblockid = blockId; - }); - WOS.setObjectValue(winData, globalStore.set, true); -} - const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { let blockElem: JSX.Element = null; const focusElemRef = React.useRef(null); diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 3b6a9583..5a34b718 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -5,6 +5,7 @@ import { LayoutTreeActionType, LayoutTreeInsertNodeAction, newLayoutNode } from import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom"; import { layoutTreeStateReducer } from "@/faraday/lib/layoutState"; +import { produce } from "immer"; import * as jotai from "jotai"; import * as rxjs from "rxjs"; import * as services from "./services"; @@ -64,6 +65,13 @@ const tabAtom: jotai.Atom = jotai.atom((get) => { } return WOS.getObjectValue(WOS.makeORef("tab", windowData.activetabid), get); }); +const activeTabIdAtom: jotai.Atom = jotai.atom((get) => { + const windowData = get(windowDataAtom); + if (windowData == null) { + return null; + } + return windowData.activetabid; +}); const atoms = { // initialized in wave.ts (will not be null inside of application) @@ -75,6 +83,7 @@ const atoms = { workspace: workspaceAtom, settingsConfigAtom: settingsConfigAtom, tabAtom: tabAtom, + activeTabId: activeTabIdAtom, }; // key is "eventType" or "eventType|oref" @@ -285,6 +294,17 @@ async function fetchWaveFile( return { data: new Uint8Array(data), fileInfo }; } +function setBlockFocus(blockId: string) { + let winData = globalStore.get(atoms.waveWindow); + if (winData.activeblockid === blockId) { + return; + } + winData = produce(winData, (draft) => { + draft.activeblockid = blockId; + }); + WOS.setObjectValue(winData, globalStore.set, true); +} + export { WOS, atoms, @@ -299,6 +319,7 @@ export { globalWS, initWS, sendWSCommand, + setBlockFocus, useBlockAtom, useBlockCache, }; diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index 0db58348..dd7db302 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -1,4 +1,5 @@ import { Button } from "@/element/button"; +import { ContextMenuModel } from "@/store/contextmenu"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; import { clsx } from "clsx"; @@ -12,7 +13,7 @@ interface TabProps { isBeforeActive: boolean; isDragging: boolean; onSelect: () => void; - onClose: (event: React.MouseEvent) => void; + onClose: (event: React.MouseEvent) => void; onDragStart: (event: React.MouseEvent) => void; onLoaded: () => void; } @@ -41,8 +42,10 @@ const Tab = forwardRef( }; }, []); - const handleDoubleClick = (event) => { - event.stopPropagation(); + const handleDoubleClick = (event?: React.MouseEvent) => { + if (event != null) { + event.stopPropagation(); + } setIsEditable(true); editableTimeoutRef.current = setTimeout(() => { if (editableRef.current) { @@ -102,12 +105,33 @@ const Tab = forwardRef( event.stopPropagation(); }; + function handleContextMenu(e: React.MouseEvent) { + let menu: ContextMenuItem[] = []; + menu.push({ + label: "Edit Name", + click: () => { + handleDoubleClick(null); + }, + }); + menu.push({ + type: "separator", + }); + menu.push({ + label: "Close", + click: () => { + onClose(e); + }, + }); + ContextMenuModel.showContextMenu(menu, e); + } + return (
{ }, 30); }; - const handleCloseTab = (event: React.MouseEvent, tabId: string) => { + const handleCloseTab = (event: React.MouseEvent, tabId: string) => { event.stopPropagation(); services.WindowService.CloseTab(tabId); deleteLayoutStateAtomForTab(tabId); diff --git a/frontend/app/tab/tabcontent.tsx b/frontend/app/tab/tabcontent.tsx index 6f9410d1..e53b9db1 100644 --- a/frontend/app/tab/tabcontent.tsx +++ b/frontend/app/tab/tabcontent.tsx @@ -64,6 +64,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
{ return false; } const b64data = btoa(asciiVal); - const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data }; + const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data, blockid: blockId }; services.BlockService.SendCommand(blockId, inputCmd); return true; }; diff --git a/frontend/app/view/term/termsticker.tsx b/frontend/app/view/term/termsticker.tsx index 7bc6332f..e41e08cc 100644 --- a/frontend/app/view/term/termsticker.tsx +++ b/frontend/app/view/term/termsticker.tsx @@ -98,7 +98,11 @@ function TermSticker({ sticker, config }: { sticker: StickerType; config: Sticke console.log("clickHandler", sticker.clickcmd, sticker.clickblockdef); if (sticker.clickcmd) { const b64data = btoa(sticker.clickcmd); - const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data }; + const inputCmd: BlockInputCommand = { + command: "controller:input", + inputdata64: b64data, + blockid: config.blockId, + }; services.BlockService.SendCommand(config.blockId, inputCmd); } if (sticker.clickblockdef) { diff --git a/frontend/faraday/lib/TileLayout.tsx b/frontend/faraday/lib/TileLayout.tsx index 6aba83ee..ef707da3 100644 --- a/frontend/faraday/lib/TileLayout.tsx +++ b/frontend/faraday/lib/TileLayout.tsx @@ -19,7 +19,7 @@ import React, { import { DropTargetMonitor, useDrag, useDragLayer, useDrop } from "react-dnd"; import { debounce, throttle } from "throttle-debounce"; import { useDevicePixelRatio } from "use-device-pixel-ratio"; -import { useLayoutTreeStateReducerAtom } from "./layoutAtom"; +import { globalLayoutTransformsMap, useLayoutTreeStateReducerAtom } from "./layoutAtom"; import { findNode } from "./layoutNode"; import { ContentRenderer, @@ -60,6 +60,11 @@ export interface TileLayoutProps { */ className?: string; + /** + * tabId this TileLayout is associated with + */ + tabId: string; + /** * A callback for getting the cursor point in reference to the current window. This removes Electron as a runtime dependency, allowing for better integration with Storybook. * @returns The cursor position relative to the current window. @@ -72,6 +77,7 @@ const DragPreviewHeight = 300; export const TileLayout = ({ layoutTreeStateAtom, + tabId, className, renderContent, renderPreview, @@ -117,7 +123,12 @@ export const TileLayout = ({ [nodeRefs, setNodeRefs] ); const [overlayTransform, setOverlayTransform] = useState(); - const [layoutLeafTransforms, setLayoutLeafTransforms] = useState>({}); + const [layoutLeafTransforms, setLayoutLeafTransformsRaw] = useState>({}); + + const setLayoutLeafTransforms = (transforms: Record) => { + globalLayoutTransformsMap.set(tabId, transforms); + setLayoutLeafTransformsRaw(transforms); + }; const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({ activeDrag: monitor.isDragging(), diff --git a/frontend/faraday/lib/layoutAtom.ts b/frontend/faraday/lib/layoutAtom.ts index 8eb5a3e9..9fdddbd2 100644 --- a/frontend/faraday/lib/layoutAtom.ts +++ b/frontend/faraday/lib/layoutAtom.ts @@ -14,6 +14,9 @@ import { WritableLayoutTreeStateAtom, } from "./model.js"; +// map from tabId => layout transforms (sizes and positions of the nodes) +let globalLayoutTransformsMap = new Map>(); + /** * Creates a new layout tree state wrapped as an atom. * @param rootNode The root node for the tree. @@ -126,3 +129,5 @@ export function deleteLayoutStateAtomForTab(tabId: string) { tabLayoutAtomCache.delete(tabId); } } + +export { globalLayoutTransformsMap }; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 51680117..f3bd797c 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -11,6 +11,13 @@ declare global { onlyPaste?: boolean; }; + type Bounds = { + x: number; + y: number; + width: number; + height: number; + }; + type ElectronApi = { /** * Determines whether the current app instance is a development build. @@ -28,6 +35,8 @@ declare global { */ getCursorPoint: () => Electron.Point; + getPlatform: () => NodeJS.Platform; + contextEditMenu: (position: { x: number; y: number }, opts: ContextMenuOpts) => void; showContextMenu: (menu: ElectronContextMenuItem[], position: { x: number; y: number }) => void; onContextMenuClick: (callback: (id: string) => void) => void; @@ -49,6 +58,61 @@ declare global { submenu?: ContextMenuItem[]; }; + type KeyPressDecl = { + mods: { + Cmd?: boolean; + Option?: boolean; + Shift?: boolean; + Ctrl?: boolean; + Alt?: boolean; + Meta?: boolean; + }; + key: string; + keyType: string; + }; + + interface WaveKeyboardEvent { + type: string; + /** + * Equivalent to KeyboardEvent.key. + */ + key: string; + /** + * Equivalent to KeyboardEvent.code. + */ + code: string; + /** + * Equivalent to KeyboardEvent.shiftKey. + */ + shift: boolean; + /** + * Equivalent to KeyboardEvent.controlKey. + */ + control: boolean; + /** + * Equivalent to KeyboardEvent.altKey. + */ + alt: boolean; + /** + * Equivalent to KeyboardEvent.metaKey. + */ + meta: boolean; + /** + * cmd is special, on mac it is meta, on windows it is alt + */ + cmd: boolean; + /** + * option is special, on mac it is alt, on windows it is meta + */ + option: boolean; + + repeat: boolean; + /** + * Equivalent to KeyboardEvent.location. + */ + location: number; + } + type SubjectWithRef = rxjs.Subject & { refCount: number; release: () => void }; } diff --git a/frontend/util/keyutil.ts b/frontend/util/keyutil.ts new file mode 100644 index 00000000..d62cea4f --- /dev/null +++ b/frontend/util/keyutil.ts @@ -0,0 +1,147 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +const KeyTypeCodeRegex = /c{(.*)}/; +const KeyTypeKey = "key"; +const KeyTypeCode = "code"; + +let PLATFORM: NodeJS.Platform = "darwin"; +const PlatformMacOS = "darwin"; + +function setKeyUtilPlatform(platform: NodeJS.Platform) { + PLATFORM = platform; +} + +function parseKey(key: string): { key: string; type: string } { + let regexMatch = key.match(KeyTypeCodeRegex); + if (regexMatch != null && regexMatch.length > 1) { + let code = regexMatch[1]; + return { key: code, type: KeyTypeCode }; + } else if (regexMatch != null) { + console.log("error: regexMatch is not null yet there is no captured group: ", regexMatch, key); + } + return { key: key, type: KeyTypeKey }; +} + +function parseKeyDescription(keyDescription: string): KeyPressDecl { + let rtn = { key: "", mods: {} } as KeyPressDecl; + let keys = keyDescription.replace(/[()]/g, "").split(":"); + for (let key of keys) { + if (key == "Cmd") { + rtn.mods.Cmd = true; + } else if (key == "Shift") { + rtn.mods.Shift = true; + } else if (key == "Ctrl") { + rtn.mods.Ctrl = true; + } else if (key == "Option") { + rtn.mods.Option = true; + } else if (key == "Alt") { + rtn.mods.Alt = true; + } else if (key == "Meta") { + rtn.mods.Meta = true; + } else { + let { key: parsedKey, type: keyType } = parseKey(key); + rtn.key = parsedKey; + rtn.keyType = keyType; + if (rtn.keyType == KeyTypeKey && key.length == 1) { + // check for if key is upper case + // TODO what about unicode upper case? + if (/[A-Z]/.test(key.charAt(0))) { + // this key is an upper case A - Z - we should apply the shift key, even if it wasn't specified + rtn.mods.Shift = true; + } else if (key == " ") { + rtn.key = "Space"; + // we allow " " and "Space" to be mapped to Space key + } + } + } + } + return rtn; +} + +function notMod(keyPressMod: boolean, eventMod: boolean) { + return (keyPressMod && !eventMod) || (eventMod && !keyPressMod); +} + +function checkKeyPressed(event: WaveKeyboardEvent, keyDescription: string): boolean { + let keyPress = parseKeyDescription(keyDescription); + if (!keyPress.mods.Alt && notMod(keyPress.mods.Option, event.option)) { + return false; + } + if (!keyPress.mods.Meta && notMod(keyPress.mods.Cmd, event.cmd)) { + return false; + } + if (notMod(keyPress.mods.Shift, event.shift)) { + return false; + } + if (notMod(keyPress.mods.Ctrl, event.control)) { + return false; + } + if (keyPress.mods.Alt && !event.alt) { + return false; + } + if (keyPress.mods.Meta && !event.meta) { + return false; + } + let eventKey = ""; + let descKey = keyPress.key; + if (keyPress.keyType == KeyTypeCode) { + eventKey = event.code; + } + if (keyPress.keyType == KeyTypeKey) { + eventKey = event.key; + if (eventKey.length == 1 && /[A-Z]/.test(eventKey.charAt(0))) { + // key is upper case A-Z, this means shift is applied, we want to allow + // "Shift:e" as well as "Shift:E" or "E" + eventKey = eventKey.toLocaleLowerCase(); + descKey = descKey.toLocaleLowerCase(); + } else if (eventKey == " ") { + eventKey = "Space"; + // a space key is shown as " ", we want users to be able to set space key as "Space" or " ", whichever they prefer + } + } + if (descKey != eventKey) { + return false; + } + return true; +} + +function adaptFromReactOrNativeKeyEvent(event: React.KeyboardEvent | KeyboardEvent): WaveKeyboardEvent { + let rtn: WaveKeyboardEvent = {} as WaveKeyboardEvent; + rtn.control = event.ctrlKey; + rtn.shift = event.shiftKey; + rtn.cmd = PLATFORM == PlatformMacOS ? event.metaKey : event.altKey; + rtn.option = PLATFORM == PlatformMacOS ? event.altKey : event.metaKey; + rtn.meta = event.metaKey; + rtn.alt = event.altKey; + rtn.code = event.code; + rtn.key = event.key; + rtn.location = event.location; + rtn.type = event.type; + rtn.repeat = event.repeat; + return rtn; +} + +function adaptFromElectronKeyEvent(event: any): WaveKeyboardEvent { + let rtn: WaveKeyboardEvent = {} as WaveKeyboardEvent; + rtn.type = event.type; + rtn.control = event.control; + rtn.cmd = PLATFORM == PlatformMacOS ? event.meta : event.alt; + rtn.option = PLATFORM == PlatformMacOS ? event.alt : event.meta; + rtn.meta = event.meta; + rtn.alt = event.alt; + rtn.shift = event.shift; + rtn.repeat = event.isAutoRepeat; + rtn.location = event.location; + rtn.code = event.code; + rtn.key = event.key; + return rtn; +} + +export { + adaptFromElectronKeyEvent, + adaptFromReactOrNativeKeyEvent, + checkKeyPressed, + parseKeyDescription, + setKeyUtilPlatform, +}; diff --git a/frontend/wave.ts b/frontend/wave.ts index ff0bb09e..032c11c6 100644 --- a/frontend/wave.ts +++ b/frontend/wave.ts @@ -1,9 +1,10 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { atoms, globalStore, globalWS, initWS } from "@/store/global"; +import { atoms, getApi, globalStore, globalWS, initWS } from "@/store/global"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; +import * as keyutil from "@/util/keyutil"; import * as React from "react"; import { createRoot } from "react-dom/client"; import { App } from "./app/app"; @@ -16,6 +17,8 @@ let clientId = urlParams.get("clientid"); console.log("Wave Starting"); console.log("clientid", clientId, "windowid", windowId); +keyutil.setKeyUtilPlatform(getApi().getPlatform()); + loadFonts(); initWS(); (window as any).globalWS = globalWS;