diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 135c91da8..0ebddc564 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -123,6 +123,9 @@ function appSelectionChange(e: Event) { } function AppFocusHandler() { + return null; + + // for debugging React.useEffect(() => { document.addEventListener("focusin", appFocusIn); document.addEventListener("focusout", appFocusOut); diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index ae63f1f05..04ced6616 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -232,6 +232,7 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => { value="" ref={focusElemRef} id={`${nodeModel.blockId}-dummy-focus`} + className="dummy-focus" onChange={() => {}} /> diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 8c2af6ee7..a7375d08e 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -18,7 +18,6 @@ import { WshServer } from "@/app/store/wshserver"; import { MagnifyIcon } from "@/element/magnify"; import { NodeModel } from "@/layout/index"; import * as keyutil from "@/util/keyutil"; -import { checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import * as util from "@/util/util"; import clsx from "clsx"; import * as jotai from "jotai"; @@ -284,19 +283,7 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => { return jotai.atom(false); }) as jotai.PrimitiveAtom; const connBtnRef = React.useRef(); - const viewIconElem = getViewIconElem(viewIconUnion, blockData); - - function handleKeyDown(waveEvent: WaveKeyboardEvent): boolean { - if (checkKeyPressed(waveEvent, "Cmd:m")) { - nodeModel.toggleMagnify(); - return true; - } - if (viewModel?.keyDownHandler) { - return viewModel.keyDownHandler(waveEvent); - } - return false; - } const innerStyle: React.CSSProperties = {}; if (!preview && customBg?.bg != null) { innerStyle.background = customBg.bg; @@ -319,7 +306,6 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => { onClick={blockModel?.onClick} onFocusCapture={blockModel?.onFocusCapture} ref={blockModel?.blockRef} - onKeyDown={keydownWrapper(handleKeyDown)} >
diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index b536bc63a..ab71aec58 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -42,6 +42,9 @@ function shouldDispatchToBlock(): boolean { const activeElem = document.activeElement; if (activeElem != null && activeElem instanceof HTMLElement) { if (activeElem.tagName == "INPUT" || activeElem.tagName == "TEXTAREA") { + if (activeElem.classList.contains("dummy-focus")) { + return true; + } return false; } if (activeElem.contentEditable == "true") { @@ -134,7 +137,23 @@ async function handleCmdN() { } function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean { - return handleGlobalWaveKeyboardEvents(waveEvent); + const handled = handleGlobalWaveKeyboardEvents(waveEvent); + if (handled) { + return true; + } + const layoutModel = getLayoutModelForActiveTab(); + const focusedNode = globalStore.get(layoutModel.focusedNode); + const blockId = focusedNode?.data?.blockId; + if (blockId != null && shouldDispatchToBlock()) { + const viewModel = getViewModel(blockId); + if (viewModel?.keyDownHandler) { + const handledByBlock = viewModel.keyDownHandler(waveEvent); + if (handledByBlock) { + return true; + } + } + } + return false; } function registerControlShiftStateUpdateHandler() { @@ -149,18 +168,7 @@ function registerControlShiftStateUpdateHandler() { function registerElectronReinjectKeyHandler() { getApi().onReinjectKey((event: WaveKeyboardEvent) => { - console.log("reinject key event", event); - const handled = handleGlobalWaveKeyboardEvents(event); - if (handled) { - return; - } - const layoutModel = getLayoutModelForActiveTab(); - const focusedNode = globalStore.get(layoutModel.focusedNode); - const blockId = focusedNode?.data?.blockId; - if (blockId != null && shouldDispatchToBlock()) { - const viewModel = getViewModel(blockId); - viewModel?.keyDownHandler?.(event); - } + appHandleKeyDown(event); }); } @@ -200,6 +208,14 @@ function registerGlobalKeys() { genericClose(tabId); return true; }); + globalKeyMap.set("Cmd:m", () => { + const layoutModel = getLayoutModelForActiveTab(); + const focusedNode = globalStore.get(layoutModel.focusedNode); + if (focusedNode != null) { + layoutModel.magnifyNodeToggle(focusedNode.id); + } + return true; + }); globalKeyMap.set("Ctrl:Shift:ArrowUp", () => { const tabId = globalStore.get(atoms.activeTabId); switchBlockInDirection(tabId, NavigateDirection.Up); diff --git a/frontend/app/tab/tab.tsx b/frontend/app/tab/tab.tsx index 7c0ceadf8..11855a958 100644 --- a/frontend/app/tab/tab.tsx +++ b/frontend/app/tab/tab.tsx @@ -104,6 +104,7 @@ const Tab = React.memo( const curLen = Array.from(editableRef.current.innerText).length; if (event.key === "Enter") { event.preventDefault(); + event.stopPropagation(); if (editableRef.current.innerText.trim() === "") { editableRef.current.innerText = originalName; } @@ -111,8 +112,11 @@ const Tab = React.memo( } else if (event.key === "Escape") { editableRef.current.innerText = originalName; editableRef.current.blur(); + event.preventDefault(); + event.stopPropagation(); } else if (curLen >= 10 && !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)) { event.preventDefault(); + event.stopPropagation(); } }; diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 2d492ae40..d1dc7e8cb 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -574,8 +574,8 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) { setFilteredData(filtered); }, [unfilteredData, showHiddenFiles, searchText]); - const handleKeyDown = useCallback( - (waveEvent: WaveKeyboardEvent): boolean => { + useEffect(() => { + model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => { if (keyutil.checkKeyPressed(waveEvent, "Escape")) { setSearchText(""); return; @@ -596,9 +596,23 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) { setSearchText(""); return true; } - }, - [filteredData, setFocusIndex, selectedPath] - ); + if (keyutil.checkKeyPressed(waveEvent, "Backspace")) { + if (searchText.length == 0) { + return true; + } + setSearchText((current) => current.slice(0, -1)); + return true; + } + if (keyutil.isCharacterKeyEvent(waveEvent)) { + setSearchText((current) => current + waveEvent.key); + return true; + } + return false; + }; + return () => { + model.directoryKeyDownHandler = null; + }; + }, [filteredData, selectedPath]); useEffect(() => { if (filteredData.length != 0 && focusIndex > filteredData.length - 1) { @@ -606,14 +620,6 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) { } }, [filteredData]); - const inputRef = React.useRef(null); - React.useEffect(() => { - model.directoryInputElem = inputRef.current; - return () => { - model.directoryInputElem = null; - }; - }, []); - return (
; setSearchText(event.target.value.toLowerCase()); }} - onKeyDownCapture={(e) => keyutil.keydownWrapper(handleKeyDown)(e)} - onFocusCapture={() => document.getSelection().collapseToEnd()} + // onFocusCapture={() => document.getSelection().collapseToEnd()} > -
- {}} //for nuisance warnings - maxLength={400} - value={searchText} - /> -
; refreshVersion: jotai.PrimitiveAtom; refreshCallback: () => void; - directoryInputElem: HTMLInputElement; + directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean; setPreviewFileName(fileName: string) { services.ObjectService.UpdateObjectMeta(`block:${this.blockId}`, { file: fileName }); @@ -416,10 +416,6 @@ export class PreviewModel implements ViewModel { } giveFocus(): boolean { - if (this.directoryInputElem) { - this.directoryInputElem.focus({ preventScroll: true }); - return true; - } return false; } @@ -437,6 +433,12 @@ export class PreviewModel implements ViewModel { this.goParentDirectory(); return true; } + if (this.directoryKeyDownHandler) { + const handled = this.directoryKeyDownHandler(e); + if (handled) { + return true; + } + } return false; } } diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 3e73028df..d3cb634a1 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -6,6 +6,7 @@ import { TypingIndicator } from "@/app/element/typingindicator"; import { WOS, atoms, fetchWaveFile, getUserName, globalStore } from "@/store/global"; import * as services from "@/store/services"; import { WshServer } from "@/store/wshserver"; +import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import * as util from "@/util/util"; import * as jotai from "jotai"; import type { OverlayScrollbars } from "overlayscrollbars"; @@ -586,12 +587,13 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => { }; const handleTextAreaKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + const waveEvent = adaptFromReactOrNativeKeyEvent(e); + if (checkKeyPressed(waveEvent, "Enter")) { e.preventDefault(); handleEnterKeyPressed(); - } else if (e.key === "ArrowUp") { + } else if (checkKeyPressed(waveEvent, "ArrowUp")) { handleArrowUpPressed(e); - } else if (e.key === "ArrowDown") { + } else if (checkKeyPressed(waveEvent, "ArrowDown")) { handleArrowDownPressed(e); } };