Skip to content
187 changes: 86 additions & 101 deletions apps/desktop/src/components/main/body/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/utils";

import { useRouteContext } from "@tanstack/react-router";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { ArrowLeftIcon, ArrowRightIcon, PanelLeftOpenIcon, PlusIcon } from "lucide-react";
import { Reorder } from "motion/react";
import { useCallback, useEffect, useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/utils";
import { useShell } from "../../../contexts/shell";
import { type Tab, uniqueIdfromTab, useTabs } from "../../../store/zustand/tabs";
import { id } from "../../../utils";
Expand All @@ -23,10 +22,6 @@ import { TabContentNote, TabItemNote } from "./sessions";
export function Body() {
const { tabs, currentTab } = useTabs();

useTabCloseHotkey();
useTabSelectHotkeys();
useNewTabHotkeys();

if (!currentTab) {
return null;
}
Expand All @@ -42,25 +37,13 @@ export function Body() {
}

function Header({ tabs }: { tabs: Tab[] }) {
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });

const { leftsidebar } = useShell();
const { select, close, reorder, openNew, goBack, goNext, canGoBack, canGoNext, closeOthers, closeAll } = useTabs();
const { select, close, reorder, goBack, goNext, canGoBack, canGoNext, closeOthers, closeAll } = useTabs();
const tabsScrollContainerRef = useRef<HTMLDivElement>(null);
const setTabRef = useScrollActiveTabIntoView(tabs);
const handleNewNote = useNewTab();

const handleNewNote = useCallback(() => {
const sessionId = id();
const user_id = internalStore?.getValue("user_id");

persistedStore?.setRow("sessions", sessionId, { user_id, created_at: new Date().toISOString(), title: "" });
openNew({
type: "sessions",
id: sessionId,
active: true,
state: { editor: "raw" },
});
}, [persistedStore, internalStore, openNew]);
const setTabRef = useScrollActiveTabIntoView(tabs);
useTabsShortcuts();

return (
<div
Expand All @@ -71,9 +54,7 @@ function Header({ tabs }: { tabs: Tab[] }) {
>
{!leftsidebar.expanded && (
<Button size="icon" variant="ghost" onClick={() => leftsidebar.setExpanded(true)}>
<PanelLeftOpenIcon
size={16}
/>
<PanelLeftOpenIcon size={16} />
</Button>
)}

Expand Down Expand Up @@ -288,81 +269,6 @@ export function StandardTabWrapper(
);
}

const useTabCloseHotkey = () => {
const { tabs, currentTab, close } = useTabs();

useHotkeys(
"mod+w",
async (e) => {
e.preventDefault();

if (currentTab && tabs.length > 1) {
close(currentTab);
} else {
const appWindow = getCurrentWebviewWindow();
await appWindow.close();
}
},
{ enableOnFormTags: true, enableOnContentEditable: true },
[tabs, currentTab, close],
);
};

const useTabSelectHotkeys = () => {
const { tabs, select } = useTabs();

useHotkeys(
["mod+1", "mod+2", "mod+3", "mod+4", "mod+5", "mod+6", "mod+7", "mod+8", "mod+9"],
(event) => {
const key = event.key;

const targetIndex = key === "9"
? tabs.length - 1
: Number.parseInt(key, 10) - 1;

const target = tabs[targetIndex];
if (!target) {
return;
}

event.preventDefault();
select(target);
},
{ enableOnFormTags: true, enableOnContentEditable: true },
[tabs, select],
);
};

const useNewTabHotkeys = () => {
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
const { currentTab, close, openNew } = useTabs();

useHotkeys(
["mod+n", "mod+t"],
(e) => {
e.preventDefault();

const sessionId = id();
const user_id = internalStore?.getValue("user_id");

persistedStore?.setRow("sessions", sessionId, { user_id, created_at: new Date().toISOString() });

if (e.key === "n" && currentTab) {
close(currentTab);
}

openNew({
type: "sessions",
id: sessionId,
active: true,
state: { editor: "raw" },
});
},
{ enableOnFormTags: true, enableOnContentEditable: true },
[persistedStore, internalStore, currentTab, close, openNew],
);
};

function useScrollActiveTabIntoView(tabs: Tab[]) {
const tabRefsMap = useRef<Map<string, HTMLDivElement>>(new Map());

Expand Down Expand Up @@ -391,3 +297,82 @@ function useScrollActiveTabIntoView(tabs: Tab[]) {

return setTabRef;
}

function useTabsShortcuts() {
const { tabs, currentTab, close, select } = useTabs();
const newTab = useNewTab();

useHotkeys(
"mod+n",
() => {
if (currentTab) {
close(currentTab);
}
newTab();
},
{ preventDefault: true },
[currentTab, close, newTab],
);

useHotkeys(
"mod+t",
() => newTab(),
{ preventDefault: true },
[newTab],
);

useHotkeys(
"mod+w",
async () => {
if (currentTab && tabs.length > 1) {
close(currentTab);
} else {
const appWindow = getCurrentWebviewWindow();
await appWindow.close();
}
},
{ preventDefault: true },
[tabs, currentTab, close],
);

useHotkeys(
"mod+1, mod+2, mod+3, mod+4, mod+5, mod+6, mod+7, mod+8, mod+9",
(event) => {
const key = event.key;
const targetIndex = key === "9" ? tabs.length - 1 : Number.parseInt(key, 10) - 1;
const target = tabs[targetIndex];
if (target) {
select(target);
}
},
{ preventDefault: true },
[tabs, select],
);

return {};
}

function useNewTab() {
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });
const { openNew } = useTabs();

const handler = useCallback(() => {
const user_id = internalStore?.getValue("user_id");
const sessionId = id();

persistedStore?.setRow("sessions", sessionId, {
user_id,
created_at: new Date().toISOString(),
title: "",
});

openNew({
type: "sessions",
id: sessionId,
active: true,
state: { editor: "raw" },
});
}, [persistedStore, internalStore, openNew]);

return handler;
}
63 changes: 9 additions & 54 deletions apps/desktop/src/components/main/body/search.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,15 @@
import { Loader2Icon, SearchIcon, XIcon } from "lucide-react";
import { useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { useState } from "react";

import { cn } from "@hypr/utils";
import { useSearch } from "../../../contexts/search/ui";

export function Search() {
const { query, setQuery, isSearching, isIndexing, onFocus, onBlur } = useSearch();
const inputRef = useRef<HTMLInputElement | null>(null);
const { query, setQuery, isSearching, isIndexing, inputRef } = useSearch();
const [isFocused, setIsFocused] = useState(false);

const showLoading = isSearching || isIndexing;

useHotkeys("mod+k", (e) => {
e.preventDefault();
inputRef.current?.focus();
});

useHotkeys(
"down",
(event) => {
if (document.activeElement === inputRef.current) {
event.preventDefault();
console.log("down");
}
},
{ enableOnFormTags: true },
);

useHotkeys(
"up",
(event) => {
if (document.activeElement === inputRef.current) {
event.preventDefault();
console.log("up");
}
},
{ enableOnFormTags: true },
);

useHotkeys(
"enter",
(event) => {
if (document.activeElement === inputRef.current) {
event.preventDefault();
console.log("enter");
}
},
{ enableOnFormTags: true },
);

const handleFocus = () => {
setIsFocused(true);
onFocus();
};

const handleBlur = () => {
setIsFocused(false);
onBlur();
};

return (
<div
className={cn([
Expand All @@ -77,8 +27,13 @@ export function Search() {
placeholder="Search anything..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={(e) => {
if (e.key === "Escape") {
e.currentTarget.blur();
}
}}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
className={cn([
"text-sm",
"w-full pl-9 h-full",
Expand Down
13 changes: 12 additions & 1 deletion apps/desktop/src/components/main/sidebar/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { commands as windowsCommands } from "@hypr/plugin-windows";
import { Kbd, KbdGroup } from "@hypr/ui/components/ui/kbd";

import { clsx } from "clsx";
import { Calendar, ChevronUpIcon, FolderOpen, Settings, Users } from "lucide-react";
Expand Down Expand Up @@ -116,7 +117,17 @@ export function ProfileSection() {
{ icon: FolderOpen, label: "Folders", onClick: handleClickFolders },
{ icon: Users, label: "Contacts", onClick: handleClickContacts },
{ icon: Calendar, label: "Calendar", onClick: handleClickCalendar },
{ icon: Settings, label: "Settings", onClick: handleClickSettings },
{
icon: Settings,
label: "Settings",
onClick: handleClickSettings,
badge: (
<KbdGroup>
<Kbd className="bg-neutral-200">⌘</Kbd>
<Kbd className="bg-neutral-200">,</Kbd>
</KbdGroup>
),
},
];

return (
Expand Down
41 changes: 30 additions & 11 deletions apps/desktop/src/components/main/sidebar/profile/ota/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,36 @@ export function UpdateChecker() {
if (state === "downloading") {
return (
<MenuItemLikeContainer>
<div className="flex items-center gap-2.5">
<Spinner size={16} className="flex-shrink-0 text-neutral-500" />
<span>
Downloading...
</span>
<div className="flex w-full items-center justify-between gap-2.5">
<div className="flex items-center gap-2.5">
<svg className="h-4 w-4 flex-shrink-0" viewBox="0 0 16 16">
<circle
cx="8"
cy="8"
r="6"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="text-neutral-200"
/>
<circle
cx="8"
cy="8"
r="6"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeDasharray={`${2 * Math.PI * 6}`}
strokeDashoffset={`${2 * Math.PI * 6 * (1 - downloadProgress.percentage / 100)}`}
strokeLinecap="round"
className="text-neutral-500 transition-all duration-300"
style={{ transform: "rotate(-90deg)", transformOrigin: "center" }}
/>
</svg>
<span className="text-sm text-black">
Downloading... ({Math.round(downloadProgress.percentage)}%)
</span>
</div>
<button
onClick={handleCancelDownload}
className={cn([
Expand All @@ -97,12 +122,6 @@ export function UpdateChecker() {
<X className="h-4 w-4 text-neutral-500" />
</button>
</div>
<div className="h-1 w-full overflow-hidden rounded-full bg-neutral-200">
<div
className="h-full bg-neutral-900 transition-all duration-300"
style={{ width: `${downloadProgress.percentage}%` }}
/>
</div>
</MenuItemLikeContainer>
);
}
Expand Down
Loading
Loading