skeleton loading

This commit is contained in:
EthanHealy01 2025-08-12 13:38:21 +01:00
parent 8581829f6e
commit 53e9dbb66e
7 changed files with 66 additions and 49 deletions

View File

@ -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>
)}

View File

@ -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>

View File

@ -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 });
}

View File

@ -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 && (

View File

@ -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">

View File

@ -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 && (