mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-22 02:41:23 +01:00
focus/key handling for directory preview (#291)
This commit is contained in:
parent
a104a6e446
commit
53d3ad04b7
@ -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);
|
||||||
|
@ -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>
|
||||||
|
@ -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}>
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user