mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 04:09:22 +00:00
change requests
This commit is contained in:
parent
c76df73154
commit
be8ab09b3c
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
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);
|
||||
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" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -8,28 +8,44 @@ export const ROUND_BORDER_RADIUS = '0.5rem';
|
||||
* Check if a navigation button is currently active
|
||||
*/
|
||||
export const isNavButtonActive = (
|
||||
config: ButtonConfig,
|
||||
activeButton: string,
|
||||
isFilesModalOpen: boolean,
|
||||
configModalOpen: boolean
|
||||
config: ButtonConfig,
|
||||
activeButton: string,
|
||||
isFilesModalOpen: 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get button styles based on active state
|
||||
*/
|
||||
export const getNavButtonStyle = (
|
||||
config: ButtonConfig,
|
||||
activeButton: string,
|
||||
isFilesModalOpen: boolean,
|
||||
configModalOpen: boolean
|
||||
config: ButtonConfig,
|
||||
activeButton: string,
|
||||
isFilesModalOpen: 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';
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user