Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/desktop2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"preview": "vite preview",
"tauri": "tauri",
"typecheck": "tsc --noEmit",
"test": "vitest run"
"test": "vitest run",
"tauri:dev": "tauri dev",
"tauri:build": "tauri build"
},
"dependencies": {
"@ai-sdk/openai-compatible": "^1.0.22",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop2/src/components/chat/body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function ChatBody({
ref={scrollRef}
className={cn([
"flex-1 overflow-y-auto",
chat.mode === "RightPanelOpen" && "border mt-1 mr-1 rounded-md rounded-b-none",
chat.mode === "RightPanelOpen" && "border mt-1 rounded-md rounded-b-none",
])}
>
{messages.length === 0
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop2/src/components/chat/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export function ChatHeader({
<div
data-tauri-drag-region={chat.mode === "RightPanelOpen"}
className={cn([
"flex items-center justify-between px-3 py-0.5 border-b border-neutral-200",
chat.mode === "RightPanelOpen" && "border mt-1 mr-1 rounded-md",
"flex items-center justify-between px-1 py-0.5 border-b border-neutral-200 h-9",
chat.mode === "RightPanelOpen" && "border rounded-md",
])}
>
<div className="flex items-center">
Expand Down Expand Up @@ -102,7 +102,7 @@ function ChatGroups({
return (
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<button className="flex items-center gap-2 hover:bg-neutral-100/60 active:bg-neutral-100 px-2 py-1.5 rounded-lg transition-all group">
<button className="flex items-center gap-2 hover:bg-neutral-100/60 active:bg-neutral-100 px-2 py-1.5 rounded-lg transition-all group focus:ring-2 focus:ring-neutral-400 focus:ring-offset-1">
<MessageCircle className="w-3.5 h-3.5 text-neutral-400 group-hover:text-neutral-600 transition-colors" />
<h3 className="font-medium text-neutral-700 text-xs truncate">
{currentChatTitle || "Ask Hyprnote anything"}
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop2/src/components/chat/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export function ChatMessageInput({
>
<div
className={cn([
"flex items-center gap-2 px-3 py-2 border border-neutral-200 rounded-xl",
"flex items-center gap-2 px-3 py-2 border border-neutral-200 rounded-md",
"focus-within:ring-1 focus-within:ring-blue-500 focus-within:border-blue-500",
chat.mode === "RightPanelOpen" && "rounded-t-none mb-1 mr-1 border-t-0",
chat.mode === "RightPanelOpen" && "rounded-t-none border-t-0",
])}
>
<button className={cn(["text-neutral-400 hover:text-neutral-600 transition-colors shrink-0"])}>
Expand Down
209 changes: 158 additions & 51 deletions apps/desktop2/src/components/main/body/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useRouteContext } from "@tanstack/react-router";
import { PanelLeftOpenIcon, PlusIcon } from "lucide-react";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { ArrowLeftIcon, ArrowRightIcon, PanelLeftOpenIcon, PlusIcon } from "lucide-react";
import { Reorder } from "motion/react";
import { useCallback } from "react";
import { useCallback, useEffect, useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { cn } from "@hypr/ui/lib/utils";
import { useShell } from "../../../contexts/shell";
Expand All @@ -21,12 +23,14 @@ export function Body() {
const { tabs, currentTab } = useTabs();
const { chat } = useShell();

useTabCloseHotkey();

if (!currentTab) {
return null;
}

return (
<div className="flex flex-col p-1 gap-1 h-full flex-1 relative">
<div className="flex flex-col gap-1 h-full flex-1 relative">
<Header tabs={tabs} />
<div className="flex-1 overflow-auto">
<Content tab={currentTab} />
Expand All @@ -40,7 +44,9 @@ function Header({ tabs }: { tabs: Tab[] }) {
const { persistedStore, internalStore } = useRouteContext({ from: "__root__" });

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

const handleNewNote = useCallback(() => {
const sessionId = id();
Expand All @@ -58,60 +64,112 @@ function Header({ tabs }: { tabs: Tab[] }) {
return (
<div
className={cn([
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]",
"w-full overflow-x-auto h-8",
"w-full h-9 flex items-end",
!leftsidebar.expanded && "pl-[72px]",
])}
>
<div className="flex w-full h-full items-end gap-4">
<div data-tauri-drag-region className="flex items-end h-full flex-1 min-w-0">
{!leftsidebar.expanded && (
<div className="flex items-center justify-center h-full px-3 sticky left-0 bg-white z-20">
<PanelLeftOpenIcon
className="h-5 w-5 cursor-pointer"
onClick={() => leftsidebar.setExpanded(true)}
/>
</div>
)}

<Reorder.Group
key={leftsidebar.expanded ? "expanded" : "collapsed"}
as="div"
axis="x"
values={tabs}
onReorder={reorder}
className="flex w-max gap-1 h-full"
>
{tabs.map((tab) => (
<Reorder.Item
key={uniqueIdfromTab(tab)}
value={tab}
as="div"
style={{ position: "relative" }}
className="h-full z-10"
layoutScroll
>
<TabItem tab={tab} handleClose={close} handleSelect={select} />
</Reorder.Item>
))}
</Reorder.Group>
<button
onClick={handleNewNote}
{!leftsidebar.expanded && (
<div className="flex items-center justify-center h-full px-3 shrink-0 bg-white z-20">
<PanelLeftOpenIcon
className="h-5 w-5 cursor-pointer"
onClick={() => leftsidebar.setExpanded(true)}
/>
</div>
)}

<div className="flex items-center h-full shrink-0">
<button
onClick={goBack}
disabled={!canGoBack}
className={cn([
"flex items-center justify-center",
"h-full",
"px-1.5",
"rounded-lg",
"transition-colors",
canGoBack && ["hover:bg-gray-50", "group"],
!canGoBack && "cursor-not-allowed",
])}
>
<ArrowLeftIcon
className={cn([
"flex items-center justify-center",
"h-full",
"mx-1 px-1.5",
"border border-gray-400 rounded-lg",
"bg-white hover:bg-gray-50",
"transition-colors",
"h-4 w-4",
canGoBack && ["text-black/70", "cursor-pointer", "group-hover:text-black"],
!canGoBack && ["text-black/30", "cursor-not-allowed"],
])}
>
<PlusIcon className="h-4 w-4 text-gray-800 cursor-pointer" />
</button>
</div>
/>
</button>
<button
onClick={goNext}
disabled={!canGoNext}
className={cn([
"flex items-center justify-center",
"h-full",
"px-1.5",
"rounded-lg",
"transition-colors",
canGoNext && ["hover:bg-gray-50", "group"],
!canGoNext && "cursor-not-allowed",
])}
>
<ArrowRightIcon
className={cn([
"h-4 w-4",
canGoNext && ["text-black/70", "cursor-pointer", "group-hover:text-black"],
!canGoNext && ["text-black/30", "cursor-not-allowed"],
])}
/>
</button>
</div>

<Search />
<div
ref={tabsScrollContainerRef}
data-tauri-drag-region
className={cn([
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]",
"flex-1 min-w-0 overflow-x-auto overflow-y-hidden h-full",
])}
>
<Reorder.Group
key={leftsidebar.expanded ? "expanded" : "collapsed"}
as="div"
axis="x"
values={tabs}
onReorder={reorder}
className="flex w-max gap-1 h-full"
>
{tabs.map((tab) => (
<Reorder.Item
key={uniqueIdfromTab(tab)}
value={tab}
as="div"
ref={(el) => setTabRef(tab, el)}
style={{ position: "relative" }}
className="h-full z-10"
layoutScroll
>
<TabItem tab={tab} handleClose={close} handleSelect={select} />
</Reorder.Item>
))}
</Reorder.Group>
</div>

<button
onClick={handleNewNote}
className={cn([
"flex items-center justify-center",
"h-full",
"px-1.5",
"rounded-lg",
"bg-white hover:bg-gray-50",
"transition-colors",
"shrink-0",
])}
>
<PlusIcon className="h-4 w-4 text-color3 cursor-pointer" />
</button>

<Search />
</div>
);
}
Expand Down Expand Up @@ -171,3 +229,52 @@ function Content({ tab }: { tab: Tab }) {

return null;
}

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 },
[tabs, currentTab, close],
);
};

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

useEffect(() => {
const activeTab = tabs.find((tab) => tab.active);
if (activeTab) {
const tabKey = uniqueIdfromTab(activeTab);
const tabElement = tabRefsMap.current.get(tabKey);
if (tabElement) {
tabElement.scrollIntoView({
behavior: "smooth",
inline: "nearest",
block: "nearest",
});
}
}
}, [tabs]);

const setTabRef = useCallback((tab: Tab, el: HTMLDivElement | null) => {
if (el) {
tabRefsMap.current.set(uniqueIdfromTab(tab), el);
} else {
tabRefsMap.current.delete(uniqueIdfromTab(tab));
}
}, []);

return setTabRef;
}
32 changes: 24 additions & 8 deletions apps/desktop2/src/components/main/body/search.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Loader2Icon, SearchIcon, XIcon } from "lucide-react";
import { useRef } from "react";
import { useRef, useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { cn } from "@hypr/ui/lib/utils";
Expand All @@ -8,6 +8,7 @@ import { useSearch } from "../../../contexts/search/ui";
export function Search() {
const { query, setQuery, isSearching, isIndexing, onFocus, onBlur } = useSearch();
const inputRef = useRef<HTMLInputElement | null>(null);
const [isFocused, setIsFocused] = useState(false);

const showLoading = isSearching || isIndexing;

Expand Down Expand Up @@ -49,9 +50,24 @@ export function Search() {
{ enableOnFormTags: true },
);

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

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

return (
<div className="flex items-center h-full pl-4 flex-[0_1_260px] min-w-[160px] w-full">
<div className="relative flex items-center w-full">
<div
className={cn([
"flex items-center h-full transition-all duration-300",
isFocused ? "w-[240px]" : "w-[200px]",
])}
>
<div className="relative flex items-center w-full h-full">
{showLoading
? <Loader2Icon className={cn(["h-4 w-4 absolute left-3 text-gray-400 animate-spin"])} />
: <SearchIcon className={cn(["h-4 w-4 absolute left-3 text-gray-400"])} />}
Expand All @@ -61,14 +77,14 @@ export function Search() {
placeholder="Search anything..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={onFocus}
onBlur={onBlur}
onFocus={handleFocus}
onBlur={handleBlur}
className={cn([
"text-sm",
"w-full pl-9 py-2",
"w-full pl-9 h-full",
query ? "pr-9" : "pr-4",
"rounded-lg bg-gray-100 border-0",
"focus:outline-none focus:bg-gray-200",
"rounded-lg bg-gray-100 border border-transparent",
"focus:outline-none focus:bg-gray-200 focus:border-black",
])}
/>
{query && (
Expand Down
9 changes: 4 additions & 5 deletions apps/desktop2/src/components/main/body/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ export function TabItemBase(
className={clsx([
"flex items-center gap-2 cursor-pointer group",
"min-w-[100px] max-w-[200px] h-full px-2",
active
? "bg-background text-foreground rounded-lg border"
: "bg-muted/50 hover:bg-muted text-muted-foreground rounded-lg border",
"bg-color1 rounded-lg border",
active ? "text-black border-black" : "text-color3 border-transparent",
])}
>
<div className="flex flex-row items-center gap-1 text-sm flex-1 min-w-0">
Expand All @@ -42,8 +41,8 @@ export function TabItemBase(
className={clsx([
"text-xs flex-shrink-0 transition-opacity",
active
? "text-muted-foreground hover:text-foreground"
: "opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-foreground",
? "text-color4"
: "opacity-0 group-hover:opacity-100 text-color3",
])}
>
Expand Down
Loading
Loading