change requests

This commit is contained in:
EthanHealy01 2025-08-18 17:02:42 +01:00
parent c76df73154
commit be8ab09b3c
6 changed files with 116 additions and 53 deletions

View File

@ -591,10 +591,6 @@
"title": "Advanced Colour options",
"desc": "Replace colour for text and background in PDF and invert full colour of pdf to reduce file size"
},
"EMLToPDF": {
"title": "Email to PDF",
"desc": "Converts email (EML) files to PDF format including headers, body, and inline images"
},
"fakeScan": {
"title": "Fake Scan",
"desc": "Create a PDF that looks like it was scanned"

View File

@ -31,7 +31,8 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
const isOverflow = useIsOverflowing(scrollableRef);
useEffect(() => {
setActiveButton(getActiveNavButton(leftPanelView, selectedToolKey, toolRegistry as any, readerMode));
const next = getActiveNavButton(leftPanelView, selectedToolKey, toolRegistry as any, readerMode);
setActiveButton(next);
}, [leftPanelView, selectedToolKey, toolRegistry, readerMode]);
const handleFilesButtonClick = () => {
@ -159,20 +160,20 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
<div className="flex flex-col items-center gap-1" style={{ marginTop: index === 0 ? '0.5rem' : "0rem" }}>
<ActionIcon
size={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen) ? (config.size || 'xl') : 'lg'}
size={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? (config.size || 'xl') : 'lg'}
variant="subtle"
onClick={() => {
config.onClick();
}}
style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen)}
className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen) ? 'activeIconScale' : ''}
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) ? 'active' : 'inactive'}`}>
<span className={`button-text ${isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'active' : 'inactive'}`}>
{config.name}
</span>
</div>
@ -201,15 +202,15 @@ const QuickAccessBar = forwardRef<HTMLDivElement>(({
size={config.size || 'lg'}
variant="subtle"
onClick={config.onClick}
style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen)}
className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen) ? 'activeIconScale' : ''}
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) ? 'active' : 'inactive'}`}>
<span className={`button-text ${isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'active' : 'inactive'}`}>
{config.name}
</span>
</div>

View File

