import { Spinner, SpinnerSize, TooltipHost } from "@fluentui/react";
import { CollectionTabKind } from "Contracts/ViewModels";
import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
import { FabricHomeScreen } from "Explorer/SplashScreen/FabricHome";
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab";
import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab";
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { userContext } from "UserContext";
import { useTeachingBubble } from "hooks/useTeachingBubble";
import ko from "knockout";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import errorQuery from "../../../images/error_no_outline.svg";
import warningIconSvg from "../../../images/warning.svg";
import { useObservable } from "../../hooks/useObservable";
import { ReactTabKind, useTabs } from "../../hooks/useTabs";
import TabsBase from "./TabsBase";
type Tab = TabsBase | (TabsBase & { render: () => JSX.Element });
interface TabsProps {
explorer: Explorer;
}
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS);
useEffect(() => {
setKeyboardHandlers({
[KeyboardAction.SELECT_LEFT_TAB]: () => useTabs.getState().selectLeftTab(),
[KeyboardAction.SELECT_RIGHT_TAB]: () => useTabs.getState().selectRightTab(),
[KeyboardAction.CLOSE_TAB]: () => useTabs.getState().closeActiveTab(),
});
}, [setKeyboardHandlers]);
// Add useEffect to handle context buttons
useEffect(() => {
if (activeReactTab !== undefined) {
// React tabs have no context buttons
useCommandBar.getState().setContextButtons([]);
}
}, [activeReactTab]);
return (
{openedReactTabs.map((tab) => (
))}
{openedTabs.map((tab) => (
))}
{activeReactTab !== undefined && getReactTabContent(activeReactTab, explorer)}
{openedTabs.map((tab) => (
))}
);
};
function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?: ReactTabKind }) {
const [hovering, setHovering] = useState(false);
const focusTab = useRef() as MutableRefObject;
const tabId = tab ? tab.tabId : "";
const getReactTabTitle = (): ko.Observable => {
if (tabKind === ReactTabKind.QueryCopilot) {
return ko.observable("Query");
}
return ko.observable(ReactTabKind[tabKind]);
};
const tabTitle = useObservable(tab?.tabTitle || getReactTabTitle());
useEffect(() => {
if (active && focusTab.current) {
focusTab.current.focus();
}
}, [active]);
return (
setHovering(true)}
onMouseLeave={() => setHovering(false)}
className={active ? "active tabList" : "tabList"}
style={active ? { fontWeight: "bolder" } : {}}
role="presentation"
>
{
if (tab) {
tab.onTabClick();
} else if (tabKind !== undefined) {
useTabs.getState().activateReactTab(tabKind);
}
}}
onKeyPress={({ nativeEvent: e }) => {
if (tab) {
tab.onKeyPressActivate(undefined, e);
} else if (tabKind !== undefined) {
onKeyPressReactTab(e, tabKind);
}
}}
aria-selected={active}
aria-expanded={active}
aria-controls={tabId}
tabIndex={0}
role="tab"
ref={focusTab}
>
{useObservable(tab?.isExecutionError || ko.observable(false)) && (
)}
{useObservable(tab?.isExecutionWarning || ko.observable(false)) && (
)}
{isTabExecuting(tab, tabKind) && (
)}
{isQueryErrorThrown(tab, tabKind) && (
)}
{tabTitle}
);
}
const onKeyPressReactTabClose = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
if (e.key === "Enter" || e.code === "Space") {
useTabs.getState().closeReactTab(tabKind);
e.stopPropagation();
}
};
const CloseButton = ({
tab,
active,
hovering,
tabKind,
ariaLabel,
}: {
tab: Tab;
active: boolean;
hovering: boolean;
tabKind?: ReactTabKind;
ariaLabel: string;
}) => (
) => {
event.stopPropagation();
tab ? tab.onCloseTabButtonClick() : useTabs.getState().closeReactTab(tabKind);
}}
tabIndex={active ? 0 : undefined}
onKeyPress={({ nativeEvent: e }) =>
tab ? tab.onKeyPressClose(undefined, e) : onKeyPressReactTabClose(e, tabKind)
}
>
);
const ErrorIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
tab.onErrorDetailsClick(undefined, e)}
onKeyPress={({ nativeEvent: e }) => tab.onErrorDetailsKeyPress(undefined, e)}
>
);
const WarningIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
tab.onErrorDetailsClick(undefined, e)}
onKeyPress={({ nativeEvent: e }) => tab.onErrorDetailsKeyPress(undefined, e)}
>
);
function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
const ref = useRef();
const attrs = {
style: { display: active ? undefined : "none" },
className: "tabs-container",
};
useEffect((): (() => void) | void => {
if (tab.tabKind === CollectionTabKind.Documents && tab.collection?.isSampleCollection) {
useTeachingBubble.getState().setIsDocumentsTabOpened(true);
}
const { current: element } = ref;
if (element) {
ko.applyBindings(tab, element);
const ctx = ko.contextFor(element).createChildContext(tab);
ko.applyBindingsToDescendants(ctx, element);
tab.isTemplateReady(true);
return () => ko.cleanNode(element);
}
}, [ref, tab]);
if (tab) {
if ("render" in tab) {
return (
{tab.render()}
);
}
}
return ;
}
const onKeyPressReactTab = (e: KeyboardEvent, tabKind: ReactTabKind): void => {
if (e.key === "Enter" || e.code === "Space") {
useTabs.getState().activateReactTab(tabKind);
e.stopPropagation();
}
};
const isTabExecuting = (tab?: Tab, tabKind?: ReactTabKind): boolean => {
if (useObservable(tab?.isExecuting || ko.observable(false))) {
return true;
} else if (tabKind !== undefined && tabKind !== ReactTabKind.Home && useTabs.getState()?.isTabExecuting) {
return true;
}
return false;
};
const isQueryErrorThrown = (tab?: Tab, tabKind?: ReactTabKind): boolean => {
if (
!tab?.isExecuting &&
tabKind !== undefined &&
tabKind !== ReactTabKind.Home &&
useTabs.getState()?.isQueryErrorThrown &&
!useTabs.getState()?.isTabExecuting
) {
return true;
}
return false;
};
const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): JSX.Element => {
switch (activeReactTab) {
case ReactTabKind.Connect:
return userContext.apiType === "VCoreMongo" ? (
) : userContext.apiType === "Postgres" ? (
) : (
);
case ReactTabKind.Home:
if (isFabricNative()) {
return ;
} else {
return ;
}
case ReactTabKind.Quickstart:
return userContext.apiType === "VCoreMongo" ? (
) : (
);
case ReactTabKind.QueryCopilot:
return ;
default:
throw new Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
}
};