mirror of
https://github.com/infinilabs/coco-app.git
synced 2026-08-29 10:09:30 +02:00
refactor: optimized the logic of esc key handling (#437)
This commit is contained in:
@@ -17,10 +17,11 @@ import FontIcon from "@/components/Common/Icons/FontIcon";
|
||||
import { useChatStore } from "@/stores/chatStore";
|
||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import { Post } from "@/api/axiosRequest";
|
||||
import { Input, Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { useDebounce, useKeyPress, useMount, usePagination } from "ahooks";
|
||||
import clsx from "clsx";
|
||||
import NoDataImage from "../Common/NoDataImage";
|
||||
import PopoverInput from "../Common/PopoverInput";
|
||||
|
||||
interface AssistantListProps {
|
||||
assistantIDs?: string[];
|
||||
@@ -262,7 +263,7 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) {
|
||||
searchInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
<PopoverInput
|
||||
ref={searchInputRef}
|
||||
autoFocus
|
||||
value={keyword}
|
||||
|
||||
34
src/components/Common/PopoverInput.tsx
Normal file
34
src/components/Common/PopoverInput.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { POPOVER_PANEL_SELECTOR } from "@/constants";
|
||||
import { Input, InputProps } from "@headlessui/react";
|
||||
import { useKeyPress } from "ahooks";
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
|
||||
const PopoverInput = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useImperativeHandle(ref, () => inputRef.current!);
|
||||
|
||||
useKeyPress(
|
||||
"esc",
|
||||
(event) => {
|
||||
if (inputRef.current === document.activeElement) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
inputRef.current?.blur();
|
||||
|
||||
const parentPanel = inputRef.current?.closest(POPOVER_PANEL_SELECTOR);
|
||||
if (parentPanel instanceof HTMLElement) {
|
||||
parentPanel.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
target: inputRef,
|
||||
}
|
||||
);
|
||||
|
||||
return <Input ref={inputRef} {...props} />;
|
||||
});
|
||||
|
||||
export default PopoverInput;
|
||||
@@ -90,7 +90,6 @@ export default function ChatInput({
|
||||
getDataSourcesByServer,
|
||||
getMCPByServer,
|
||||
setupWindowFocusListener,
|
||||
hideCoco,
|
||||
hasModules = [],
|
||||
searchPlaceholder,
|
||||
chatPlaceholder,
|
||||
@@ -100,7 +99,6 @@ export default function ChatInput({
|
||||
const currentAssistant = useConnectStore((state) => state.currentAssistant);
|
||||
|
||||
const showTooltip = useAppStore((state) => state.showTooltip);
|
||||
const isPinned = useAppStore((state) => state.isPinned);
|
||||
|
||||
const sourceData = useSearchStore((state) => state.sourceData);
|
||||
const setSourceData = useSearchStore((state) => state.setSourceData);
|
||||
@@ -184,35 +182,14 @@ export default function ChatInput({
|
||||
|
||||
const pressedKeys = new Set<string>();
|
||||
|
||||
const handleEscapeKey = useCallback(() => {
|
||||
if (inputValue) {
|
||||
changeInput("");
|
||||
} else if (!isPinned) {
|
||||
hideCoco && hideCoco();
|
||||
}
|
||||
}, [inputValue, isPinned]);
|
||||
|
||||
useKeyPress(`${modifierKey}.${returnToInput}`, handleToggleFocus);
|
||||
|
||||
const visibleContextMenu = useSearchStore((state) => {
|
||||
return state.visibleContextMenu;
|
||||
});
|
||||
const setVisibleContextMenu = useSearchStore((state) => {
|
||||
return state.setVisibleContextMenu;
|
||||
});
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
// console.log("handleKeyDown", e.code, e.key);
|
||||
|
||||
if (e.key === "Escape") {
|
||||
if (visibleContextMenu) {
|
||||
return setVisibleContextMenu(false);
|
||||
}
|
||||
|
||||
return handleEscapeKey();
|
||||
}
|
||||
|
||||
pressedKeys.add(e.key);
|
||||
|
||||
if (e.key === metaOrCtrlKey()) {
|
||||
@@ -496,7 +473,7 @@ export default function ChatInput({
|
||||
getDataSourcesByServer={getDataSourcesByServer}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
{currentAssistant?._source?.mcp_servers?.visible && (
|
||||
<MCPPopover
|
||||
isMCPActive={isMCPActive}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Input, Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
RefreshCw,
|
||||
@@ -21,6 +21,7 @@ import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||
import VisibleKey from "@/components/Common/VisibleKey";
|
||||
import { useChatStore } from "@/stores/chatStore";
|
||||
import NoDataImage from "@/components/Common/NoDataImage";
|
||||
import PopoverInput from "../Common/PopoverInput";
|
||||
|
||||
interface SearchPopoverProps {
|
||||
isMCPActive: boolean;
|
||||
@@ -248,7 +249,7 @@ export default function SearchPopover({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
<PopoverInput
|
||||
autoFocus
|
||||
ref={searchInputRef}
|
||||
className="size-full px-2 rounded-lg border dark:border-white/10 bg-transparent"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Input, Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
RefreshCw,
|
||||
@@ -22,6 +22,7 @@ import VisibleKey from "@/components/Common/VisibleKey";
|
||||
import { useChatStore } from "@/stores/chatStore";
|
||||
import NoDataImage from "@/components/Common/NoDataImage";
|
||||
import FontIcon from "../Common/Icons/FontIcon";
|
||||
import PopoverInput from "../Common/PopoverInput";
|
||||
|
||||
interface SearchPopoverProps {
|
||||
isSearchActive: boolean;
|
||||
@@ -254,7 +255,7 @@ export default function SearchPopover({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
<PopoverInput
|
||||
autoFocus
|
||||
ref={searchInputRef}
|
||||
className="size-full px-2 rounded-lg border dark:border-white/10 bg-transparent"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
import platformAdapter from "@/utils/platformAdapter";
|
||||
import { useSearchStore } from "@/stores/searchStore";
|
||||
import { useKeyPress } from "ahooks";
|
||||
import { HISTORY_PANEL_ID } from "@/constants";
|
||||
|
||||
const useEscape = () => {
|
||||
const visibleContextMenu = useSearchStore((state) => {
|
||||
@@ -11,38 +11,33 @@ const useEscape = () => {
|
||||
return state.setVisibleContextMenu;
|
||||
});
|
||||
|
||||
const handleEscape = useCallback(() => {
|
||||
async (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
console.log("Escape key pressed.");
|
||||
useKeyPress("esc", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
event.preventDefault();
|
||||
if (
|
||||
document.activeElement instanceof HTMLInputElement ||
|
||||
document.activeElement instanceof HTMLTextAreaElement
|
||||
) {
|
||||
return document.activeElement.blur();
|
||||
}
|
||||
|
||||
if (visibleContextMenu) {
|
||||
return setVisibleContextMenu(false);
|
||||
}
|
||||
if (visibleContextMenu) {
|
||||
return setVisibleContextMenu(false);
|
||||
}
|
||||
|
||||
// Hide the Tauri app window when 'Esc' is pressed
|
||||
await platformAdapter.invokeBackend("hide_coco");
|
||||
const historyPanel = document.getElementById(HISTORY_PANEL_ID);
|
||||
|
||||
console.log("App window hidden successfully.");
|
||||
}
|
||||
};
|
||||
}, [visibleContextMenu]);
|
||||
if (historyPanel) {
|
||||
const button = document.querySelector(
|
||||
`[aria-controls="${HISTORY_PANEL_ID}"]`
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = platformAdapter.listenEvent("tauri://focus", () => {
|
||||
// Add event listener for keydown
|
||||
window.addEventListener("keydown", handleEscape);
|
||||
});
|
||||
return (button as HTMLElement).click();
|
||||
}
|
||||
|
||||
// Cleanup event listener on component unmount
|
||||
return () => {
|
||||
unlisten.then((unlistenFn) => unlistenFn());
|
||||
|
||||
window.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, []);
|
||||
platformAdapter.hideWindow();
|
||||
});
|
||||
};
|
||||
|
||||
export default useEscape;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useSyncStore } from "@/hooks/useSyncStore";
|
||||
function MainApp() {
|
||||
const setIsTauri = useAppStore((state) => state.setIsTauri);
|
||||
setIsTauri(true);
|
||||
|
||||
|
||||
const hideCoco = useCallback(() => {
|
||||
return platformAdapter.hideWindow();
|
||||
}, []);
|
||||
|
||||
@@ -64,8 +64,6 @@ function WebApp({
|
||||
localStorage.setItem("headers", JSON.stringify(headers || {}));
|
||||
}, []);
|
||||
|
||||
useModifierKeyPress();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [isChatMode, setIsChatMode] = useState(false);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "./wrappers/tauriWrappers";
|
||||
import type { BasePlatformAdapter } from "@/types/platform";
|
||||
import type { AppTheme } from "@/types/index";
|
||||
import { useAppStore } from "@/stores/appStore";
|
||||
|
||||
export interface TauriPlatformAdapter extends BasePlatformAdapter {
|
||||
openFileDialog: (
|
||||
@@ -27,6 +28,10 @@ export const createTauriAdapter = (): TauriPlatformAdapter => {
|
||||
},
|
||||
|
||||
async hideWindow() {
|
||||
const isPinned = useAppStore.getState().isPinned;
|
||||
|
||||
if (isPinned) return;
|
||||
|
||||
const window = await windowWrapper.getWebviewWindow();
|
||||
return window?.hide();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user