@ -199,9 +199,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
) : null;
const handleMouseEnter = (e: React.MouseEvent) => {
if (openTimeoutRef.current) {
clearTimeout(openTimeoutRef.current);
}
clearTimers();
if (!isPinned) {
const effectiveDelay = Math.max(0, delay || 0);
openTimeoutRef.current = setTimeout(() => {
@ -213,10 +211,8 @@ export const Tooltip: React.FC<TooltipProps> = ({
};
const handleMouseLeave = (e: React.MouseEvent) => {
if (openTimeoutRef.current) {
clearTimeout(openTimeoutRef.current);
clearTimers();
openTimeoutRef.current = null;
}
if (!isPinned) {
handleOpenChange(false);

View File

@ -13,7 +13,7 @@
*/
import React, { useEffect, useRef, useState } from 'react';
import { ActionIcon, Divider } from '@mantine/core';
import { ActionIcon } from '@mantine/core';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext';
import FitText from '../FitText';
@ -29,9 +29,9 @@ const NAV_IDS = ['read', 'sign', 'automate'];
const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setActiveButton }) => {
const { selectedTool, selectedToolKey, leftPanelView, handleBackToTools } = useToolWorkflow();
// Determine if the indicator should be visible
// Determine if the indicator should be visible (do not require selectedTool to be resolved yet)
const indicatorShouldShow = Boolean(
selectedToolKey && selectedTool && leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey)
selectedToolKey && leftPanelView === 'toolContent' && !NAV_IDS.includes(selectedToolKey)
);
// Local animation and hover state
@ -41,53 +41,99 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
const [isAnimating, setIsAnimating] = useState<boolean>(false);
const [isBackHover, setIsBackHover] = useState<boolean>(false);
const prevKeyRef = useRef<string | null>(null);
const collapseTimeoutRef = useRef<number | null>(null);
const animTimeoutRef = useRef<number | null>(null);
const replayRafRef = useRef<number | null>(null);
const isSwitchingToNewTool = () => { return prevKeyRef.current && prevKeyRef.current !== selectedToolKey };
const playGrowDown = () => {
const clearTimers = () => {
if (collapseTimeoutRef.current) {
window.clearTimeout(collapseTimeoutRef.current);
collapseTimeoutRef.current = null;
}
if (animTimeoutRef.current) {
window.clearTimeout(animTimeoutRef.current);
animTimeoutRef.current = null;
}
};
const playGrowDown = () => {
clearTimers();
setIndicatorTool(selectedTool);
setIndicatorVisible(true);
// Force a replay even if the class is already applied
setReplayAnim(false);
if (replayRafRef.current) {
cancelAnimationFrame(replayRafRef.current);
replayRafRef.current = null;
}
replayRafRef.current = requestAnimationFrame(() => {
setReplayAnim(true);
});
setIsAnimating(true);
const t = window.setTimeout(() => {
prevKeyRef.current = (selectedToolKey as string) || null;
animTimeoutRef.current = window.setTimeout(() => {
setReplayAnim(false);
setIsAnimating(false);
animTimeoutRef.current = null;
}, 500);
return () => window.clearTimeout(t);
}
const firstShow = () => {
clearTimers();
setIndicatorTool(selectedTool);
setIndicatorVisible(true);
setIsAnimating(true);
prevKeyRef.current = (selectedToolKey as string) || null;
const tShow = window.setTimeout(() => setIsAnimating(false), 500);
return () => window.clearTimeout(tShow);
animTimeoutRef.current = window.setTimeout(() => {
setIsAnimating(false);
animTimeoutRef.current = null;
}, 500);
}
const triggerCollapse = () => {
clearTimers();
setIndicatorVisible(false);
setIsAnimating(true);
const timeout = window.setTimeout(() => {
collapseTimeoutRef.current = window.setTimeout(() => {
setIndicatorTool(null);
prevKeyRef.current = null;
setIsAnimating(false);
collapseTimeoutRef.current = null;
}, 500); // match CSS transition duration
return () => window.clearTimeout(timeout);
}
useEffect(() => {
if (indicatorShouldShow) {
if (isSwitchingToNewTool()) {
playGrowDown();
clearTimers();
if (!indicatorVisible) {
firstShow();
return;
}
firstShow()
} else if (indicatorTool) {
if (!indicatorTool) {
firstShow();
} else if (isSwitchingToNewTool()) {
playGrowDown();
} else {
// keep reference in sync
prevKeyRef.current = (selectedToolKey as string) || null;
}
} else if (indicatorTool || indicatorVisible) {
triggerCollapse();
}
}, [indicatorShouldShow, selectedTool, selectedToolKey]);
useEffect(() => {
return () => {
clearTimers();
if (replayRafRef.current) {
cancelAnimationFrame(replayRafRef.current);
replayRafRef.current = null;
}
};
}, []);
return (
<>
<div style={{ overflow: 'visible' }} className={`current-tool-slot ${indicatorVisible ? 'visible' : ''} ${replayAnim ? 'replay' : ''}`}>
@ -133,9 +179,6 @@ const ActiveToolButton: React.FC<ActiveToolButtonProps> = ({ activeButton, setAc
</div>
)}
</div>
{(indicatorTool && !isAnimating) && (
<Divider size="xs" className="current-tool-divider" />
)}
</>
);
};

View File

@ -212,6 +212,9 @@
.current-tool-slot.visible {
max-height: 8.25rem; /* icon + up to 3-line label + divider (132px) */
opacity: 1;
border-bottom: 1px solid var(--color-gray-300);
padding-bottom: 0.75rem; /* push border down for spacing */
margin-bottom: 1rem;
}
/* Replay the grow-down animation when switching tools while visible */
@ -219,6 +222,11 @@
animation: currentToolGrowDown 450ms ease-out;
}
/* Also animate the container itself when replaying so it "pushes down" again */
.current-tool-slot.replay {
animation: currentToolGrowDown 450ms ease-out;
}
@keyframes currentToolGrowDown {
0% {
max-height: 0;

View File

@ -11,13 +11,20 @@ export const isNavButtonActive = (
config: ButtonConfig,
activeButton: string,
isFilesModalOpen: boolean,
configModalOpen: boolean
configModalOpen: boolean,
selectedToolKey?: string | null,
leftPanelView?: 'toolPicker' | 'toolContent'
): boolean => {
return (
(config.type === 'navigation' && activeButton === config.id) ||
const isActiveByLocalState = config.type === 'navigation' && activeButton === config.id;
const isActiveByContext =
config.type === 'navigation' &&
leftPanelView === 'toolContent' &&
selectedToolKey === config.id;
const isActiveByModal =
(config.type === 'modal' && config.id === 'files' && isFilesModalOpen) ||
(config.type === 'modal' && config.id === 'config' && configModalOpen)
);
(config.type === 'modal' && config.id === 'config' && configModalOpen);
return isActiveByLocalState || isActiveByContext || isActiveByModal;
};
/**
@ -27,9 +34,18 @@ export const getNavButtonStyle = (
config: ButtonConfig,
activeButton: string,
isFilesModalOpen: boolean,
configModalOpen: boolean
configModalOpen: boolean,
selectedToolKey?: string | null,
leftPanelView?: 'toolPicker' | 'toolContent'
) => {
const isActive = isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen);
const isActive = isNavButtonActive(
config,
activeButton,
isFilesModalOpen,
configModalOpen,
selectedToolKey,
leftPanelView
);
if (isActive) {
return {
@ -75,9 +91,12 @@ export const getActiveNavButton = (
if (readerMode) {
return 'read';
}
if (leftPanelView !== 'toolContent' || !selectedToolKey) {
return 'tools';
// If a tool is selected, highlight it immediately even if the panel view
// transition to 'toolContent' has not completed yet. This prevents a brief
// period of no-highlight during rapid navigation.
if (selectedToolKey) {
return getTargetNavButton(selectedToolKey, registry) || selectedToolKey;
}
return getTargetNavButton(selectedToolKey, registry) || 'tools';
// Default to All Tools when no tool is selected
return 'tools';
};