mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
fix: resolve pinned window shortcut not working (#917)
* fix: fix pinned window shortcut not working * docs: update changelog * refactor: update * Update _index.md --------- Co-authored-by: Medcl <m@medcl.net>
This commit is contained in:
@@ -20,6 +20,7 @@ feat: support opening logs from about page #915
|
|||||||
|
|
||||||
fix: automatic update of service list #913
|
fix: automatic update of service list #913
|
||||||
fix: duplicate chat content #916
|
fix: duplicate chat content #916
|
||||||
|
fix: resolve pinned window shortcut not working (#917)
|
||||||
|
|
||||||
### ✈️ Improvements
|
### ✈️ Improvements
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { MessageSquarePlus } from "lucide-react";
|
import { MessageSquarePlus } from "lucide-react";
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import HistoryIcon from "@/icons/History";
|
import HistoryIcon from "@/icons/History";
|
||||||
import PinOffIcon from "@/icons/PinOff";
|
|
||||||
import PinIcon from "@/icons/Pin";
|
|
||||||
import WindowsFullIcon from "@/icons/WindowsFull";
|
import WindowsFullIcon from "@/icons/WindowsFull";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import type { Chat } from "@/types/chat";
|
import type { Chat } from "@/types/chat";
|
||||||
@@ -12,6 +9,7 @@ import { useShortcutsStore } from "@/stores/shortcutsStore";
|
|||||||
import { HISTORY_PANEL_ID } from "@/constants";
|
import { HISTORY_PANEL_ID } from "@/constants";
|
||||||
import { AssistantList } from "./AssistantList";
|
import { AssistantList } from "./AssistantList";
|
||||||
import { ServerList } from "./ServerList";
|
import { ServerList } from "./ServerList";
|
||||||
|
import TogglePin from "../Common/TogglePin";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
clearChat: () => void;
|
clearChat: () => void;
|
||||||
@@ -34,21 +32,9 @@ export function ChatHeader({
|
|||||||
showChatHistory = true,
|
showChatHistory = true,
|
||||||
assistantIDs,
|
assistantIDs,
|
||||||
}: ChatHeaderProps) {
|
}: ChatHeaderProps) {
|
||||||
const { isPinned, setIsPinned, isTauri } = useAppStore();
|
const { isTauri } = useAppStore();
|
||||||
|
|
||||||
const { historicalRecords, newSession, fixedWindow, external } =
|
const { historicalRecords, newSession, external } = useShortcutsStore();
|
||||||
useShortcutsStore();
|
|
||||||
|
|
||||||
const togglePin = async () => {
|
|
||||||
try {
|
|
||||||
const { isPinned } = useAppStore.getState();
|
|
||||||
|
|
||||||
setIsPinned(!isPinned);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to toggle window pin state:", err);
|
|
||||||
setIsPinned(isPinned);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
@@ -101,16 +87,7 @@ export function ChatHeader({
|
|||||||
|
|
||||||
{isTauri ? (
|
{isTauri ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<TogglePin className="inline-flex" />
|
||||||
onClick={togglePin}
|
|
||||||
className={clsx("inline-flex", {
|
|
||||||
"text-blue-500": isPinned,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>
|
|
||||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
|
||||||
</VisibleKey>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ServerList clearChat={clearChat} />
|
<ServerList clearChat={clearChat} />
|
||||||
|
|
||||||
|
|||||||
50
src/components/Common/TogglePin.tsx
Normal file
50
src/components/Common/TogglePin.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import VisibleKey from "./VisibleKey";
|
||||||
|
import { FC, HTMLAttributes } from "react";
|
||||||
|
import PinOffIcon from "@/icons/PinOff";
|
||||||
|
import PinIcon from "@/icons/Pin";
|
||||||
|
|
||||||
|
interface TogglePinProps extends HTMLAttributes<HTMLButtonElement> {
|
||||||
|
setIsPinnedWeb?: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TogglePin: FC<TogglePinProps> = (props) => {
|
||||||
|
const { className, setIsPinnedWeb } = props;
|
||||||
|
const { isPinned, setIsPinned } = useAppStore();
|
||||||
|
const { fixedWindow } = useShortcutsStore();
|
||||||
|
|
||||||
|
const togglePin = async () => {
|
||||||
|
const { isTauri, isPinned } = useAppStore.getState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nextPinned = !isPinned;
|
||||||
|
|
||||||
|
if (!isTauri) {
|
||||||
|
setIsPinnedWeb?.(nextPinned);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsPinned(nextPinned);
|
||||||
|
} catch (err) {
|
||||||
|
setIsPinned(isPinned);
|
||||||
|
|
||||||
|
console.error("Failed to toggle window pin state:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={togglePin}
|
||||||
|
className={clsx(className, {
|
||||||
|
"text-blue-500": isPinned,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>
|
||||||
|
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
||||||
|
</VisibleKey>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TogglePin;
|
||||||
@@ -5,13 +5,10 @@ import clsx from "clsx";
|
|||||||
|
|
||||||
import CommonIcon from "@/components/Common/Icons/CommonIcon";
|
import CommonIcon from "@/components/Common/Icons/CommonIcon";
|
||||||
import Copyright from "@/components/Common/Copyright";
|
import Copyright from "@/components/Common/Copyright";
|
||||||
import PinOffIcon from "@/icons/PinOff";
|
|
||||||
import PinIcon from "@/icons/Pin";
|
|
||||||
import logoImg from "@/assets/icon.svg";
|
import logoImg from "@/assets/icon.svg";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
import { useSearchStore } from "@/stores/searchStore";
|
import { useSearchStore } from "@/stores/searchStore";
|
||||||
import { useUpdateStore } from "@/stores/updateStore";
|
import { useUpdateStore } from "@/stores/updateStore";
|
||||||
import VisibleKey from "../VisibleKey";
|
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { formatKey } from "@/utils/keyboardUtils";
|
import { formatKey } from "@/utils/keyboardUtils";
|
||||||
import source_default_img from "@/assets/images/source_default.png";
|
import source_default_img from "@/assets/images/source_default.png";
|
||||||
@@ -19,6 +16,7 @@ import source_default_dark_img from "@/assets/images/source_default_dark.png";
|
|||||||
import { useThemeStore } from "@/stores/themeStore";
|
import { useThemeStore } from "@/stores/themeStore";
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
import platformAdapter from "@/utils/platformAdapter";
|
||||||
import FontIcon from "../Icons/FontIcon";
|
import FontIcon from "../Icons/FontIcon";
|
||||||
|
import TogglePin from "../TogglePin";
|
||||||
|
|
||||||
interface FooterProps {
|
interface FooterProps {
|
||||||
setIsPinnedWeb?: (value: boolean) => void;
|
setIsPinnedWeb?: (value: boolean) => void;
|
||||||
@@ -37,28 +35,11 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
|
|
||||||
const isDark = useThemeStore((state) => state.isDark);
|
const isDark = useThemeStore((state) => state.isDark);
|
||||||
|
|
||||||
const { isTauri, isPinned, setIsPinned } = useAppStore();
|
const { isTauri } = useAppStore();
|
||||||
|
|
||||||
const { setVisible, updateInfo, skipVersions } = useUpdateStore();
|
const { setVisible, updateInfo, skipVersions } = useUpdateStore();
|
||||||
|
|
||||||
const { fixedWindow, modifierKey } = useShortcutsStore();
|
const { modifierKey } = useShortcutsStore();
|
||||||
|
|
||||||
const togglePin = async () => {
|
|
||||||
try {
|
|
||||||
const { isTauri, isPinned } = useAppStore.getState();
|
|
||||||
|
|
||||||
const nextPinned = !isPinned;
|
|
||||||
|
|
||||||
if (!isTauri) {
|
|
||||||
setIsPinnedWeb?.(nextPinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsPinned(nextPinned);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to toggle window pin state:", err);
|
|
||||||
setIsPinned(isPinned);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openSetting = useCallback(() => {
|
const openSetting = useCallback(() => {
|
||||||
return platformAdapter.emitEvent("open_settings", "");
|
return platformAdapter.emitEvent("open_settings", "");
|
||||||
@@ -88,7 +69,10 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
if (visibleExtensionDetail && selectedExtension) {
|
if (visibleExtensionDetail && selectedExtension) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<img src={selectedExtension.icon} className="size-5 dark:drop-shadow-[0_0_6px_rgb(255,255,255)]" />
|
<img
|
||||||
|
src={selectedExtension.icon}
|
||||||
|
className="size-5 dark:drop-shadow-[0_0_6px_rgb(255,255,255)]"
|
||||||
|
/>
|
||||||
<span className="text-sm">{selectedExtension.name}</span>
|
<span className="text-sm">{selectedExtension.name}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -139,17 +123,12 @@ export default function Footer({ setIsPinnedWeb }: FooterProps) {
|
|||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
{renderLeft()}
|
{renderLeft()}
|
||||||
|
|
||||||
<button
|
<TogglePin
|
||||||
onClick={togglePin}
|
|
||||||
className={clsx({
|
className={clsx({
|
||||||
"text-blue-500": isPinned,
|
|
||||||
"pl-2": hasUpdate,
|
"pl-2": hasUpdate,
|
||||||
})}
|
})}
|
||||||
>
|
setIsPinnedWeb={setIsPinnedWeb}
|
||||||
<VisibleKey shortcut={fixedWindow} onKeyPress={togglePin}>
|
/>
|
||||||
{isPinned ? <PinIcon /> : <PinOffIcon />}
|
|
||||||
</VisibleKey>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import { last } from "lodash-es";
|
|||||||
import { POPOVER_PANEL_SELECTOR } from "@/constants";
|
import { POPOVER_PANEL_SELECTOR } from "@/constants";
|
||||||
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
import { useShortcutsStore } from "@/stores/shortcutsStore";
|
||||||
import { useAppStore } from "@/stores/appStore";
|
import { useAppStore } from "@/stores/appStore";
|
||||||
|
import { KeyType } from "ahooks/lib/useKeyPress";
|
||||||
|
|
||||||
|
const keyTriggerMap = new Map<KeyType, number>();
|
||||||
|
|
||||||
interface VisibleKeyProps extends HTMLAttributes<HTMLDivElement> {
|
interface VisibleKeyProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
shortcut: string;
|
shortcut: string;
|
||||||
@@ -60,8 +63,16 @@ const VisibleKey: FC<VisibleKeyProps> = (props) => {
|
|||||||
setVisibleShortcut(isChildInPopover && modifierKeyPressed);
|
setVisibleShortcut(isChildInPopover && modifierKeyPressed);
|
||||||
}, [openPopover, modifierKeyPressed]);
|
}, [openPopover, modifierKeyPressed]);
|
||||||
|
|
||||||
useKeyPress(`${modifierKey}.${shortcut}`, (event) => {
|
useKeyPress(`${modifierKey}.${shortcut}`, (event, key) => {
|
||||||
if (!visibleShortcut) return;
|
if (!visibleShortcut || event.repeat) return;
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const last = keyTriggerMap.get(key) ?? 0;
|
||||||
|
const wait = 100;
|
||||||
|
|
||||||
|
if (now - last < wait) return;
|
||||||
|
|
||||||
|
keyTriggerMap.set(key, now);
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
Reference in New Issue
Block a user