integrate part of keyutil, and implement tab and block movement with keyboard (#70)

This commit is contained in:
Mike Sawka
2024-06-21 12:32:38 -07:00
committed by GitHub
parent 9cc5d9d3ae
commit 0ea8e5ac88
15 changed files with 483 additions and 25 deletions
+6
View File
@@ -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();
+1
View File
@@ -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),
+186 -3
View File
@@ -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 (
<Provider store={globalStore}>
<AppInner />
@@ -45,9 +49,145 @@ function handleContextMenu(e: React.MouseEvent<HTMLDivElement>) {
}
}
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<TabLayoutData>, 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<string, Bounds>): 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<string, Bounds>): 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<string, Bounds>, 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<Tab>(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<string, Bounds> = 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 (
<div className="mainapp">
@@ -56,6 +196,49 @@ const AppInner = () => {
</div>
);
}
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 (
<div className="mainapp" onContextMenu={handleContextMenu}>
<DndProvider backend={HTML5Backend}>
+1 -13
View File
@@ -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 <BlockFrame_Tech {...props} />;
};
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<HTMLInputElement>(null);
+21
View File
@@ -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<Tab> = jotai.atom((get) => {
}
return WOS.getObjectValue(WOS.makeORef("tab", windowData.activetabid), get);
});
const activeTabIdAtom: jotai.Atom<string> = 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,
};
+27 -3
View File
@@ -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<HTMLButtonElement, MouseEvent>) => void;
onClose: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onDragStart: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onLoaded: () => void;
}
@@ -41,8 +42,10 @@ const Tab = forwardRef<HTMLDivElement, TabProps>(
};
}, []);
const handleDoubleClick = (event) => {
event.stopPropagation();
const handleDoubleClick = (event?: React.MouseEvent<any, any>) => {
if (event != null) {
event.stopPropagation();
}
setIsEditable(true);
editableTimeoutRef.current = setTimeout(() => {
if (editableRef.current) {
@@ -102,12 +105,33 @@ const Tab = forwardRef<HTMLDivElement, TabProps>(
event.stopPropagation();
};
function handleContextMenu(e: React.MouseEvent<HTMLElement>) {
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 (
<div
ref={ref}
className={clsx("tab", { active, isDragging, "before-active": isBeforeActive })}
onMouseDown={onDragStart}
onClick={onSelect}
onContextMenu={handleContextMenu}
data-tab-id={id}
>
<div
+1 -1
View File
@@ -425,7 +425,7 @@ const TabBar = ({ workspace }: TabBarProps) => {
}, 30);
};
const handleCloseTab = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, tabId: string) => {
const handleCloseTab = (event: React.MouseEvent<HTMLElement, MouseEvent>, tabId: string) => {
event.stopPropagation();
services.WindowService.CloseTab(tabId);
deleteLayoutStateAtomForTab(tabId);
+1
View File
@@ -64,6 +64,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
<div className="tabcontent">
<TileLayout
key={tabId}
tabId={tabId}
renderContent={renderBlock}
renderPreview={renderPreview}
layoutTreeStateAtom={layoutStateAtom}
+1 -1
View File
@@ -188,7 +188,7 @@ const TerminalView = ({ blockId }: { blockId: 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;
};
+5 -1
View File
@@ -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) {
+13 -2
View File
@@ -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<T> {
*/
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 = <T,>({
layoutTreeStateAtom,
tabId,
className,
renderContent,
renderPreview,
@@ -117,7 +123,12 @@ export const TileLayout = <T,>({
[nodeRefs, setNodeRefs]
);
const [overlayTransform, setOverlayTransform] = useState<CSSProperties>();
const [layoutLeafTransforms, setLayoutLeafTransforms] = useState<Record<string, CSSProperties>>({});
const [layoutLeafTransforms, setLayoutLeafTransformsRaw] = useState<Record<string, CSSProperties>>({});
const setLayoutLeafTransforms = (transforms: Record<string, CSSProperties>) => {
globalLayoutTransformsMap.set(tabId, transforms);
setLayoutLeafTransformsRaw(transforms);
};
const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({
activeDrag: monitor.isDragging(),
+5
View File
@@ -14,6 +14,9 @@ import {
WritableLayoutTreeStateAtom,
} from "./model.js";
// map from tabId => layout transforms (sizes and positions of the nodes)
let globalLayoutTransformsMap = new Map<string, Record<string, React.CSSProperties>>();
/**
* 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 };
+64
View File
@@ -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<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
}
+147
View File
@@ -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,
};
+4 -1
View File
@@ -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;