diff --git a/apps/desktop/src/components/main/body/ai.tsx b/apps/desktop/src/components/main/body/ai.tsx index ca5841cb51..79d4fbf264 100644 --- a/apps/desktop/src/components/main/body/ai.tsx +++ b/apps/desktop/src/components/main/body/ai.tsx @@ -19,6 +19,8 @@ export const TabItemAI: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const suffix = tab.state.tab === "transcription" ? "STT" : "LLM"; @@ -32,11 +34,14 @@ export const TabItemAI: TabItem> = ({ } selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/calendar/index.tsx b/apps/desktop/src/components/main/body/calendar/index.tsx index 67efa70b89..c1c509d469 100644 --- a/apps/desktop/src/components/main/body/calendar/index.tsx +++ b/apps/desktop/src/components/main/body/calendar/index.tsx @@ -12,17 +12,22 @@ export const TabItemCalendar: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Calendar"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/changelog/index.tsx b/apps/desktop/src/components/main/body/changelog/index.tsx index 96cae9b9b7..1e3c17266b 100644 --- a/apps/desktop/src/components/main/body/changelog/index.tsx +++ b/apps/desktop/src/components/main/body/changelog/index.tsx @@ -16,16 +16,21 @@ export const TabItemChangelog: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => ( } title="What's New" selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); diff --git a/apps/desktop/src/components/main/body/chat-shortcuts/index.tsx b/apps/desktop/src/components/main/body/chat-shortcuts/index.tsx index 8ef06cfab0..8ca123043b 100644 --- a/apps/desktop/src/components/main/body/chat-shortcuts/index.tsx +++ b/apps/desktop/src/components/main/body/chat-shortcuts/index.tsx @@ -27,17 +27,22 @@ export const TabItemChatShortcut: TabItem< handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title="Shortcuts" selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/contacts/index.tsx b/apps/desktop/src/components/main/body/contacts/index.tsx index 66da75321f..057c04565b 100644 --- a/apps/desktop/src/components/main/body/contacts/index.tsx +++ b/apps/desktop/src/components/main/body/contacts/index.tsx @@ -24,17 +24,22 @@ export const TabItemContact: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Contacts"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/data.tsx b/apps/desktop/src/components/main/body/data.tsx index 1d3eed4893..14b70da29e 100644 --- a/apps/desktop/src/components/main/body/data.tsx +++ b/apps/desktop/src/components/main/body/data.tsx @@ -19,6 +19,8 @@ export const TabItemData: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const suffix = tab.state.tab === "import" ? "Import" : "Export"; @@ -32,11 +34,14 @@ export const TabItemData: TabItem> = ({ } selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/empty/index.tsx b/apps/desktop/src/components/main/body/empty/index.tsx index 328df89c43..c5a7509739 100644 --- a/apps/desktop/src/components/main/body/empty/index.tsx +++ b/apps/desktop/src/components/main/body/empty/index.tsx @@ -16,17 +16,22 @@ export const TabItemEmpty: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title="New tab" selected={tab.active} + allowPin={false} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/events.tsx b/apps/desktop/src/components/main/body/events.tsx index 73e2628a2f..ee22841649 100644 --- a/apps/desktop/src/components/main/body/events.tsx +++ b/apps/desktop/src/components/main/body/events.tsx @@ -12,6 +12,8 @@ export const TabItemEvent: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const title = main.UI.useCell( "events", @@ -25,11 +27,14 @@ export const TabItemEvent: TabItem> = ({ icon={} title={title ?? ""} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/extensions/index.tsx b/apps/desktop/src/components/main/body/extensions/index.tsx index 0f0551e855..b324f6e2b6 100644 --- a/apps/desktop/src/components/main/body/extensions/index.tsx +++ b/apps/desktop/src/components/main/body/extensions/index.tsx @@ -1,9 +1,7 @@ import { convertFileSrc } from "@tauri-apps/api/core"; -import { AlertTriangleIcon, BlocksIcon, PuzzleIcon, XIcon } from "lucide-react"; -import { Reorder, useDragControls } from "motion/react"; +import { AlertTriangleIcon, BlocksIcon, PuzzleIcon } from "lucide-react"; import { Component, - type PointerEvent, type ReactNode, useCallback, useEffect, @@ -14,19 +12,11 @@ import type { MergeableStore } from "tinybase"; import { useStores } from "tinybase/ui-react"; import { Button } from "@hypr/ui/components/ui/button"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuSeparator, - ContextMenuTrigger, -} from "@hypr/ui/components/ui/context-menu"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@hypr/ui/components/ui/resizable"; -import { cn } from "@hypr/utils"; import { createIframeSynchronizer } from "../../../../store/tinybase/iframe-sync"; import { type Store, STORE_ID } from "../../../../store/tinybase/main"; @@ -47,17 +37,22 @@ export const TabItemExtensions: TabItem = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Extensions"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; @@ -171,6 +166,8 @@ export function TabItemExtension({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }: { tab: ExtensionTab; tabIndex?: number; @@ -178,58 +175,23 @@ export function TabItemExtension({ handleSelectThis: (tab: Tab) => void; handleCloseOthers: () => void; handleCloseAll: () => void; + handlePinThis: () => void; + handleUnpinThis: () => void; }) { - const controls = useDragControls(); - return ( - - - handleSelectThis(tab)} - onPointerDown={(e: PointerEvent) => controls.start(e)} - > - - - {tab.extensionId} - - {tabIndex && ( - - {tabIndex} - - )} - - - - - handleCloseThis(tab)}> - Close - - - Close Others - - - Close All - - + } + title={tab.extensionId} + selected={tab.active} + pinned={tab.pinned} + tabIndex={tabIndex} + handleCloseThis={() => handleCloseThis(tab)} + handleSelectThis={() => handleSelectThis(tab)} + handleCloseOthers={handleCloseOthers} + handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} + /> ); } diff --git a/apps/desktop/src/components/main/body/folders/index.tsx b/apps/desktop/src/components/main/body/folders/index.tsx index ee4c7e29ee..7ed51dcd53 100644 --- a/apps/desktop/src/components/main/body/folders/index.tsx +++ b/apps/desktop/src/components/main/body/folders/index.tsx @@ -36,17 +36,22 @@ const TabItemFolderAll: TabItem> = ({ handleSelectThis, handleCloseAll, handleCloseOthers, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Folders"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; @@ -58,6 +63,8 @@ const TabItemFolderSpecific: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const folderId = tab.id!; const folders = useFolderChain(folderId); @@ -71,11 +78,14 @@ const TabItemFolderSpecific: TabItem> = ({ icon={} title={title} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/humans.tsx b/apps/desktop/src/components/main/body/humans.tsx index dd43b2a179..9bbc1e498c 100644 --- a/apps/desktop/src/components/main/body/humans.tsx +++ b/apps/desktop/src/components/main/body/humans.tsx @@ -12,6 +12,8 @@ export const TabItemHuman: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const title = main.UI.useCell("humans", tab.id, "name", main.STORE_ID); @@ -20,11 +22,14 @@ export const TabItemHuman: TabItem> = ({ icon={} title={title ?? "Human"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/index.tsx b/apps/desktop/src/components/main/body/index.tsx index ec240a7554..a6c4ef7cd8 100644 --- a/apps/desktop/src/components/main/body/index.tsx +++ b/apps/desktop/src/components/main/body/index.tsx @@ -86,6 +86,8 @@ function Header({ tabs }: { tabs: Tab[] }) { canGoNext, closeOthers, closeAll, + pin, + unpin, } = useTabs( useShallow((state) => ({ select: state.select, @@ -97,6 +99,8 @@ function Header({ tabs }: { tabs: Tab[] }) { canGoNext: state.canGoNext, closeOthers: state.closeOthers, closeAll: state.closeAll, + pin: state.pin, + unpin: state.unpin, })), ); const tabsScrollContainerRef = useRef(null); @@ -187,6 +191,8 @@ function Header({ tabs }: { tabs: Tab[] }) { handleSelect={select} handleCloseOthersCallback={closeOthers} handleCloseAll={closeAll} + handlePin={pin} + handleUnpin={unpin} tabIndex={shortcutIndex} /> @@ -236,6 +242,8 @@ function TabItem({ handleSelect, handleCloseOthersCallback, handleCloseAll, + handlePin, + handleUnpin, tabIndex, }: { tab: Tab; @@ -243,9 +251,13 @@ function TabItem({ handleSelect: (tab: Tab) => void; handleCloseOthersCallback: (tab: Tab) => void; handleCloseAll: () => void; + handlePin: (tab: Tab) => void; + handleUnpin: (tab: Tab) => void; tabIndex?: number; }) { const handleCloseOthers = () => handleCloseOthersCallback(tab); + const handlePinThis = () => handlePin(tab); + const handleUnpinThis = () => handleUnpin(tab); if (tab.type === "sessions") { return ( @@ -256,6 +268,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -268,6 +282,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -280,6 +296,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -292,6 +310,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -304,6 +324,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -316,6 +338,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -328,6 +352,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -340,6 +366,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -352,6 +380,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -364,6 +394,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -376,6 +408,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -388,6 +422,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -400,6 +436,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -412,6 +450,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -424,6 +464,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -436,6 +478,8 @@ function TabItem({ handleSelectThis={handleSelect} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={handlePinThis} + handleUnpinThis={handleUnpinThis} /> ); } @@ -614,12 +658,13 @@ function useScrollActiveTabIntoView(tabs: Tab[]) { } function useTabsShortcuts() { - const { tabs, currentTab, close, select } = useTabs( + const { tabs, currentTab, close, select, unpin } = useTabs( useShallow((state) => ({ tabs: state.tabs, currentTab: state.currentTab, close: state.close, select: state.select, + unpin: state.unpin, })), ); const newNote = useNewNote({ behavior: "new" }); @@ -658,7 +703,11 @@ function useTabsShortcuts() { "mod+w", async () => { if (currentTab) { - close(currentTab); + if (currentTab.pinned) { + unpin(currentTab); + } else { + close(currentTab); + } } }, { @@ -666,7 +715,7 @@ function useTabsShortcuts() { enableOnFormTags: true, enableOnContentEditable: true, }, - [currentTab, close], + [currentTab, close, unpin], ); useHotkeys( diff --git a/apps/desktop/src/components/main/body/prompts/index.tsx b/apps/desktop/src/components/main/body/prompts/index.tsx index 2a1b4856d7..667177ce69 100644 --- a/apps/desktop/src/components/main/body/prompts/index.tsx +++ b/apps/desktop/src/components/main/body/prompts/index.tsx @@ -21,17 +21,22 @@ export const TabItemPrompt: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Prompts"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/sessions/index.tsx b/apps/desktop/src/components/main/body/sessions/index.tsx index e2ced0f69c..b796d7c292 100644 --- a/apps/desktop/src/components/main/body/sessions/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/index.tsx @@ -28,6 +28,8 @@ export const TabItemNote: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { const title = main.UI.useCell( "sessions", @@ -49,11 +51,14 @@ export const TabItemNote: TabItem> = ({ selected={tab.active} active={isActive} finalizing={showSpinner} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/settings.tsx b/apps/desktop/src/components/main/body/settings.tsx index 4001b7bb2b..923fd9804c 100644 --- a/apps/desktop/src/components/main/body/settings.tsx +++ b/apps/desktop/src/components/main/body/settings.tsx @@ -12,17 +12,22 @@ export const TabItemSettings: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Settings"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/components/main/body/shared.tsx b/apps/desktop/src/components/main/body/shared.tsx index 2671935622..c23a6ba417 100644 --- a/apps/desktop/src/components/main/body/shared.tsx +++ b/apps/desktop/src/components/main/body/shared.tsx @@ -1,4 +1,4 @@ -import { X } from "lucide-react"; +import { Pin, X } from "lucide-react"; import { useState } from "react"; import { ContextMenuItem } from "@hypr/ui/components/ui/context-menu"; @@ -15,6 +15,8 @@ type TabItemProps = { tab: T; tabIndex?: number } & { handleCloseThis: (tab: T) => void; handleCloseOthers: () => void; handleCloseAll: () => void; + handlePinThis: (tab: T) => void; + handleUnpinThis: (tab: T) => void; }; type TabItemBaseProps = { @@ -23,12 +25,16 @@ type TabItemBaseProps = { selected: boolean; active?: boolean; finalizing?: boolean; + pinned?: boolean; + allowPin?: boolean; tabIndex?: number; } & { handleCloseThis: () => void; handleSelectThis: () => void; handleCloseOthers: () => void; handleCloseAll: () => void; + handlePinThis: () => void; + handleUnpinThis: () => void; }; export type TabItem = ( @@ -41,11 +47,15 @@ export function TabItemBase({ selected, active = false, finalizing = false, + pinned = false, + allowPin = true, tabIndex, handleCloseThis, handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }: TabItemBaseProps) { const isCmdPressed = useCmdKeyPressed(); const [isHovered, setIsHovered] = useState(false); @@ -60,6 +70,12 @@ export function TabItemBase({ const contextMenu = !active ? ( <> + {allowPin && + (pinned ? ( + unpin tab + ) : ( + pin tab + ))} close tab close others @@ -114,6 +130,20 @@ export function TabItemBase({
+ ) : pinned ? ( + ) : ( icon )} diff --git a/apps/desktop/src/components/main/body/templates/index.tsx b/apps/desktop/src/components/main/body/templates/index.tsx index 485cfc3146..50830dde3b 100644 --- a/apps/desktop/src/components/main/body/templates/index.tsx +++ b/apps/desktop/src/components/main/body/templates/index.tsx @@ -26,17 +26,22 @@ export const TabItemTemplate: TabItem> = ({ handleSelectThis, handleCloseOthers, handleCloseAll, + handlePinThis, + handleUnpinThis, }) => { return ( } title={"Templates"} selected={tab.active} + pinned={tab.pinned} tabIndex={tabIndex} handleCloseThis={() => handleCloseThis(tab)} handleSelectThis={() => handleSelectThis(tab)} handleCloseOthers={handleCloseOthers} handleCloseAll={handleCloseAll} + handlePinThis={() => handlePinThis(tab)} + handleUnpinThis={() => handleUnpinThis(tab)} /> ); }; diff --git a/apps/desktop/src/store/zustand/tabs/basic.ts b/apps/desktop/src/store/zustand/tabs/basic.ts index ac6b1ea79e..26c824c06b 100644 --- a/apps/desktop/src/store/zustand/tabs/basic.ts +++ b/apps/desktop/src/store/zustand/tabs/basic.ts @@ -19,6 +19,8 @@ export type BasicActions = { reorder: (tabs: Tab[]) => void; closeOthers: (tab: Tab) => void; closeAll: () => void; + pin: (tab: Tab) => void; + unpin: (tab: Tab) => void; }; export const createBasicSlice = < @@ -31,7 +33,12 @@ export const createBasicSlice = < currentTab: null, openCurrent: (tab) => { const { tabs, history } = get(); - set(openTab(tabs, tab, history, true)); + const currentActiveTab = tabs.find((t) => t.active); + if (currentActiveTab?.pinned) { + set(openTab(tabs, tab, history, false)); + } else { + set(openTab(tabs, tab, history, true)); + } }, openNew: (tab) => { const { tabs, history } = get(); @@ -118,6 +125,34 @@ export const createBasicSlice = < canGoNext: false, } as unknown as Partial); }, + pin: (tab) => { + const { tabs } = get(); + const tabIndex = tabs.findIndex((t) => isSameTab(t, tab)); + if (tabIndex === -1) return; + + const pinnedTab = { ...tabs[tabIndex], pinned: true }; + const pinnedCount = tabs.filter((t) => t.pinned).length; + + const nextTabs = [...tabs.slice(0, tabIndex), ...tabs.slice(tabIndex + 1)]; + nextTabs.splice(pinnedCount, 0, pinnedTab); + + const currentTab = nextTabs.find((t) => t.active) || null; + set({ tabs: nextTabs, currentTab } as Partial); + }, + unpin: (tab) => { + const { tabs } = get(); + const tabIndex = tabs.findIndex((t) => isSameTab(t, tab)); + if (tabIndex === -1) return; + + const unpinnedTab = { ...tabs[tabIndex], pinned: false }; + const pinnedCount = tabs.filter((t) => t.pinned).length; + + const nextTabs = [...tabs.slice(0, tabIndex), ...tabs.slice(tabIndex + 1)]; + nextTabs.splice(pinnedCount - 1, 0, unpinnedTab); + + const currentTab = nextTabs.find((t) => t.active) || null; + set({ tabs: nextTabs, currentTab } as Partial); + }, }); const removeDuplicates = (tabs: Tab[], newTab: Tab): Tab[] => { diff --git a/apps/desktop/src/store/zustand/tabs/schema.ts b/apps/desktop/src/store/zustand/tabs/schema.ts index aaf3ee466a..14dcedd462 100644 --- a/apps/desktop/src/store/zustand/tabs/schema.ts +++ b/apps/desktop/src/store/zustand/tabs/schema.ts @@ -42,6 +42,7 @@ export const isTranscriptView = ( type BaseTab = { active: boolean; slotId: string; + pinned: boolean; }; export type Tab = @@ -96,7 +97,7 @@ export type Tab = }); export const getDefaultState = (tab: TabInput): Tab => { - const base = { active: false, slotId: "" }; + const base = { active: false, slotId: "", pinned: false }; switch (tab.type) { case "sessions": diff --git a/apps/desktop/src/store/zustand/tabs/test-utils.ts b/apps/desktop/src/store/zustand/tabs/test-utils.ts index d77efee99c..207ead3f71 100644 --- a/apps/desktop/src/store/zustand/tabs/test-utils.ts +++ b/apps/desktop/src/store/zustand/tabs/test-utils.ts @@ -19,6 +19,7 @@ export const createSessionTab = ( type: "sessions", id: overrides.id ?? id(), active: overrides.active ?? false, + pinned: overrides.pinned ?? false, slotId: id(), state: { editor: null, @@ -31,6 +32,7 @@ export const createContactsTab = ( ): ContactsTab => ({ type: "contacts", active: overrides.active ?? false, + pinned: overrides.pinned ?? false, slotId: id(), state: { selectedOrganization: null,