import React, { useState, useRef, forwardRef, useEffect } from "react"; import { Stack, Divider, Menu, Indicator } from "@mantine/core"; import { useTranslation } from 'react-i18next'; import { useNavigate, useLocation } from 'react-router-dom'; import LocalIcon from '@app/components/shared/LocalIcon'; import { useRainbowThemeContext } from "@app/components/shared/RainbowThemeProvider"; import { useFilesModalContext } from '@app/contexts/FilesModalContext'; import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; import { useNavigationState, useNavigationActions } from '@app/contexts/NavigationContext'; import { useSidebarNavigation } from '@app/hooks/useSidebarNavigation'; import { handleUnlessSpecialClick } from '@app/utils/clickHandlers'; import { ButtonConfig } from '@app/types/sidebar'; import '@app/components/shared/quickAccessBar/QuickAccessBar.css'; import { Tooltip } from '@app/components/shared/Tooltip'; import AllToolsNavButton from '@app/components/shared/AllToolsNavButton'; import ActiveToolButton from "@app/components/shared/quickAccessBar/ActiveToolButton"; import AppConfigModal from '@app/components/shared/AppConfigModal'; import { useAppConfig } from '@app/contexts/AppConfigContext'; import { useLicenseAlert } from "@app/hooks/useLicenseAlert"; import { requestStartTour } from '@app/constants/events'; import QuickAccessButton from '@app/components/shared/quickAccessBar/QuickAccessButton'; import { useToursTooltip } from '@app/components/shared/quickAccessBar/useToursTooltip'; import { isNavButtonActive, getNavButtonStyle, getActiveNavButton, } from '@app/components/shared/quickAccessBar/QuickAccessBar'; import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '@app/styles/zIndex'; const QuickAccessBar = forwardRef((_, ref) => { const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const { isRainbowMode } = useRainbowThemeContext(); const { openFilesModal, isFilesModalOpen } = useFilesModalContext(); const { handleReaderToggle, handleToolSelect, selectedToolKey, leftPanelView, toolRegistry, readerMode, resetTool } = useToolWorkflow(); const { hasUnsavedChanges } = useNavigationState(); const { actions: navigationActions } = useNavigationActions(); const { getToolNavigation } = useSidebarNavigation(); const { config } = useAppConfig(); const licenseAlert = useLicenseAlert(); const [configModalOpen, setConfigModalOpen] = useState(false); const [activeButton, setActiveButton] = useState('tools'); const scrollableRef = useRef(null); const { tooltipOpen, manualCloseOnly, showCloseButton, toursMenuOpen, setToursMenuOpen, handleTooltipOpenChange, } = useToursTooltip(); const isRTL = typeof document !== 'undefined' && document.documentElement.dir === 'rtl'; // Open modal if URL is at /settings/* useEffect(() => { const isSettings = location.pathname.startsWith('/settings'); setConfigModalOpen(isSettings); }, [location.pathname]); useEffect(() => { const next = getActiveNavButton(selectedToolKey, readerMode); setActiveButton(next); }, [leftPanelView, selectedToolKey, toolRegistry, readerMode]); const handleFilesButtonClick = () => { openFilesModal(); }; // Helper function to render navigation buttons with URL support const renderNavButton = (config: ButtonConfig, index: number, shouldGuardNavigation = false) => { const isActive = isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView); // Check if this button has URL navigation support const navProps = config.type === 'navigation' && (config.id === 'read' || config.id === 'automate') ? getToolNavigation(config.id) : null; const handleClick = (e?: React.MouseEvent) => { // If there are unsaved changes and this button should guard navigation, show warning modal if (shouldGuardNavigation && hasUnsavedChanges) { e?.preventDefault(); navigationActions.requestNavigation(() => { config.onClick(); }); return; } if (navProps && e) { handleUnlessSpecialClick(e, config.onClick); } else { config.onClick(); } }; const buttonStyle = getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView); // Render navigation button with conditional URL support return (
); }; const mainButtons: ButtonConfig[] = [ { id: 'read', name: t("quickAccess.reader", "Reader"), icon: , size: 'md', isRound: false, type: 'navigation', onClick: () => { setActiveButton('read'); handleReaderToggle(); } }, { id: 'automate', name: t("quickAccess.automate", "Automate"), icon: , size: 'md', isRound: false, type: 'navigation', onClick: () => { setActiveButton('automate'); // If already on automate tool, reset it directly if (selectedToolKey === 'automate') { resetTool('automate'); } else { handleToolSelect('automate'); } } }, ]; const middleButtons: ButtonConfig[] = [ { id: 'files', name: t("quickAccess.files", "Files"), icon: , isRound: true, size: 'md', type: 'modal', onClick: handleFilesButtonClick }, ]; //TODO: Activity //{ // id: 'activity', // name: t("quickAccess.activity", "Activity"), // icon: , // isRound: true, // size: 'lg', // type: 'navigation', // onClick: () => setActiveButton('activity') //}, const bottomButtons: ButtonConfig[] = [ { id: 'help', name: t("quickAccess.tours", "Tours"), icon: , isRound: true, size: 'md', type: 'action', onClick: () => { // This will be overridden by the wrapper logic }, }, { id: 'config', name: t("quickAccess.settings", "Settings"), icon: , size: 'md', type: 'modal', onClick: () => { navigate('/settings/overview'); setConfigModalOpen(true); } } ]; return (
{/* Fixed header outside scrollable area */}
{/* Scrollable content area */}
{ // Prevent the wheel event from bubbling up to parent containers e.stopPropagation(); }} >
{/* Main navigation section */} {mainButtons.map((config, index) => ( {renderNavButton(config, index, config.id === 'read' || config.id === 'automate')} ))} {/* Middle section */} {middleButtons.length > 0 && ( <> {middleButtons.map((config, index) => ( {renderNavButton(config, index)} ))} )} {/* Spacer to push bottom buttons to bottom */}
{/* Bottom section */} {bottomButtons.map((buttonConfig, index) => { // Handle help button with menu or direct action if (buttonConfig.id === 'help') { const isAdmin = config?.isAdmin === true; const toursTooltipContent = isAdmin ? t('quickAccess.toursTooltip.admin', 'Watch walkthroughs here: Tools tour, New V2 layout tour, and the Admin tour.') : t('quickAccess.toursTooltip.user', 'Watch walkthroughs here: Tools tour and the New V2 layout tour.'); const tourItems = [ { key: 'whatsnew', icon: , title: t("quickAccess.helpMenu.whatsNewTour", "See what's new in V2"), description: t("quickAccess.helpMenu.whatsNewTourDesc", "Tour the updated layout"), onClick: () => requestStartTour('whatsnew'), }, { key: 'tools', icon: , title: t("quickAccess.helpMenu.toolsTour", "Tools Tour"), description: t("quickAccess.helpMenu.toolsTourDesc", "Learn what the tools can do"), onClick: () => requestStartTour('tools'), }, ...(isAdmin ? [{ key: 'admin', icon: , title: t("quickAccess.helpMenu.adminTour", "Admin Tour"), description: t("quickAccess.helpMenu.adminTourDesc", "Explore admin settings & features"), onClick: () => requestStartTour('admin'), }] : []), ]; const helpButtonNode = (
{renderNavButton(buttonConfig, index)}
{tourItems.map((item) => (
{item.title}
{item.description}
))}
); return ( {helpButtonNode} ); } const buttonNode = renderNavButton(buttonConfig, index); const shouldShowSettingsBadge = buttonConfig.id === 'config' && licenseAlert.active && licenseAlert.audience === 'admin'; return ( {shouldShowSettingsBadge ? ( {buttonNode} ) : ( buttonNode )} ); })}
setConfigModalOpen(false)} />
); }); QuickAccessBar.displayName = 'QuickAccessBar'; export default QuickAccessBar;