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", "title": "Advanced Colour options",
"desc": "Replace colour for text and background in PDF and invert full colour of pdf to reduce file size" "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": { "fakeScan": {
"title": "Fake Scan", "title": "Fake Scan",
"desc": "Create a PDF that looks like it was scanned" "desc": "Create a PDF that looks like it was scanned"

View File

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

View File

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

View File

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

View File

@ -212,6 +212,9 @@
.current-tool-slot.visible { .current-tool-slot.visible {
max-height: 8.25rem; /* icon + up to 3-line label + divider (132px) */ max-height: 8.25rem; /* icon + up to 3-line label + divider (132px) */
opacity: 1; 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 */ /* Replay the grow-down animation when switching tools while visible */
@ -219,6 +222,11 @@
animation: currentToolGrowDown 450ms ease-out; 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 { @keyframes currentToolGrowDown {
0% { 0% {
max-height: 0; max-height: 0;

View File

@ -11,13 +11,20 @@ export const isNavButtonActive = (
config: ButtonConfig, config: ButtonConfig,
activeButton: string, activeButton: string,
isFilesModalOpen: boolean, isFilesModalOpen: boolean,
configModalOpen: boolean configModalOpen: boolean,
selectedToolKey?: string | null,
leftPanelView?: 'toolPicker' | 'toolContent'
): boolean => { ): boolean => {
return ( const isActiveByLocalState = config.type === 'navigation' && activeButton === config.id;
(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 === '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, config: ButtonConfig,
activeButton: string, activeButton: string,
isFilesModalOpen: boolean, 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) { if (isActive) {
return { return {
@ -75,9 +91,12 @@ export const getActiveNavButton = (
if (readerMode) { if (readerMode) {
return 'read'; return 'read';
} }
if (leftPanelView !== 'toolContent' || !selectedToolKey) { // If a tool is selected, highlight it immediately even if the panel view
return 'tools'; // 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;
} }
// Default to All Tools when no tool is selected
return getTargetNavButton(selectedToolKey, registry) || 'tools'; return 'tools';
}; };