|
1 | 1 | // Copyright 2026, Command Line Inc. |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
| 4 | +import { Tooltip } from "@/app/element/tooltip"; |
4 | 5 | import { getTabBadgeAtom } from "@/app/store/badge"; |
5 | 6 | import { makeORef } from "@/app/store/wos"; |
6 | 7 | import { TabRpcClient } from "@/app/store/wshrpcutil"; |
7 | 8 | import { useWaveEnv } from "@/app/waveenv/waveenv"; |
| 9 | +import { WorkspaceLayoutModel } from "@/app/workspace/workspace-layout-model"; |
8 | 10 | import { validateCssColor } from "@/util/color-validator"; |
9 | 11 | import { cn, fireAndForget } from "@/util/util"; |
10 | 12 | import { useAtomValue } from "jotai"; |
11 | | -import { useCallback, useEffect, useRef, useState } from "react"; |
12 | | -import { buildTabContextMenu } from "./tabcontextmenu"; |
| 13 | +import { memo, useCallback, useEffect, useRef, useState } from "react"; |
| 14 | +import { buildTabBarContextMenu, buildTabContextMenu } from "./tabcontextmenu"; |
| 15 | +import { UpdateStatusBanner } from "./updatebanner"; |
13 | 16 | import { VTab, VTabItem } from "./vtab"; |
14 | 17 | import { VTabBarEnv } from "./vtabbarenv"; |
| 18 | +import { WorkspaceSwitcher } from "./workspaceswitcher"; |
15 | 19 | export type { VTabItem } from "./vtab"; |
16 | 20 |
|
| 21 | +const VTabBarAIButton = memo(() => { |
| 22 | + const env = useWaveEnv<VTabBarEnv>(); |
| 23 | + const aiPanelOpen = useAtomValue(WorkspaceLayoutModel.getInstance().panelVisibleAtom); |
| 24 | + const hideAiButton = useAtomValue(env.getSettingsKeyAtom("app:hideaibutton")); |
| 25 | + |
| 26 | + const onClick = () => { |
| 27 | + const currentVisible = WorkspaceLayoutModel.getInstance().getAIPanelVisible(); |
| 28 | + WorkspaceLayoutModel.getInstance().setAIPanelVisible(!currentVisible); |
| 29 | + }; |
| 30 | + |
| 31 | + if (hideAiButton) { |
| 32 | + return null; |
| 33 | + } |
| 34 | + |
| 35 | + return ( |
| 36 | + <Tooltip |
| 37 | + content="Toggle Wave AI Panel" |
| 38 | + placement="bottom" |
| 39 | + hideOnClick |
| 40 | + divClassName={`flex h-[22px] px-3.5 justify-end mb-1 items-center rounded-md mr-1 box-border cursor-pointer bg-hover hover:bg-hoverbg transition-colors text-[12px] ${aiPanelOpen ? "text-accent" : "text-secondary"}`} |
| 41 | + divStyle={{ WebkitAppRegion: "no-drag" } as React.CSSProperties} |
| 42 | + divOnClick={onClick} |
| 43 | + > |
| 44 | + <i className="fa fa-sparkles" /> |
| 45 | + </Tooltip> |
| 46 | + ); |
| 47 | +}); |
| 48 | +VTabBarAIButton.displayName = "VTabBarAIButton"; |
| 49 | + |
| 50 | +const MacOSHeader = memo(() => { |
| 51 | + const env = useWaveEnv<VTabBarEnv>(); |
| 52 | + const isFullScreen = useAtomValue(env.atoms.isFullScreen); |
| 53 | + return ( |
| 54 | + <> |
| 55 | + {!isFullScreen && ( |
| 56 | + <div |
| 57 | + className="w-full shrink-0" |
| 58 | + style={ |
| 59 | + { |
| 60 | + height: "calc(25px * var(--zoomfactor-inv))", |
| 61 | + WebkitAppRegion: "drag", |
| 62 | + } as React.CSSProperties |
| 63 | + } |
| 64 | + /> |
| 65 | + )} |
| 66 | + <div |
| 67 | + className="flex shrink-0 flex-row flex-wrap items-end px-1 pb-1 pl-2" |
| 68 | + style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties} |
| 69 | + > |
| 70 | + <VTabBarAIButton /> |
| 71 | + <Tooltip content="Workspace Switcher" placement="bottom" hideOnClick divClassName="flex items-center"> |
| 72 | + <WorkspaceSwitcher /> |
| 73 | + </Tooltip> |
| 74 | + <UpdateStatusBanner /> |
| 75 | + </div> |
| 76 | + </> |
| 77 | + ); |
| 78 | +}); |
| 79 | +MacOSHeader.displayName = "MacOSHeader"; |
| 80 | + |
17 | 81 | interface VTabBarProps { |
18 | 82 | workspace: Workspace; |
19 | 83 | className?: string; |
@@ -79,6 +143,7 @@ function VTabWrapper({ |
79 | 143 | const handleContextMenu = useCallback( |
80 | 144 | (e: React.MouseEvent<HTMLDivElement>) => { |
81 | 145 | e.preventDefault(); |
| 146 | + e.stopPropagation(); |
82 | 147 | const menu = buildTabContextMenu(tabId, renameRef, () => onClose(), env); |
83 | 148 | env.showContextMenu(menu, e); |
84 | 149 | }, |
@@ -238,11 +303,22 @@ export function VTabBar({ workspace, className }: VTabBarProps) { |
238 | 303 | fireAndForget(() => env.rpc.UpdateWorkspaceTabIdsCommand(TabRpcClient, workspace.oid, nextTabIds)); |
239 | 304 | }; |
240 | 305 |
|
| 306 | + const handleTabBarContextMenu = useCallback( |
| 307 | + (e: React.MouseEvent<HTMLDivElement>) => { |
| 308 | + e.preventDefault(); |
| 309 | + const menu = buildTabBarContextMenu(env); |
| 310 | + env.showContextMenu(menu, e); |
| 311 | + }, |
| 312 | + [env] |
| 313 | + ); |
| 314 | + |
241 | 315 | return ( |
242 | 316 | <div |
243 | 317 | className={cn("flex h-full flex-col overflow-hidden", className)} |
244 | 318 | style={{ backdropFilter: "blur(20px)", background: "rgba(0, 0, 0, 0.35)" }} |
| 319 | + onContextMenu={handleTabBarContextMenu} |
245 | 320 | > |
| 321 | + {env.isMacOS() && <MacOSHeader />} |
246 | 322 | <div |
247 | 323 | ref={scrollContainerRef} |
248 | 324 | className="relative flex min-h-0 flex-col overflow-y-auto" |
|
0 commit comments