focus/key handling for directory preview (#291)

This commit is contained in:
Mike Sawka 2024-08-29 17:00:24 -07:00 committed by GitHub
parent a104a6e446
commit 53d3ad04b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 69 additions and 60 deletions

View File

@ -123,6 +123,9 @@ function appSelectionChange(e: Event) {
} }
function AppFocusHandler() { function AppFocusHandler() {
return null;
// for debugging
React.useEffect(() => { React.useEffect(() => {
document.addEventListener("focusin", appFocusIn); document.addEventListener("focusin", appFocusIn);
document.addEventListener("focusout", appFocusOut); document.addEventListener("focusout", appFocusOut);

View File

@ -232,6 +232,7 @@ const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
value="" value=""
ref={focusElemRef} ref={focusElemRef}
id={`${nodeModel.blockId}-dummy-focus`} id={`${nodeModel.blockId}-dummy-focus`}
className="dummy-focus"
onChange={() => {}} onChange={() => {}}
/> />
</div> </div>

View File

@ -18,7 +18,6 @@ import { WshServer } from "@/app/store/wshserver";
import { MagnifyIcon } from "@/element/magnify"; import { MagnifyIcon } from "@/element/magnify";
import { NodeModel } from "@/layout/index"; import { NodeModel } from "@/layout/index";
import * as keyutil from "@/util/keyutil"; import * as keyutil from "@/util/keyutil";
import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
import * as util from "@/util/util"; import * as util from "@/util/util";
import clsx from "clsx"; import clsx from "clsx";
import * as jotai from "jotai"; import * as jotai from "jotai";
@ -284,19 +283,7 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
return jotai.atom(false); return jotai.atom(false);
}) as jotai.PrimitiveAtom<boolean>; }) as jotai.PrimitiveAtom<boolean>;
const connBtnRef = React.useRef<HTMLDivElement>(); const connBtnRef = React.useRef<HTMLDivElement>();
const viewIconElem = getViewIconElem(viewIconUnion, blockData); 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 = {}; const innerStyle: React.CSSProperties = {};
if (!preview && customBg?.bg != null) { if (!preview && customBg?.bg != null) {
innerStyle.background = customBg.bg; innerStyle.background = customBg.bg;
@ -319,7 +306,6 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
onClick={blockModel?.onClick} onClick={blockModel?.onClick}
onFocusCapture={blockModel?.onFocusCapture} onFocusCapture={blockModel?.onFocusCapture}
ref={blockModel?.blockRef} ref={blockModel?.blockRef}
onKeyDown={keydownWrapper(handleKeyDown)}
> >
<BlockMask nodeModel={nodeModel} /> <BlockMask nodeModel={nodeModel} />
<div className="block-frame-default-inner" style={innerStyle}> <div className="block-frame-default-inner" style={innerStyle}>

View File

@ -42,6 +42,9 @@ function shouldDispatchToBlock(): boolean {
const activeElem = document.activeElement; const activeElem = document.activeElement;
if (activeElem != null && activeElem instanceof HTMLElement) { if (activeElem != null && activeElem instanceof HTMLElement) {
if (activeElem.tagName == "INPUT" || activeElem.tagName == "TEXTAREA") { if (activeElem.tagName == "INPUT" || activeElem.tagName == "TEXTAREA") {
if (activeElem.classList.contains("dummy-focus")) {
return true;
}
return false; return false;
} }
if (activeElem.contentEditable == "true") { if (activeElem.contentEditable == "true") {
@ -134,7 +137,23 @@ async function handleCmdN() {
} }
function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean { 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() { function registerControlShiftStateUpdateHandler() {
@ -149,18 +168,7 @@ function registerControlShiftStateUpdateHandler() {
function registerElectronReinjectKeyHandler() { function registerElectronReinjectKeyHandler() {
getApi().onReinjectKey((event: WaveKeyboardEvent) => { getApi().onReinjectKey((event: WaveKeyboardEvent) => {
console.log("reinject key event", event); appHandleKeyDown(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);
}
}); });
} }
@ -200,6 +208,14 @@ function registerGlobalKeys() {
genericClose(tabId); genericClose(tabId);
return true; 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", () => { globalKeyMap.set("Ctrl:Shift:ArrowUp", () => {
const tabId = globalStore.get(atoms.activeTabId); const tabId = globalStore.get(atoms.activeTabId);
switchBlockInDirection(tabId, NavigateDirection.Up); switchBlockInDirection(tabId, NavigateDirection.Up);

View File

@ -104,6 +104,7 @@ const Tab = React.memo(
const curLen = Array.from(editableRef.current.innerText).length; const curLen = Array.from(editableRef.current.innerText).length;
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
event.stopPropagation();
if (editableRef.current.innerText.trim() === "") { if (editableRef.current.innerText.trim() === "") {
editableRef.current.innerText = originalName; editableRef.current.innerText = originalName;
} }
@ -111,8 +112,11 @@ const Tab = React.memo(
} else if (event.key === "Escape") { } else if (event.key === "Escape") {
editableRef.current.innerText = originalName; editableRef.current.innerText = originalName;
editableRef.current.blur(); editableRef.current.blur();
event.preventDefault();
event.stopPropagation();
} else if (curLen >= 10 && !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)) { } else if (curLen >= 10 && !["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)) {
event.preventDefault(); event.preventDefault();
event.stopPropagation();
} }
}; };

View File

@ -574,8 +574,8 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) {
setFilteredData(filtered); setFilteredData(filtered);
}, [unfilteredData, showHiddenFiles, searchText]); }, [unfilteredData, showHiddenFiles, searchText]);
const handleKeyDown = useCallback( useEffect(() => {
(waveEvent: WaveKeyboardEvent): boolean => { model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {
if (keyutil.checkKeyPressed(waveEvent, "Escape")) { if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
setSearchText(""); setSearchText("");
return; return;
@ -596,9 +596,23 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) {
setSearchText(""); setSearchText("");
return true; return true;
} }
}, if (keyutil.checkKeyPressed(waveEvent, "Backspace")) {
[filteredData, setFocusIndex, selectedPath] 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(() => { useEffect(() => {
if (filteredData.length != 0 && focusIndex > filteredData.length - 1) { if (filteredData.length != 0 && focusIndex > filteredData.length - 1) {
@ -606,14 +620,6 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) {
} }
}, [filteredData]); }, [filteredData]);
const inputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
model.directoryInputElem = inputRef.current;
return () => {
model.directoryInputElem = null;
};
}, []);
return ( return (
<div <div
className="dir-table-container" className="dir-table-container"
@ -621,19 +627,8 @@ function DirectoryPreview({ fileNameAtom, model }: DirectoryPreviewProps) {
const event = e as React.ChangeEvent<HTMLInputElement>; const event = e as React.ChangeEvent<HTMLInputElement>;
setSearchText(event.target.value.toLowerCase()); setSearchText(event.target.value.toLowerCase());
}} }}
onKeyDownCapture={(e) => keyutil.keydownWrapper(handleKeyDown)(e)} // onFocusCapture={() => document.getSelection().collapseToEnd()}
onFocusCapture={() => document.getSelection().collapseToEnd()}
> >
<div className="dir-table-search-line">
<input
type="text"
className="dir-table-search-box"
ref={inputRef}
onChange={() => {}} //for nuisance warnings
maxLength={400}
value={searchText}
/>
</div>
<DirectoryTable <DirectoryTable
model={model} model={model}
data={filteredData} data={filteredData}

View File

@ -60,7 +60,7 @@ export class PreviewModel implements ViewModel {
showHiddenFiles: jotai.PrimitiveAtom<boolean>; showHiddenFiles: jotai.PrimitiveAtom<boolean>;
refreshVersion: jotai.PrimitiveAtom<number>; refreshVersion: jotai.PrimitiveAtom<number>;
refreshCallback: () => void; refreshCallback: () => void;
directoryInputElem: HTMLInputElement; directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
setPreviewFileName(fileName: string) { setPreviewFileName(fileName: string) {
services.ObjectService.UpdateObjectMeta(`block:${this.blockId}`, { file: fileName }); services.ObjectService.UpdateObjectMeta(`block:${this.blockId}`, { file: fileName });
@ -416,10 +416,6 @@ export class PreviewModel implements ViewModel {
} }
giveFocus(): boolean { giveFocus(): boolean {
if (this.directoryInputElem) {
this.directoryInputElem.focus({ preventScroll: true });
return true;
}
return false; return false;
} }
@ -437,6 +433,12 @@ export class PreviewModel implements ViewModel {
this.goParentDirectory(); this.goParentDirectory();
return true; return true;
} }
if (this.directoryKeyDownHandler) {
const handled = this.directoryKeyDownHandler(e);
if (handled) {
return true;
}
}
return false; return false;
} }
} }

View File

@ -6,6 +6,7 @@ import { TypingIndicator } from "@/app/element/typingindicator";
import { WOS, atoms, fetchWaveFile, getUserName, globalStore } from "@/store/global"; import { WOS, atoms, fetchWaveFile, getUserName, globalStore } from "@/store/global";
import * as services from "@/store/services"; import * as services from "@/store/services";
import { WshServer } from "@/store/wshserver"; import { WshServer } from "@/store/wshserver";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
import * as util from "@/util/util"; import * as util from "@/util/util";
import * as jotai from "jotai"; import * as jotai from "jotai";
import type { OverlayScrollbars } from "overlayscrollbars"; import type { OverlayScrollbars } from "overlayscrollbars";
@ -586,12 +587,13 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
}; };
const handleTextAreaKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleTextAreaKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === "Enter") { const waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (checkKeyPressed(waveEvent, "Enter")) {
e.preventDefault(); e.preventDefault();
handleEnterKeyPressed(); handleEnterKeyPressed();
} else if (e.key === "ArrowUp") { } else if (checkKeyPressed(waveEvent, "ArrowUp")) {
handleArrowUpPressed(e); handleArrowUpPressed(e);
} else if (e.key === "ArrowDown") { } else if (checkKeyPressed(waveEvent, "ArrowDown")) {
handleArrowDownPressed(e); handleArrowDownPressed(e);
} }
}; };