Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
16 changes: 14 additions & 2 deletions apps/desktop2/src/components/interactive-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface InteractiveButtonProps {
children: ReactNode;
onClick?: () => void;
onCmdClick?: () => void;
onMouseDown?: (e: MouseEvent<HTMLElement>) => void;
contextMenu?: ReactNode;
className?: string;
disabled?: boolean;
Expand All @@ -15,6 +16,7 @@ export function InteractiveButton({
children,
onClick,
onCmdClick,
onMouseDown,
contextMenu,
className,
disabled,
Expand All @@ -40,7 +42,12 @@ export function InteractiveButton({

if (!contextMenu) {
return (
<Element onClick={handleClick} className={className} disabled={!asChild ? disabled : undefined}>
<Element
onClick={handleClick}
onMouseDown={onMouseDown}
className={className}
disabled={!asChild ? disabled : undefined}
>
{children}
</Element>
);
Expand All @@ -49,7 +56,12 @@ export function InteractiveButton({
return (
<ContextMenu>
<ContextMenuTrigger asChild={asChild}>
<Element onClick={handleClick} className={className} disabled={!asChild ? disabled : undefined}>
<Element
onClick={handleClick}
onMouseDown={onMouseDown}
className={className}
disabled={!asChild ? disabled : undefined}
>
{children}
</Element>
</ContextMenuTrigger>
Expand Down
127 changes: 66 additions & 61 deletions apps/desktop2/src/components/main/body/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/ui/lib/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 { cn } from "@hypr/ui/lib/utils";
import { useShell } from "../../../contexts/shell";
import { type Tab, uniqueIdfromTab, useTabs } from "../../../store/zustand/tabs";
import { id } from "../../../utils";
Expand All @@ -23,6 +25,7 @@ export function Body() {

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

if (!currentTab) {
return null;
Expand Down Expand Up @@ -62,70 +65,43 @@ function Header({ tabs }: { tabs: Tab[] }) {
return (
<div
className={cn([
"w-full h-9 flex items-end",
"w-full h-9 flex items-center",
!leftsidebar.expanded && "pl-[72px]",
])}
>
{!leftsidebar.expanded && (
<div className="flex items-center justify-center h-full px-3 shrink-0 bg-white z-20">
<Button size="icon" variant="ghost" onClick={() => leftsidebar.setExpanded(true)}>
<PanelLeftOpenIcon
className="h-5 w-5 cursor-pointer"
onClick={() => leftsidebar.setExpanded(true)}
size={16}
/>
</div>
</Button>
)}

<div className="flex items-center h-full shrink-0">
<button
<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",
])}
variant="ghost"
size="icon"
>
<ArrowLeftIcon
className={cn([
"h-4 w-4",
canGoBack && ["text-black/70", "cursor-pointer", "group-hover:text-black"],
!canGoBack && ["text-black/30", "cursor-not-allowed"],
])}
/>
</button>
<button
<ArrowLeftIcon size={16} />
</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",
])}
variant="ghost"
size="icon"
>
<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>
<ArrowRightIcon size={16} />
</Button>
</div>

<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",
"w-fit overflow-x-auto overflow-y-hidden h-full",
])}
>
<Reorder.Group
Expand Down Expand Up @@ -158,22 +134,21 @@ function Header({ tabs }: { tabs: Tab[] }) {
</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",
])}
<div
data-tauri-drag-region
className="flex-1 flex h-full items-center justify-between"
>
<PlusIcon className="h-4 w-4 text-color3 cursor-pointer" />
</button>
<Button
onClick={handleNewNote}
variant="ghost"
size="icon"
className="text-color3"
>
<PlusIcon size={16} />
</Button>

<Search />
<Search />
</div>
</div>
);
}
Expand Down Expand Up @@ -303,8 +278,8 @@ export function StandardTabWrapper(
{ children, afterBorder }: { children: React.ReactNode; afterBorder?: React.ReactNode },
) {
return (
<div className="flex flex-col h-full gap-1">
<div className="flex flex-col px-4 py-1 rounded-lg border flex-1 overflow-hidden relative">
<div className="flex flex-col h-full">
<div className="flex flex-col p-2 rounded-lg border flex-1 overflow-hidden relative">
{children}
<TabChatButton />
</div>
Expand All @@ -328,7 +303,7 @@ const useTabCloseHotkey = () => {
await appWindow.close();
}
},
{ enableOnFormTags: true },
{ enableOnFormTags: true, enableOnContentEditable: true },
[tabs, currentTab, close],
);
};
Expand All @@ -353,11 +328,41 @@ const useTabSelectHotkeys = () => {
event.preventDefault();
select(target);
},
{ enableOnFormTags: true },
{ 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
12 changes: 7 additions & 5 deletions apps/desktop2/src/components/main/body/sessions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ export function TabContentNote({ tab }: { tab: Extract<Tab, { type: "sessions" }
return (
<AudioPlayer.Provider url="/assets/audio.wav">
<StandardTabWrapper afterBorder={tab.state.editor === "transcript" && <AudioPlayer.Timeline />}>
<div className="mt-0.5 mb-2">
<OuterHeader sessionId={tab.id} />
<OuterHeader sessionId={tab.id} />
<div className="mt-3 px-2">
<TitleInput tab={tab} />
<div className="mt-2">
<NoteInput tab={tab} />
</div>
<FloatingActionButton tab={tab} />
</div>
<TitleInput tab={tab} />
<NoteInput tab={tab} />
<FloatingActionButton tab={tab} />
</StandardTabWrapper>
</AudioPlayer.Provider>
);
Expand Down
102 changes: 102 additions & 0 deletions apps/desktop2/src/components/main/body/sessions/inner-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useEffect } from "react";

import { cn } from "@hypr/ui/lib/utils";
import { type Tab, useTabs } from "../../../../store/zustand/tabs";

interface TabHeaderProps {
tab: Tab;
onVisibilityChange?: (isVisible: boolean) => void;
isCurrentlyRecording: boolean;
shouldShowTab: boolean;
shouldShowEnhancedTab: boolean;
}

export const InnerHeader = ({
tab,
onVisibilityChange,
isCurrentlyRecording,
shouldShowTab,
shouldShowEnhancedTab,
}: TabHeaderProps) => {
const { updateSessionTabState } = useTabs();

const currentTab = tab.type === "sessions" ? (tab.state.editor ?? "raw") : "raw";

const handleTabChange = (view: "raw" | "enhanced" | "transcript") => {
updateSessionTabState(tab, { editor: view });
};

// set default tab to 'raw' for blank notes (no meeting session)
useEffect(() => {
if (!shouldShowTab && tab.type === "sessions") {
updateSessionTabState(tab, { editor: "raw" });
}
}, [shouldShowTab, tab, updateSessionTabState]);

// notify parent when visibility changes
useEffect(() => {
if (onVisibilityChange) {
onVisibilityChange(shouldShowTab ?? false);
}
}, [shouldShowTab, onVisibilityChange]);

// don't render tabs at all for blank notes (no meeting session)
if (!shouldShowTab) {
return null;
}

return (
<div className="relative">
<div className="bg-white px-2">
<div className="flex">
<div className="flex border-b border-neutral-100 w-full">
{shouldShowEnhancedTab && (
<button
onClick={() => handleTabChange("enhanced")}
className={cn(
"relative px-2 py-2 text-xs pl-1 font-medium transition-all duration-200 border-b-2 -mb-px flex items-center gap-1.5",
currentTab === "enhanced"
? "text-neutral-900 border-neutral-900"
: "text-neutral-600 border-transparent hover:text-neutral-800",
)}
>
Summary
</button>
)}

<button
onClick={() => handleTabChange("raw")}
className={cn(
"relative py-2 text-xs font-medium transition-all duration-200 border-b-2 -mb-px flex items-center gap-1.5",
shouldShowEnhancedTab ? "pl-3 px-4" : "pl-1 px-2",
currentTab === "raw"
? "text-neutral-900 border-neutral-900"
: "text-neutral-600 border-transparent hover:text-neutral-800",
)}
>
Memos
</button>

<button
onClick={() => handleTabChange("transcript")}
className={cn(
"relative px-4 py-2 text-xs pl-3 font-medium transition-all duration-200 border-b-2 -mb-px flex items-center gap-1.5",
currentTab === "transcript"
? "text-neutral-900 border-neutral-900"
: "text-neutral-600 border-transparent hover:text-neutral-800",
)}
>
Transcript
{isCurrentlyRecording && (
<div className="relative h-2 w-2">
<div className="absolute inset-0 rounded-full bg-red-500/30"></div>
<div className="absolute inset-0 rounded-full bg-red-500 animate-ping"></div>
</div>
)}
</button>
</div>
</div>
</div>
</div>
);
};
Loading
Loading