mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 12:19:24 +00:00
skeleton loading
This commit is contained in:
parent
8581829f6e
commit
53e9dbb66e
@ -109,10 +109,12 @@ export default function ToolPanel() {
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Tool content */}
|
||||
<div className="flex-1 min-h-0">
|
||||
{selectedToolKey && (
|
||||
<ToolRenderer
|
||||
selectedToolKey={selectedToolKey}
|
||||
onPreviewFile={setPreviewFile}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
||||
import React, { createContext, useContext, useMemo, useRef, useEffect, useState } from 'react';
|
||||
import { Paper, Text, Stack, Box, Flex } from '@mantine/core';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
@ -22,6 +22,10 @@ export interface ToolStepProps {
|
||||
completedMessage?: string;
|
||||
helpText?: string;
|
||||
showNumber?: boolean;
|
||||
/** Show a brief skeleton for smoother tool transitions */
|
||||
enableInitialSkeleton?: boolean;
|
||||
/** Duration for the initial skeleton in milliseconds */
|
||||
initialSkeletonMs?: number;
|
||||
tooltip?: {
|
||||
content?: React.ReactNode;
|
||||
tips?: TooltipTip[];
|
||||
@ -74,6 +78,8 @@ const ToolStep = ({
|
||||
completedMessage,
|
||||
helpText,
|
||||
showNumber,
|
||||
enableInitialSkeleton = true,
|
||||
initialSkeletonMs = 800,
|
||||
tooltip
|
||||
}: ToolStepProps) => {
|
||||
if (!isVisible) return null;
|
||||
@ -88,6 +94,16 @@ const ToolStep = ({
|
||||
|
||||
const stepNumber = parent?.getStepNumber?.() || 1;
|
||||
|
||||
// Show a step-sized skeleton immediately after mount to indicate tool switch
|
||||
const [showInitialSkeleton, setShowInitialSkeleton] = useState(enableInitialSkeleton);
|
||||
useEffect(() => {
|
||||
if (!enableInitialSkeleton) return;
|
||||
setShowInitialSkeleton(true);
|
||||
const id = setTimeout(() => setShowInitialSkeleton(false), initialSkeletonMs);
|
||||
return () => clearTimeout(id);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
p="md"
|
||||
@ -109,11 +125,19 @@ const ToolStep = ({
|
||||
>
|
||||
<Flex align="center" gap="sm">
|
||||
{shouldShowNumber && (
|
||||
showInitialSkeleton ? (
|
||||
<Box w={18} h={18} bg="gray.1" style={{ borderRadius: 4 }} />
|
||||
) : (
|
||||
<Text fw={500} size="lg" c="dimmed">
|
||||
{stepNumber}
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
{showInitialSkeleton ? (
|
||||
<Box w={80} h={20} bg="gray.1" style={{ borderRadius: 4 }} />
|
||||
) : (
|
||||
renderTooltipTitle(title, tooltip, isCollapsed)
|
||||
)}
|
||||
{renderTooltipTitle(title, tooltip, isCollapsed)}
|
||||
</Flex>
|
||||
|
||||
{isCollapsed ? (
|
||||
@ -133,7 +157,10 @@ const ToolStep = ({
|
||||
|
||||
{isCollapsed ? (
|
||||
<Box>
|
||||
{isCompleted && completedMessage && (
|
||||
{showInitialSkeleton ? (
|
||||
<Box w="40%" h={14} bg="gray.1" style={{ borderRadius: 4 }} />
|
||||
) : (
|
||||
isCompleted && completedMessage && (
|
||||
<Text size="sm" c="green">
|
||||
✓ {completedMessage}
|
||||
{onCollapsedClick && (
|
||||
@ -142,6 +169,7 @@ const ToolStep = ({
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
@ -151,7 +179,16 @@ const ToolStep = ({
|
||||
{helpText}
|
||||
</Text>
|
||||
)}
|
||||
{children}
|
||||
{showInitialSkeleton ? (
|
||||
<>
|
||||
<Box w="80%" h={16} bg="gray.1" style={{ borderRadius: 4 }} />
|
||||
<Box w="60%" h={16} bg="gray.1" style={{ borderRadius: 4 }} />
|
||||
<Box w="100%" h={44} bg="gray.1" style={{ borderRadius: 8 }} />
|
||||
<Box w="100%" h={44} bg="gray.1" style={{ borderRadius: 8 }} />
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
|
@ -514,33 +514,6 @@ export const flatToolRegistryMap: ToolRegistry = {
|
||||
category: "Standard Tools",
|
||||
subcategory: "Page Formatting"
|
||||
},
|
||||
"split": {
|
||||
icon: <span className="material-symbols-rounded">content_cut</span>,
|
||||
name: "home.split.title",
|
||||
component: null,
|
||||
view: "format",
|
||||
description: "home.split.desc",
|
||||
category: "Standard Tools",
|
||||
subcategory: "Page Formatting"
|
||||
},
|
||||
"split-by-chapters": {
|
||||
icon: <span className="material-symbols-rounded">collections_bookmark</span>,
|
||||
name: "home.splitPdfByChapters.title",
|
||||
component: null,
|
||||
view: "format",
|
||||
description: "home.splitPdfByChapters.desc",
|
||||
category: "Advanced Tools",
|
||||
subcategory: "Advanced Formatting"
|
||||
},
|
||||
"split-by-sections": {
|
||||
icon: <span className="material-symbols-rounded">grid_on</span>,
|
||||
name: "home.split-by-sections.title",
|
||||
component: null,
|
||||
view: "format",
|
||||
description: "home.split-by-sections.desc",
|
||||
category: "Advanced Tools",
|
||||
subcategory: "Advanced Formatting"
|
||||
},
|
||||
"splitPdf": {
|
||||
icon: <span className="material-symbols-rounded">content_cut</span>,
|
||||
name: "home.split.title",
|
||||
@ -594,7 +567,7 @@ function buildStructuredRegistry(): ToolRegistryStructured {
|
||||
for (const [id, tool] of entries) {
|
||||
const sub = tool.subcategory ?? 'General';
|
||||
const cat = tool.category ?? 'OTHER';
|
||||
// Quick access: use the existing "Recommended Tools" category
|
||||
// Quick access: use the existing "Recommended Tools" category, this will change in future
|
||||
if (tool.category === 'Recommended Tools') {
|
||||
quick.push({ id, ...tool });
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
<ToolStep
|
||||
title="Settings"
|
||||
isVisible={hasFiles}
|
||||
enableInitialSkeleton={false}
|
||||
isCollapsed={settingsCollapsed}
|
||||
isCompleted={settingsCollapsed}
|
||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
||||
@ -129,6 +130,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
<ToolStep
|
||||
title="Results"
|
||||
isVisible={hasResults}
|
||||
enableInitialSkeleton={false}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
{compressOperation.status && (
|
||||
|
@ -163,6 +163,7 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
<ToolStep
|
||||
title={t("convert.results", "Results")}
|
||||
isVisible={hasResults}
|
||||
enableInitialSkeleton={false}
|
||||
data-testid="conversion-results"
|
||||
>
|
||||
<Stack gap="sm">
|
||||
|
@ -96,7 +96,8 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
{/* Settings Step */}
|
||||
<ToolStep
|
||||
title="Settings"
|
||||
isVisible={hasFiles}
|
||||
isVisible={true}
|
||||
enableInitialSkeleton={true}
|
||||
isCollapsed={settingsCollapsed}
|
||||
isCompleted={settingsCollapsed}
|
||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
||||
@ -125,6 +126,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
<ToolStep
|
||||
title="Results"
|
||||
isVisible={hasResults}
|
||||
enableInitialSkeleton={false}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
{splitOperation.status && (
|
||||
|
Loading…
x
Reference in New Issue
Block a user