mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-19 01:49:24 +00:00

# Description of Changes Because we used string typing for IDs and names, it was really easy to make mistakes where variables named like `subcategory` would be stored as an ID in one file, but then read assuming it's a name in another file. This PR changes the code to consistently use enum cases when referring to IDs of categories, subcategories, and tools (at least in as many places as I can find them, ~I had to add a `ToolId` enum for this work~ I originally added a `ToolId` type for this work, but it caused too many issues when merging with #4222 so I've pulled it back out for now). Making that change made it obvious where we were inconsistently passing IDs and reading them as names etc. allowing me to fix rendering issues in the All Tools pane, where the subcategory IDs were being rendered directly (instead of being translated) or where IDs were being translated into names, but were then being re-translated, causing warnings in the log.
230 lines
7.9 KiB
TypeScript
230 lines
7.9 KiB
TypeScript
import React, { useState, useRef, forwardRef, useEffect } from "react";
|
|
import { ActionIcon, Stack, Divider } from "@mantine/core";
|
|
import { useTranslation } from 'react-i18next';
|
|
import MenuBookIcon from "@mui/icons-material/MenuBookRounded";
|
|
import SettingsIcon from "@mui/icons-material/SettingsRounded";
|
|
import FolderIcon from "@mui/icons-material/FolderRounded";
|
|
import { useRainbowThemeContext } from "./RainbowThemeProvider";
|
|
import AppConfigModal from './AppConfigModal';
|
|
import { useIsOverflowing } from '../../hooks/useIsOverflowing';
|
|
import { useFilesModalContext } from '../../contexts/FilesModalContext';
|
|
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
|
import { ButtonConfig } from '../../types/sidebar';
|
|
import './quickAccessBar/QuickAccessBar.css';
|
|
import AllToolsNavButton from './AllToolsNavButton';
|
|
import ActiveToolButton from "./quickAccessBar/ActiveToolButton";
|
|
import {
|
|
isNavButtonActive,
|
|
getNavButtonStyle,
|
|
getActiveNavButton,
|
|
} from './quickAccessBar/QuickAccessBar';
|
|
|
|
const QuickAccessBar = forwardRef<HTMLDivElement>(({
|
|
}, ref) => {
|
|
const { t } = useTranslation();
|
|
const { isRainbowMode } = useRainbowThemeContext();
|
|
const { openFilesModal, isFilesModalOpen } = useFilesModalContext();
|
|
const { handleReaderToggle, handleBackToTools, handleToolSelect, selectedToolKey, leftPanelView, toolRegistry, readerMode } = useToolWorkflow();
|
|
const [configModalOpen, setConfigModalOpen] = useState(false);
|
|
const [activeButton, setActiveButton] = useState<string>('tools');
|
|
const scrollableRef = useRef<HTMLDivElement>(null);
|
|
const isOverflow = useIsOverflowing(scrollableRef);
|
|
|
|
useEffect(() => {
|
|
const next = getActiveNavButton(selectedToolKey, readerMode);
|
|
setActiveButton(next);
|
|
}, [leftPanelView, selectedToolKey, toolRegistry, readerMode]);
|
|
|
|
const handleFilesButtonClick = () => {
|
|
openFilesModal();
|
|
};
|
|
|
|
|
|
const buttonConfigs: ButtonConfig[] = [
|
|
{
|
|
id: 'read',
|
|
name: t("quickAccess.read", "Read"),
|
|
icon: <MenuBookIcon sx={{ fontSize: "1.5rem" }} />,
|
|
size: 'lg',
|
|
isRound: false,
|
|
type: 'navigation',
|
|
onClick: () => {
|
|
setActiveButton('read');
|
|
handleBackToTools();
|
|
handleReaderToggle();
|
|
}
|
|
},
|
|
{
|
|
id: 'sign',
|
|
name: t("quickAccess.sign", "Sign"),
|
|
icon:
|
|
<span className="material-symbols-rounded font-size-20">
|
|
signature
|
|
</span>,
|
|
size: 'lg',
|
|
isRound: false,
|
|
type: 'navigation',
|
|
onClick: () => {
|
|
setActiveButton('sign');
|
|
handleToolSelect('sign');
|
|
}
|
|
},
|
|
{
|
|
id: 'automate',
|
|
name: t("quickAccess.automate", "Automate"),
|
|
icon:
|
|
<span className="material-symbols-rounded font-size-20">
|
|
automation
|
|
</span>,
|
|
size: 'lg',
|
|
isRound: false,
|
|
type: 'navigation',
|
|
onClick: () => {
|
|
setActiveButton('automate');
|
|
handleToolSelect('automate');
|
|
}
|
|
},
|
|
{
|
|
id: 'files',
|
|
name: t("quickAccess.files", "Files"),
|
|
icon: <FolderIcon sx={{ fontSize: "1.25rem" }} />,
|
|
isRound: true,
|
|
size: 'lg',
|
|
type: 'modal',
|
|
onClick: handleFilesButtonClick
|
|
},
|
|
{
|
|
id: 'activity',
|
|
name: t("quickAccess.activity", "Activity"),
|
|
icon:
|
|
<span className="material-symbols-rounded font-size-20">
|
|
vital_signs
|
|
</span>,
|
|
isRound: true,
|
|
size: 'lg',
|
|
type: 'navigation',
|
|
onClick: () => setActiveButton('activity')
|
|
},
|
|
{
|
|
id: 'config',
|
|
name: t("quickAccess.config", "Config"),
|
|
icon: <SettingsIcon sx={{ fontSize: "1rem" }} />,
|
|
size: 'lg',
|
|
type: 'modal',
|
|
onClick: () => {
|
|
setConfigModalOpen(true);
|
|
}
|
|
}
|
|
];
|
|
|
|
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
data-sidebar="quick-access"
|
|
className={`h-screen flex flex-col w-20 quick-access-bar-main ${isRainbowMode ? 'rainbow-mode' : ''}`}
|
|
style={{
|
|
borderRight: '1px solid var(--border-default)'
|
|
}}
|
|
>
|
|
{/* Fixed header outside scrollable area */}
|
|
<div className="quick-access-header">
|
|
<ActiveToolButton activeButton={activeButton} setActiveButton={setActiveButton} />
|
|
<AllToolsNavButton activeButton={activeButton} setActiveButton={setActiveButton} />
|
|
|
|
</div>
|
|
|
|
{/* Conditional divider when overflowing */}
|
|
{isOverflow && (
|
|
<Divider
|
|
size="xs"
|
|
className="overflow-divider"
|
|
/>
|
|
)}
|
|
|
|
{/* Scrollable content area */}
|
|
<div
|
|
ref={scrollableRef}
|
|
className="quick-access-bar flex-1"
|
|
onWheel={(e) => {
|
|
// Prevent the wheel event from bubbling up to parent containers
|
|
e.stopPropagation();
|
|
}}
|
|
>
|
|
<div className="scrollable-content">
|
|
{/* Top section with main buttons */}
|
|
<Stack gap="lg" align="center">
|
|
{buttonConfigs.slice(0, -1).map((config, index) => (
|
|
<React.Fragment key={config.id}>
|
|
|
|
<div className="flex flex-col items-center gap-1" style={{ marginTop: index === 0 ? '0.5rem' : "0rem" }}>
|
|
<ActionIcon
|
|
size={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? (config.size || 'xl') : 'lg'}
|
|
variant="subtle"
|
|
onClick={() => {
|
|
config.onClick();
|
|
}}
|
|
style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)}
|
|
className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''}
|
|
data-testid={`${config.id}-button`}
|
|
>
|
|
<span className="iconContainer">
|
|
{config.icon}
|
|
</span>
|
|
</ActionIcon>
|
|
<span className={`button-text ${isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'active' : 'inactive'}`}>
|
|
{config.name}
|
|
</span>
|
|
</div>
|
|
|
|
|
|
{/* Add divider after Automate button (index 2) */}
|
|
{index === 2 && (
|
|
<Divider
|
|
size="xs"
|
|
className="content-divider"
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</Stack>
|
|
|
|
{/* Spacer to push Config button to bottom */}
|
|
<div className="spacer" />
|
|
|
|
{/* Config button at the bottom */}
|
|
{buttonConfigs
|
|
.filter(config => config.id === 'config')
|
|
.map(config => (
|
|
<div className="flex flex-col items-center gap-1">
|
|
<ActionIcon
|
|
size={config.size || 'lg'}
|
|
variant="subtle"
|
|
onClick={config.onClick}
|
|
style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)}
|
|
className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''}
|
|
data-testid={`${config.id}-button`}
|
|
>
|
|
<span className="iconContainer">
|
|
{config.icon}
|
|
</span>
|
|
</ActionIcon>
|
|
<span className={`button-text ${isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'active' : 'inactive'}`}>
|
|
{config.name}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<AppConfigModal
|
|
opened={configModalOpen}
|
|
onClose={() => setConfigModalOpen(false)}
|
|
/>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
export default QuickAccessBar;
|