Skip to content

Commit c9fe2cf

Browse files
committed
this types of shortcut only makes sense when tabs are visible, so better to co-locate with ui component
1 parent 6e76a26 commit c9fe2cf

File tree

4 files changed

+98
-108
lines changed

4 files changed

+98
-108
lines changed

apps/desktop/src/components/main/body/index.tsx

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { Button } from "@hypr/ui/components/ui/button";
2-
import { cn } from "@hypr/utils";
3-
41
import { useRouteContext } from "@tanstack/react-router";
2+
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
53
import { ArrowLeftIcon, ArrowRightIcon, PanelLeftOpenIcon, PlusIcon } from "lucide-react";
64
import { Reorder } from "motion/react";
75
import { useCallback, useEffect, useRef } from "react";
6+
import { useHotkeys } from "react-hotkeys-hook";
87

8+
import { Button } from "@hypr/ui/components/ui/button";
9+
import { cn } from "@hypr/utils";
910
import { useShell } from "../../../contexts/shell";
1011
import { type Tab, uniqueIdfromTab, useTabs } from "../../../store/zustand/tabs";
1112
import { id } from "../../../utils";
@@ -36,25 +37,13 @@ export function Body() {
3637
}
3738

3839
function Header({ tabs }: { tabs: Tab[] }) {
39-
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
40-
4140
const { leftsidebar } = useShell();
42-
const { select, close, reorder, openNew, goBack, goNext, canGoBack, canGoNext, closeOthers, closeAll } = useTabs();
41+
const { select, close, reorder, goBack, goNext, canGoBack, canGoNext, closeOthers, closeAll } = useTabs();
4342
const tabsScrollContainerRef = useRef<HTMLDivElement>(null);
44-
const setTabRef = useScrollActiveTabIntoView(tabs);
45-
46-
const handleNewNote = useCallback(() => {
47-
const sessionId = id();
48-
const user_id = internalStore?.getValue("user_id");
43+
const handleNewNote = useNewTab();
4944

50-
persistedStore?.setRow("sessions", sessionId, { user_id, created_at: new Date().toISOString(), title: "" });
51-
openNew({
52-
type: "sessions",
53-
id: sessionId,
54-
active: true,
55-
state: { editor: "raw" },
56-
});
57-
}, [persistedStore, internalStore, openNew]);
45+
const setTabRef = useScrollActiveTabIntoView(tabs);
46+
useTabsShortcuts();
5847

5948
return (
6049
<div
@@ -65,9 +54,7 @@ function Header({ tabs }: { tabs: Tab[] }) {
6554
>
6655
{!leftsidebar.expanded && (
6756
<Button size="icon" variant="ghost" onClick={() => leftsidebar.setExpanded(true)}>
68-
<PanelLeftOpenIcon
69-
size={16}
70-
/>
57+
<PanelLeftOpenIcon size={16} />
7158
</Button>
7259
)}
7360

@@ -310,3 +297,82 @@ function useScrollActiveTabIntoView(tabs: Tab[]) {
310297

311298
return setTabRef;
312299
}
300+
301+
function useTabsShortcuts() {
302+
const { tabs, currentTab, close, select } = useTabs();
303+
const newTab = useNewTab();
304+
305+
useHotkeys(
306+
"mod+n",
307+
() => {
308+
if (currentTab) {
309+
close(currentTab);
310+
}
311+
newTab();
312+
},
313+
{ preventDefault: true },
314+
[currentTab, close, newTab],
315+
);
316+
317+
useHotkeys(
318+
"mod+t",
319+
() => newTab(),
320+
{ preventDefault: true },
321+
[newTab],
322+
);
323+
324+
useHotkeys(
325+
"mod+w",
326+
async () => {
327+
if (currentTab && tabs.length > 1) {
328+
close(currentTab);
329+
} else {
330+
const appWindow = getCurrentWebviewWindow();
331+
await appWindow.close();
332+
}
333+
},
334+
{ preventDefault: true },
335+
[tabs, currentTab, close],
336+
);
337+
338+
useHotkeys(
339+
"mod+1, mod+2, mod+3, mod+4, mod+5, mod+6, mod+7, mod+8, mod+9",
340+
(event) => {
341+
const key = event.key;
342+
const targetIndex = key === "9" ? tabs.length - 1 : Number.parseInt(key, 10) - 1;
343+
const target = tabs[targetIndex];
344+
if (target) {
345+
select(target);
346+
}
347+
},
348+
{ preventDefault: true },
349+
[tabs, select],
350+
);
351+
352+
return {};
353+
}
354+
355+
function useNewTab() {
356+
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
357+
const { openNew } = useTabs();
358+
359+
const handler = useCallback(() => {
360+
const user_id = internalStore?.getValue("user_id");
361+
const sessionId = id();
362+
363+
persistedStore?.setRow("sessions", sessionId, {
364+
user_id,
365+
created_at: new Date().toISOString(),
366+
title: "",
367+
});
368+
369+
openNew({
370+
type: "sessions",
371+
id: sessionId,
372+
active: true,
373+
state: { editor: "raw" },
374+
});
375+
}, [persistedStore, internalStore, openNew]);
376+
377+
return handler;
378+
}

apps/desktop/src/contexts/shell/index.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,22 @@ import { createContext, useContext } from "react";
33
import { useChatMode } from "./chat";
44
import { useLeftSidebar } from "./leftsidebar";
55
import { useSettings } from "./settings";
6-
import { useTabsShortcuts } from "./tabs";
76

87
interface ShellContextType {
98
chat: ReturnType<typeof useChatMode>;
109
leftsidebar: ReturnType<typeof useLeftSidebar>;
1110
settings: ReturnType<typeof useSettings>;
12-
tabs: ReturnType<typeof useTabsShortcuts>;
1311
}
1412

1513
const ShellContext = createContext<ShellContextType | null>(null);
1614

17-
interface ShellProviderProps {
18-
children: React.ReactNode;
19-
onNewTab: (closeCurrentFirst: boolean) => void;
20-
}
21-
22-
export function ShellProvider({ children, onNewTab }: ShellProviderProps) {
15+
export function ShellProvider({ children }: { children: React.ReactNode }) {
2316
const chat = useChatMode();
2417
const leftsidebar = useLeftSidebar();
2518
const settings = useSettings();
26-
const tabs = useTabsShortcuts(onNewTab);
2719

2820
return (
29-
<ShellContext.Provider value={{ chat, leftsidebar, settings, tabs }}>
21+
<ShellContext.Provider value={{ chat, leftsidebar, settings }}>
3022
{children}
3123
</ShellContext.Provider>
3224
);

apps/desktop/src/contexts/shell/tabs.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

apps/desktop/src/routes/app/main/_layout.tsx

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createFileRoute, Outlet, useRouteContext } from "@tanstack/react-router";
2-
import { useCallback, useEffect } from "react";
2+
import { useEffect } from "react";
33

44
import { toolFactories } from "../../../chat/tools";
55
import { useSearchEngine } from "../../../contexts/search/engine";
@@ -17,7 +17,7 @@ export const Route = createFileRoute("/app/main/_layout")({
1717

1818
function Component() {
1919
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
20-
const { registerOnClose, registerOnEmpty, currentTab, openNew, close } = useTabs();
20+
const { registerOnClose, registerOnEmpty, currentTab, openNew } = useTabs();
2121

2222
useEffect(() => {
2323
return registerOnClose((tab) => {
@@ -49,47 +49,20 @@ function Component() {
4949
return registerOnEmpty(createDefaultSession);
5050
}, [currentTab, persistedStore, internalStore, registerOnEmpty, openNew]);
5151

52-
const handleNewTab = useCallback((closeCurrentFirst: boolean) => {
53-
const user_id = internalStore?.getValue("user_id");
54-
const sessionId = id();
55-
persistedStore?.setRow("sessions", sessionId, {
56-
user_id,
57-
created_at: new Date().toISOString(),
58-
title: "",
59-
});
60-
61-
if (closeCurrentFirst && currentTab) {
62-
close(currentTab);
63-
}
64-
65-
openNew({
66-
type: "sessions",
67-
id: sessionId,
68-
active: true,
69-
state: { editor: "raw" },
70-
});
71-
}, [persistedStore, internalStore, currentTab, close, openNew]);
72-
7352
return (
7453
<SearchEngineProvider store={persistedStore}>
7554
<SearchUIProvider>
76-
<SearchShortcutBridge onNewTab={handleNewTab} />
55+
<ShellProvider>
56+
<ToolRegistryProvider>
57+
<ToolRegistration />
58+
<Outlet />
59+
</ToolRegistryProvider>
60+
</ShellProvider>
7761
</SearchUIProvider>
7862
</SearchEngineProvider>
7963
);
8064
}
8165

82-
function SearchShortcutBridge({ onNewTab }: { onNewTab: (closeCurrentFirst: boolean) => void }) {
83-
return (
84-
<ShellProvider onNewTab={onNewTab}>
85-
<ToolRegistryProvider>
86-
<ToolRegistration />
87-
<Outlet />
88-
</ToolRegistryProvider>
89-
</ShellProvider>
90-
);
91-
}
92-
9366
function ToolRegistration() {
9467
const registry = useToolRegistry();
9568
const { search } = useSearchEngine();

0 commit comments

Comments
 (0)