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",
|
"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"
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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" />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -8,28 +8,44 @@ export const ROUND_BORDER_RADIUS = '0.5rem';
|
|||||||
* Check if a navigation button is currently active
|
* Check if a navigation button is currently active
|
||||||
*/
|
*/
|
||||||
export const isNavButtonActive = (
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get button styles based on active state
|
* Get button styles based on active state
|
||||||
*/
|
*/
|
||||||
export const getNavButtonStyle = (
|
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';
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user