mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Toolstep factory and generic files and results steps
This commit is contained in:
parent
1bf9da08af
commit
9d2ca3c8c8
@ -39,7 +39,7 @@ export default function ToolPanel() {
|
|||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: isPanelVisible ? '20rem' : '0',
|
width: isPanelVisible ? '20rem' : '0',
|
||||||
padding: isPanelVisible ? '0.5rem' : '0'
|
padding: '0'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button, Stack, Text, NumberInput, Select } from "@mantine/core";
|
import { Button, Stack, Text, NumberInput, Select, Divider } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
interface CompressParameters {
|
interface CompressParameters {
|
||||||
@ -22,6 +22,8 @@ const CompressSettings = ({ parameters, onParameterChange, disabled = false }: C
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
|
|
||||||
|
<Divider ml='-md'></Divider>
|
||||||
{/* Compression Method */}
|
{/* Compression Method */}
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="sm" fw={500}>Compression Method</Text>
|
<Text size="sm" fw={500}>Compression Method</Text>
|
||||||
@ -54,6 +56,7 @@ const CompressSettings = ({ parameters, onParameterChange, disabled = false }: C
|
|||||||
{/* Quality Adjustment */}
|
{/* Quality Adjustment */}
|
||||||
{parameters.compressionMethod === 'quality' && (
|
{parameters.compressionMethod === 'quality' && (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
<Divider />
|
||||||
<Text size="sm" fw={500}>Compression Level</Text>
|
<Text size="sm" fw={500}>Compression Level</Text>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<input
|
<input
|
||||||
@ -107,6 +110,8 @@ const CompressSettings = ({ parameters, onParameterChange, disabled = false }: C
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Divider/>
|
||||||
|
|
||||||
{/* File Size Input */}
|
{/* File Size Input */}
|
||||||
{parameters.compressionMethod === 'filesize' && (
|
{parameters.compressionMethod === 'filesize' && (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
|
@ -24,7 +24,7 @@ const OperationButton = ({
|
|||||||
submitText,
|
submitText,
|
||||||
variant = 'filled',
|
variant = 'filled',
|
||||||
color = 'blue',
|
color = 'blue',
|
||||||
fullWidth = true,
|
fullWidth = false,
|
||||||
mt = 'md',
|
mt = 'md',
|
||||||
type = 'button',
|
type = 'button',
|
||||||
'data-testid': dataTestId
|
'data-testid': dataTestId
|
||||||
@ -36,6 +36,8 @@ const OperationButton = ({
|
|||||||
type={type}
|
type={type}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
|
mr='md'
|
||||||
|
ml='md'
|
||||||
mt={mt}
|
mt={mt}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
||||||
import { Paper, Text, Stack, Box, Flex } from '@mantine/core';
|
import { Text, Stack, Box, Flex, Divider } from '@mantine/core';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||||
import { Tooltip } from '../../shared/Tooltip';
|
import { Tooltip } from '../../shared/Tooltip';
|
||||||
import { TooltipTip } from '../../shared/tooltip/TooltipContent';
|
import { TooltipTip } from '../../shared/tooltip/TooltipContent';
|
||||||
|
import { createFilesToolStep, FilesToolStepProps } from './createFilesToolStep';
|
||||||
|
import { createResultsToolStep, ResultsToolStepProps } from './createResultsToolStep';
|
||||||
|
|
||||||
interface ToolStepContextType {
|
interface ToolStepContextType {
|
||||||
visibleStepCount: number;
|
visibleStepCount: number;
|
||||||
@ -87,15 +89,16 @@ const ToolStep = ({
|
|||||||
return parent ? parent.visibleStepCount >= 3 : false;
|
return parent ? parent.visibleStepCount >= 3 : false;
|
||||||
}, [showNumber, parent]);
|
}, [showNumber, parent]);
|
||||||
|
|
||||||
const stepNumber = _stepNumber || 1;
|
const stepNumber = _stepNumber;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<div>
|
||||||
p="md"
|
<div
|
||||||
withBorder
|
|
||||||
style={{
|
style={{
|
||||||
|
padding: '1rem',
|
||||||
opacity: isCollapsed ? 0.8 : 1,
|
opacity: isCollapsed ? 0.8 : 1,
|
||||||
transition: 'opacity 0.2s ease'
|
color: isCollapsed ? 'var(--mantine-color-dimmed)' : 'inherit',
|
||||||
|
transition: 'opacity 0.2s ease, color 0.2s ease'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Chevron icon to collapse/expand the step */}
|
{/* Chevron icon to collapse/expand the step */}
|
||||||
@ -133,7 +136,7 @@ const ToolStep = ({
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<Box>
|
<div>
|
||||||
{isCompleted && completedMessage && (
|
{isCompleted && completedMessage && (
|
||||||
<Text size="sm" c="green">
|
<Text size="sm" c="green">
|
||||||
✓ {completedMessage}
|
✓ {completedMessage}
|
||||||
@ -144,9 +147,9 @@ const ToolStep = ({
|
|||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Stack gap="md">
|
<Stack gap="md" pl="md">
|
||||||
{helpText && (
|
{helpText && (
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{helpText}
|
{helpText}
|
||||||
@ -155,33 +158,57 @@ const ToolStep = ({
|
|||||||
{children}
|
{children}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</div>
|
||||||
|
<Divider style={{ marginLeft: '1rem', marginRight: '-1rem' }} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolStepContainerProps {
|
// ToolStepFactory for creating numbered steps
|
||||||
children: React.ReactNode;
|
export function createToolSteps() {
|
||||||
}
|
let stepNumber = 1;
|
||||||
|
const steps: React.ReactElement[] = [];
|
||||||
|
|
||||||
export const ToolStepContainer = ({ children }: ToolStepContainerProps) => {
|
const create = (
|
||||||
// Process children and inject step numbers for visible ToolSteps
|
title: string,
|
||||||
const processedChildren = useMemo(() => {
|
props: Omit<ToolStepProps, 'title' | '_stepNumber'> = {},
|
||||||
let visibleStepNumber = 1;
|
children?: React.ReactNode
|
||||||
|
): React.ReactElement => {
|
||||||
|
const isVisible = props.isVisible !== false;
|
||||||
|
const currentStepNumber = isVisible ? stepNumber++ : undefined;
|
||||||
|
|
||||||
return React.Children.map(children, (child) => {
|
const step = React.createElement(ToolStep, {
|
||||||
if (React.isValidElement(child) && child.type === ToolStep) {
|
...props,
|
||||||
const isVisible = (child.props as ToolStepProps).isVisible !== false;
|
title,
|
||||||
if (isVisible) {
|
_stepNumber: currentStepNumber,
|
||||||
return React.cloneElement(child, {
|
children,
|
||||||
...child.props,
|
key: `step-${title.toLowerCase().replace(/\s+/g, '-')}`
|
||||||
_stepNumber: visibleStepNumber++
|
|
||||||
} as ToolStepProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
});
|
});
|
||||||
}, [children]);
|
|
||||||
|
|
||||||
|
steps.push(step);
|
||||||
|
return step;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFilesStep = (props: FilesToolStepProps): React.ReactElement => {
|
||||||
|
return createFilesToolStep(create, props);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createResultsStep = <TParams = any>(props: ResultsToolStepProps<TParams>): React.ReactElement => {
|
||||||
|
return createResultsToolStep(create, props);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVisibleCount = () => {
|
||||||
|
return steps.filter(step =>
|
||||||
|
(step.props as ToolStepProps).isVisible !== false
|
||||||
|
).length;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { create, createFilesStep, createResultsStep, getVisibleCount, steps };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context provider wrapper for tools using the factory
|
||||||
|
export function ToolStepProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
// Count visible steps from children that are ToolStep elements
|
||||||
const visibleStepCount = useMemo(() => {
|
const visibleStepCount = useMemo(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
React.Children.forEach(children, (child) => {
|
React.Children.forEach(children, (child) => {
|
||||||
@ -199,9 +226,11 @@ export const ToolStepContainer = ({ children }: ToolStepContainerProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolStepContext.Provider value={contextValue}>
|
<ToolStepContext.Provider value={contextValue}>
|
||||||
{processedChildren}
|
{children}
|
||||||
</ToolStepContext.Provider>
|
</ToolStepContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { FilesToolStepProps } from './createFilesToolStep';
|
||||||
|
export type { ResultsToolStepProps } from './createResultsToolStep';
|
||||||
export default ToolStep;
|
export default ToolStep;
|
||||||
|
31
frontend/src/components/tools/shared/createFilesToolStep.tsx
Normal file
31
frontend/src/components/tools/shared/createFilesToolStep.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import FileStatusIndicator from './FileStatusIndicator';
|
||||||
|
|
||||||
|
export interface FilesToolStepProps {
|
||||||
|
selectedFiles: File[];
|
||||||
|
isCollapsed?: boolean;
|
||||||
|
onCollapsedClick?: () => void;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFilesToolStep(
|
||||||
|
createStep: (title: string, props: any, children?: React.ReactNode) => React.ReactElement,
|
||||||
|
props: FilesToolStepProps
|
||||||
|
): React.ReactElement {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const hasFiles = props.selectedFiles.length > 0;
|
||||||
|
|
||||||
|
return createStep("Files", {
|
||||||
|
isVisible: true,
|
||||||
|
isCollapsed: props.isCollapsed,
|
||||||
|
isCompleted: hasFiles,
|
||||||
|
onCollapsedClick: props.onCollapsedClick,
|
||||||
|
completedMessage: undefined
|
||||||
|
}, (
|
||||||
|
<FileStatusIndicator
|
||||||
|
selectedFiles={props.selectedFiles}
|
||||||
|
placeholder={props.placeholder || t("files.placeholder", "Select a PDF file in the main view to get started")}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Stack, Text } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import DownloadIcon from '@mui/icons-material/Download';
|
||||||
|
import ErrorNotification from './ErrorNotification';
|
||||||
|
import ResultsPreview from './ResultsPreview';
|
||||||
|
import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation';
|
||||||
|
|
||||||
|
export interface ResultsToolStepProps<TParams = any> {
|
||||||
|
isVisible: boolean;
|
||||||
|
operation: ToolOperationHook<TParams>;
|
||||||
|
title?: string;
|
||||||
|
onFileClick?: (file: File) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createResultsToolStep<TParams = any>(
|
||||||
|
createStep: (title: string, props: any, children?: React.ReactNode) => React.ReactElement,
|
||||||
|
props: ResultsToolStepProps<TParams>
|
||||||
|
): React.ReactElement {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { operation } = props;
|
||||||
|
|
||||||
|
const previewFiles = operation.files?.map((file, index) => ({
|
||||||
|
file,
|
||||||
|
thumbnail: operation.thumbnails[index]
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return createStep("Results", {
|
||||||
|
isVisible: props.isVisible
|
||||||
|
}, (
|
||||||
|
<Stack gap="sm">
|
||||||
|
{operation.status && (
|
||||||
|
<Text size="sm" c="dimmed">{operation.status}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ErrorNotification
|
||||||
|
error={operation.errorMessage}
|
||||||
|
onClose={operation.clearError}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{operation.downloadUrl && (
|
||||||
|
<Button
|
||||||
|
component="a"
|
||||||
|
href={operation.downloadUrl}
|
||||||
|
download={operation.downloadFilename}
|
||||||
|
leftSection={<DownloadIcon />}
|
||||||
|
color="green"
|
||||||
|
fullWidth
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
{t("download", "Download")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{previewFiles.length > 0 && (
|
||||||
|
<ResultsPreview
|
||||||
|
files={previewFiles}
|
||||||
|
onFileClick={props.onFileClick}
|
||||||
|
isGeneratingThumbnails={operation.isGeneratingThumbnails}
|
||||||
|
title={props.title || "Results"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
));
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { Button, Stack, Text } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||||
import OperationButton from "../components/tools/shared/OperationButton";
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
|
||||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
|
||||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
|
||||||
|
|
||||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||||
|
|
||||||
@ -67,103 +63,52 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const hasFiles = selectedFiles.length > 0;
|
const hasFiles = selectedFiles.length > 0;
|
||||||
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
|
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
|
||||||
const filesCollapsed = hasFiles;
|
const filesCollapsed = hasFiles;
|
||||||
const settingsCollapsed = hasResults;
|
const settingsCollapsed = !hasFiles || hasResults;
|
||||||
|
|
||||||
const previewResults = useMemo(() =>
|
|
||||||
compressOperation.files?.map((file, index) => ({
|
const steps = createToolSteps();
|
||||||
file,
|
|
||||||
thumbnail: compressOperation.thumbnails[index]
|
|
||||||
})) || [],
|
|
||||||
[compressOperation.files, compressOperation.thumbnails]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolStepContainer>
|
<Stack gap="md" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||||
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
<ToolStepProvider>
|
||||||
{/* Files Step */}
|
{/* Files Step */}
|
||||||
<ToolStep
|
{steps.createFilesStep({
|
||||||
title="Files"
|
selectedFiles,
|
||||||
isVisible={true}
|
isCollapsed: filesCollapsed
|
||||||
isCollapsed={filesCollapsed}
|
})}
|
||||||
isCompleted={filesCollapsed}
|
|
||||||
completedMessage={hasFiles ?
|
|
||||||
selectedFiles.length === 1
|
|
||||||
? `Selected: ${selectedFiles[0].name}`
|
|
||||||
: `Selected: ${selectedFiles.length} files`
|
|
||||||
: undefined}
|
|
||||||
>
|
|
||||||
<FileStatusIndicator
|
|
||||||
selectedFiles={selectedFiles}
|
|
||||||
placeholder="Select a PDF file in the main view to get started"
|
|
||||||
/>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
{/* Settings Step */}
|
{/* Settings Step */}
|
||||||
<ToolStep
|
{steps.create("Settings", {
|
||||||
title="Settings"
|
isCollapsed: settingsCollapsed,
|
||||||
isVisible={hasFiles}
|
isCompleted: hasResults,
|
||||||
isCollapsed={settingsCollapsed}
|
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
|
||||||
isCompleted={settingsCollapsed}
|
completedMessage: t("compress.header", "Compression completed"),
|
||||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
tooltip: compressTips
|
||||||
completedMessage={settingsCollapsed ? "Compression completed" : undefined}
|
}, (
|
||||||
tooltip={compressTips}
|
<Stack gap="md">
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
|
||||||
<CompressSettings
|
<CompressSettings
|
||||||
parameters={compressParams.parameters}
|
parameters={compressParams.parameters}
|
||||||
onParameterChange={compressParams.updateParameter}
|
onParameterChange={compressParams.updateParameter}
|
||||||
disabled={endpointLoading}
|
disabled={endpointLoading}
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
<OperationButton
|
<OperationButton
|
||||||
onClick={handleCompress}
|
onClick={handleCompress}
|
||||||
isLoading={compressOperation.isLoading}
|
isLoading={compressOperation.isLoading}
|
||||||
disabled={!compressParams.validateParameters() || !hasFiles || !endpointEnabled}
|
disabled={!compressParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||||
loadingText={t("loading")}
|
loadingText={t("loading")}
|
||||||
submitText="Compress and Review"
|
submitText={t("compress.submit", "Compress")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
{/* Results Step */}
|
{/* Results Step */}
|
||||||
<ToolStep
|
{steps.createResultsStep({
|
||||||
title="Results"
|
isVisible: hasResults,
|
||||||
isVisible={hasResults}
|
operation: compressOperation,
|
||||||
>
|
title: t("compress.title", "Compression Results"),
|
||||||
<Stack gap="sm">
|
onFileClick: handleThumbnailClick
|
||||||
{compressOperation.status && (
|
})}
|
||||||
<Text size="sm" c="dimmed">{compressOperation.status}</Text>
|
</ToolStepProvider>
|
||||||
)}
|
|
||||||
|
|
||||||
<ErrorNotification
|
|
||||||
error={compressOperation.errorMessage}
|
|
||||||
onClose={compressOperation.clearError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{compressOperation.downloadUrl && (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
href={compressOperation.downloadUrl}
|
|
||||||
download={compressOperation.downloadFilename}
|
|
||||||
leftSection={<DownloadIcon />}
|
|
||||||
color="green"
|
|
||||||
fullWidth
|
|
||||||
mb="md"
|
|
||||||
>
|
|
||||||
{t("download", "Download")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ResultsPreview
|
|
||||||
files={previewResults}
|
|
||||||
onFileClick={handleThumbnailClick}
|
|
||||||
isGeneratingThumbnails={compressOperation.isGeneratingThumbnails}
|
|
||||||
title="Compression Results"
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</ToolStep>
|
|
||||||
</Stack>
|
|
||||||
</ToolStepContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import React, { useEffect, useMemo, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Button, Stack, Text } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||||
import OperationButton from "../components/tools/shared/OperationButton";
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
|
||||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
|
||||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
|
||||||
|
|
||||||
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||||
|
|
||||||
@ -105,39 +101,25 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
setCurrentMode('convert');
|
setCurrentMode('convert');
|
||||||
};
|
};
|
||||||
|
|
||||||
const previewResults = useMemo(() =>
|
const steps = createToolSteps();
|
||||||
convertOperation.files?.map((file, index) => ({
|
|
||||||
file,
|
|
||||||
thumbnail: convertOperation.thumbnails[index]
|
|
||||||
})) || [],
|
|
||||||
[convertOperation.files, convertOperation.thumbnails]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full max-h-screen overflow-y-auto" ref={scrollContainerRef}>
|
<div className="h-full max-h-screen overflow-y-auto" ref={scrollContainerRef}>
|
||||||
<ToolStepContainer>
|
|
||||||
<Stack gap="sm" p="sm">
|
<Stack gap="sm" p="sm">
|
||||||
<ToolStep
|
<ToolStepProvider>
|
||||||
title={t("convert.files", "Files")}
|
{/* Files Step */}
|
||||||
isVisible={true}
|
{steps.createFilesStep({
|
||||||
isCollapsed={filesCollapsed}
|
selectedFiles,
|
||||||
isCompleted={filesCollapsed}
|
isCollapsed: filesCollapsed,
|
||||||
completedMessage={hasFiles ? `${selectedFiles.length} ${t("filesSelected", "files selected")}` : undefined}
|
placeholder: t("convert.selectFilesPlaceholder", "Select files in the main view to get started")
|
||||||
>
|
})}
|
||||||
<FileStatusIndicator
|
|
||||||
selectedFiles={selectedFiles}
|
|
||||||
placeholder={t("convert.selectFilesPlaceholder", "Select files in the main view to get started")}
|
|
||||||
/>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
<ToolStep
|
{/* Settings Step */}
|
||||||
title={t("convert.settings", "Settings")}
|
{steps.create(t("convert.settings", "Settings"), {
|
||||||
isVisible={true}
|
isCollapsed: settingsCollapsed,
|
||||||
isCollapsed={settingsCollapsed}
|
isCompleted: settingsCollapsed,
|
||||||
isCompleted={settingsCollapsed}
|
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
|
||||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
}, (
|
||||||
completedMessage={settingsCollapsed ? t("convert.conversionCompleted", "Conversion completed") : undefined}
|
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<ConvertSettings
|
<ConvertSettings
|
||||||
parameters={convertParams.parameters}
|
parameters={convertParams.parameters}
|
||||||
@ -146,8 +128,9 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
selectedFiles={selectedFiles}
|
selectedFiles={selectedFiles}
|
||||||
disabled={endpointLoading}
|
disabled={endpointLoading}
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
{hasFiles && convertParams.parameters.fromExtension && convertParams.parameters.toExtension && (
|
))}
|
||||||
|
{!hasResults && (
|
||||||
<OperationButton
|
<OperationButton
|
||||||
onClick={handleConvert}
|
onClick={handleConvert}
|
||||||
isLoading={convertOperation.isLoading}
|
isLoading={convertOperation.isLoading}
|
||||||
@ -157,49 +140,16 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
data-testid="convert-button"
|
data-testid="convert-button"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Results Step */}
|
||||||
|
{steps.createResultsStep({
|
||||||
|
isVisible: hasResults,
|
||||||
|
operation: convertOperation,
|
||||||
|
title: t("convert.conversionResults", "Conversion Results"),
|
||||||
|
onFileClick: handleThumbnailClick
|
||||||
|
})}
|
||||||
|
</ToolStepProvider>
|
||||||
</Stack>
|
</Stack>
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
<ToolStep
|
|
||||||
title={t("convert.results", "Results")}
|
|
||||||
isVisible={hasResults}
|
|
||||||
data-testid="conversion-results"
|
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
|
||||||
{convertOperation.status && (
|
|
||||||
<Text size="sm" c="dimmed">{convertOperation.status}</Text>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ErrorNotification
|
|
||||||
error={convertOperation.errorMessage}
|
|
||||||
onClose={convertOperation.clearError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{convertOperation.downloadUrl && (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
href={convertOperation.downloadUrl}
|
|
||||||
download={convertOperation.downloadFilename || t("convert.defaultFilename", "converted_file")}
|
|
||||||
leftSection={<DownloadIcon />}
|
|
||||||
color="green"
|
|
||||||
fullWidth
|
|
||||||
mb="md"
|
|
||||||
data-testid="download-button"
|
|
||||||
>
|
|
||||||
{t("convert.downloadConverted", "Download Converted File")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ResultsPreview
|
|
||||||
files={previewResults}
|
|
||||||
onFileClick={handleThumbnailClick}
|
|
||||||
isGeneratingThumbnails={convertOperation.isGeneratingThumbnails}
|
|
||||||
title={t("convert.conversionResults", "Conversion Results")}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</ToolStep>
|
|
||||||
</Stack>
|
|
||||||
</ToolStepContainer>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Stack, Text, Box } from "@mantine/core";
|
import { Stack, Box } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||||
import OperationButton from "../components/tools/shared/OperationButton";
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
|
||||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
|
||||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
|
||||||
|
|
||||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
|
import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
|
||||||
@ -79,138 +75,78 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Step visibility and collapse logic
|
|
||||||
const filesVisible = true;
|
|
||||||
const settingsVisible = true;
|
|
||||||
const resultsVisible = hasResults;
|
const resultsVisible = hasResults;
|
||||||
|
|
||||||
const filesCollapsed = expandedStep !== 'files';
|
const filesCollapsed = expandedStep !== 'files';
|
||||||
const settingsCollapsed = expandedStep !== 'settings';
|
const settingsCollapsed = expandedStep !== 'settings';
|
||||||
|
|
||||||
const previewResults = useMemo(() =>
|
|
||||||
ocrOperation.files?.map((file: File, index: number) => ({
|
const steps = createToolSteps();
|
||||||
file,
|
|
||||||
thumbnail: ocrOperation.thumbnails[index]
|
|
||||||
})) || [],
|
|
||||||
[ocrOperation.files, ocrOperation.thumbnails]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||||
<ToolStepContainer>
|
<ToolStepProvider>
|
||||||
{/* Files Step */}
|
{/* Files Step */}
|
||||||
<ToolStep
|
{steps.createFilesStep({
|
||||||
title="Files"
|
selectedFiles,
|
||||||
isVisible={filesVisible}
|
isCollapsed: hasFiles && filesCollapsed,
|
||||||
isCollapsed={hasFiles ? filesCollapsed : false}
|
})}
|
||||||
isCompleted={hasFiles}
|
|
||||||
onCollapsedClick={undefined}
|
|
||||||
completedMessage={hasFiles && filesCollapsed ?
|
|
||||||
selectedFiles.length === 1
|
|
||||||
? `Selected: ${selectedFiles[0].name}`
|
|
||||||
: `Selected: ${selectedFiles.length} files`
|
|
||||||
: undefined}
|
|
||||||
>
|
|
||||||
<FileStatusIndicator
|
|
||||||
selectedFiles={selectedFiles}
|
|
||||||
placeholder="Select a PDF file in the main view to get started"
|
|
||||||
/>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
{/* Settings Step */}
|
{/* Settings Step */}
|
||||||
<ToolStep
|
{steps.create("Settings", {
|
||||||
title="Settings"
|
isCollapsed: !hasFiles || settingsCollapsed,
|
||||||
isVisible={settingsVisible}
|
isCompleted: hasFiles && hasValidSettings,
|
||||||
isCollapsed={settingsCollapsed}
|
onCollapsedClick: () => {
|
||||||
isCompleted={hasFiles && hasValidSettings}
|
|
||||||
onCollapsedClick={() => {
|
|
||||||
if (!hasFiles) return; // Only allow if files are selected
|
if (!hasFiles) return; // Only allow if files are selected
|
||||||
setExpandedStep(expandedStep === 'settings' ? null : 'settings');
|
setExpandedStep(expandedStep === 'settings' ? null : 'settings');
|
||||||
}}
|
},
|
||||||
completedMessage={hasFiles && hasValidSettings && settingsCollapsed ? "Basic settings configured" : undefined}
|
tooltip: ocrTips
|
||||||
tooltip={ocrTips}
|
}, (
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<OCRSettings
|
<OCRSettings
|
||||||
parameters={ocrParams.parameters}
|
parameters={ocrParams.parameters}
|
||||||
onParameterChange={ocrParams.updateParameter}
|
onParameterChange={ocrParams.updateParameter}
|
||||||
disabled={endpointLoading}
|
disabled={endpointLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</ToolStep>
|
))}
|
||||||
|
|
||||||
{/* Advanced Step */}
|
{/* Advanced Step */}
|
||||||
<ToolStep
|
{steps.create("Advanced", {
|
||||||
title="Advanced"
|
isCollapsed: expandedStep !== 'advanced',
|
||||||
isVisible={true}
|
isCompleted: hasFiles && hasResults,
|
||||||
isCollapsed={expandedStep !== 'advanced'}
|
onCollapsedClick: () => {
|
||||||
isCompleted={hasFiles && hasResults}
|
|
||||||
onCollapsedClick={() => {
|
|
||||||
if (!hasFiles) return; // Only allow if files are selected
|
if (!hasFiles) return; // Only allow if files are selected
|
||||||
setExpandedStep(expandedStep === 'advanced' ? null : 'advanced');
|
setExpandedStep(expandedStep === 'advanced' ? null : 'advanced');
|
||||||
}}
|
},
|
||||||
completedMessage={hasFiles && hasResults && expandedStep !== 'advanced' ? "OCR processing completed" : undefined}
|
}, (
|
||||||
>
|
|
||||||
<AdvancedOCRSettings
|
<AdvancedOCRSettings
|
||||||
advancedOptions={ocrParams.parameters.additionalOptions}
|
advancedOptions={ocrParams.parameters.additionalOptions}
|
||||||
ocrRenderType={ocrParams.parameters.ocrRenderType}
|
ocrRenderType={ocrParams.parameters.ocrRenderType}
|
||||||
onParameterChange={ocrParams.updateParameter}
|
onParameterChange={ocrParams.updateParameter}
|
||||||
disabled={endpointLoading}
|
disabled={endpointLoading}
|
||||||
/>
|
/>
|
||||||
</ToolStep>
|
))}
|
||||||
|
|
||||||
{/* Process Button - Available after all configuration */}
|
{/* Process Button - Available after all configuration */}
|
||||||
{hasValidSettings && !hasResults && (
|
{hasValidSettings && !hasResults && (
|
||||||
<Box mt="md">
|
|
||||||
<OperationButton
|
<OperationButton
|
||||||
onClick={handleOCR}
|
onClick={handleOCR}
|
||||||
isLoading={ocrOperation.isLoading}
|
isLoading={ocrOperation.isLoading}
|
||||||
disabled={!ocrParams.validateParameters() || !hasFiles || !endpointEnabled}
|
disabled={!ocrParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||||
loadingText={t("loading")}
|
loadingText={t("loading")}
|
||||||
submitText="Process OCR and Review"
|
submitText={t("ocr.operation.submit", "Process OCR and Review")}
|
||||||
/>
|
/>
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Results Step */}
|
{/* Results Step */}
|
||||||
<ToolStep
|
{steps.createResultsStep({
|
||||||
title="Results"
|
isVisible: resultsVisible,
|
||||||
isVisible={resultsVisible}
|
operation: ocrOperation,
|
||||||
>
|
title: t("ocr.results.title", "OCR Results"),
|
||||||
<Stack gap="sm">
|
onFileClick: handleThumbnailClick
|
||||||
{ocrOperation.status && (
|
})}
|
||||||
<Text size="sm" c="dimmed">{ocrOperation.status}</Text>
|
</ToolStepProvider>
|
||||||
)}
|
|
||||||
|
|
||||||
<ErrorNotification
|
|
||||||
error={ocrOperation.errorMessage}
|
|
||||||
onClose={ocrOperation.clearError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{ocrOperation.downloadUrl && (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
href={ocrOperation.downloadUrl}
|
|
||||||
download={ocrOperation.downloadFilename}
|
|
||||||
leftSection={<DownloadIcon />}
|
|
||||||
color="green"
|
|
||||||
fullWidth
|
|
||||||
mb="md"
|
|
||||||
>
|
|
||||||
{t("download", "Download")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ResultsPreview
|
|
||||||
files={previewResults}
|
|
||||||
onFileClick={handleThumbnailClick}
|
|
||||||
isGeneratingThumbnails={ocrOperation.isGeneratingThumbnails}
|
|
||||||
title="OCR Results"
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</ToolStep>
|
|
||||||
</ToolStepContainer>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { Button, Stack, Text } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
import { useFileContext } from "../contexts/FileContext";
|
import { useFileContext } from "../contexts/FileContext";
|
||||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||||
|
|
||||||
import ToolStep, { ToolStepContainer } from "../components/tools/shared/ToolStep";
|
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||||
import OperationButton from "../components/tools/shared/OperationButton";
|
import OperationButton from "../components/tools/shared/OperationButton";
|
||||||
import ErrorNotification from "../components/tools/shared/ErrorNotification";
|
|
||||||
import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator";
|
|
||||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
|
||||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||||
|
|
||||||
import { useSplitParameters } from "../hooks/tools/split/useSplitParameters";
|
import { useSplitParameters } from "../hooks/tools/split/useSplitParameters";
|
||||||
@ -66,42 +62,26 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const hasFiles = selectedFiles.length > 0;
|
const hasFiles = selectedFiles.length > 0;
|
||||||
const hasResults = splitOperation.downloadUrl !== null;
|
const hasResults = splitOperation.downloadUrl !== null;
|
||||||
const filesCollapsed = hasFiles;
|
const filesCollapsed = hasFiles;
|
||||||
const settingsCollapsed = hasResults;
|
const settingsCollapsed = !hasFiles || hasResults;
|
||||||
|
|
||||||
const previewResults = useMemo(() =>
|
const steps = createToolSteps();
|
||||||
splitOperation.files?.map((file, index) => ({
|
|
||||||
file,
|
|
||||||
thumbnail: splitOperation.thumbnails[index]
|
|
||||||
})) || [],
|
|
||||||
[splitOperation.files, splitOperation.thumbnails]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolStepContainer>
|
|
||||||
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||||
|
<ToolStepProvider>
|
||||||
{/* Files Step */}
|
{/* Files Step */}
|
||||||
<ToolStep
|
{steps.createFilesStep({
|
||||||
title="Files"
|
selectedFiles,
|
||||||
isVisible={true}
|
isCollapsed: filesCollapsed,
|
||||||
isCollapsed={filesCollapsed}
|
placeholder: "Select a PDF file in the main view to get started"
|
||||||
isCompleted={filesCollapsed}
|
})}
|
||||||
completedMessage={hasFiles ? `Selected: ${selectedFiles[0]?.name}` : undefined}
|
|
||||||
>
|
|
||||||
<FileStatusIndicator
|
|
||||||
selectedFiles={selectedFiles}
|
|
||||||
placeholder="Select a PDF file in the main view to get started"
|
|
||||||
/>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
{/* Settings Step */}
|
{/* Settings Step */}
|
||||||
<ToolStep
|
{steps.create("Settings", {
|
||||||
title="Settings"
|
isCollapsed: settingsCollapsed,
|
||||||
isVisible={hasFiles}
|
isCompleted: hasResults,
|
||||||
isCollapsed={settingsCollapsed}
|
onCollapsedClick: hasResults ? handleSettingsReset : undefined,
|
||||||
isCompleted={settingsCollapsed}
|
}, (
|
||||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
|
||||||
completedMessage={settingsCollapsed ? "Split completed" : undefined}
|
|
||||||
>
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<SplitSettings
|
<SplitSettings
|
||||||
parameters={splitParams.parameters}
|
parameters={splitParams.parameters}
|
||||||
@ -109,7 +89,10 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
disabled={endpointLoading}
|
disabled={endpointLoading}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{splitParams.parameters.mode && (
|
</Stack>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{!hasResults && (
|
||||||
<OperationButton
|
<OperationButton
|
||||||
onClick={handleSplit}
|
onClick={handleSplit}
|
||||||
isLoading={splitOperation.isLoading}
|
isLoading={splitOperation.isLoading}
|
||||||
@ -118,48 +101,16 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
submitText={t("split.submit", "Split PDF")}
|
submitText={t("split.submit", "Split PDF")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
|
||||||
</ToolStep>
|
|
||||||
|
|
||||||
{/* Results Step */}
|
{/* Results Step */}
|
||||||
<ToolStep
|
{steps.createResultsStep({
|
||||||
title="Results"
|
isVisible: hasResults,
|
||||||
isVisible={hasResults}
|
operation: splitOperation,
|
||||||
>
|
title: "Split Results",
|
||||||
<Stack gap="sm">
|
onFileClick: handleThumbnailClick
|
||||||
{splitOperation.status && (
|
})}
|
||||||
<Text size="sm" c="dimmed">{splitOperation.status}</Text>
|
</ToolStepProvider>
|
||||||
)}
|
|
||||||
|
|
||||||
<ErrorNotification
|
|
||||||
error={splitOperation.errorMessage}
|
|
||||||
onClose={splitOperation.clearError}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{splitOperation.downloadUrl && (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
href={splitOperation.downloadUrl}
|
|
||||||
download="split_output.zip"
|
|
||||||
leftSection={<DownloadIcon />}
|
|
||||||
color="green"
|
|
||||||
fullWidth
|
|
||||||
mb="md"
|
|
||||||
>
|
|
||||||
{t("download", "Download")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ResultsPreview
|
|
||||||
files={previewResults}
|
|
||||||
onFileClick={handleThumbnailClick}
|
|
||||||
isGeneratingThumbnails={splitOperation.isGeneratingThumbnails}
|
|
||||||
title="Split Results"
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</ToolStep>
|
|
||||||
</Stack>
|
|
||||||
</ToolStepContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user