suggested tool structure

This commit is contained in:
Connor Yoh 2025-08-13 15:13:01 +01:00
parent ee6f7a2939
commit fa4098504a
10 changed files with 160 additions and 102 deletions

View File

@ -1,25 +1,17 @@
import React from 'react'; import React from 'react';
import { Text, Box, Flex, ActionIcon, Tooltip } from '@mantine/core'; import { Text } from '@mantine/core';
import PushPinIcon from '@mui/icons-material/PushPin';
import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined';
import { useFileContext } from '../../../contexts/FileContext';
export interface FileStatusIndicatorProps { export interface FileStatusIndicatorProps {
selectedFiles?: File[]; selectedFiles?: File[];
isCompleted?: boolean;
placeholder?: string; placeholder?: string;
showFileName?: boolean;
showPinControls?: boolean;
} }
const FileStatusIndicator = ({ const FileStatusIndicator = ({
selectedFiles = [], selectedFiles = [],
isCompleted = false, placeholder = "Select a PDF file in the main view to get started"
placeholder = "Select a PDF file in the main view to get started",
showFileName = true,
showPinControls = true
}: FileStatusIndicatorProps) => { }: FileStatusIndicatorProps) => {
const { pinFile, unpinFile, isFilePinned } = useFileContext();
// Only show content when no files are selected
if (selectedFiles.length === 0) { if (selectedFiles.length === 0) {
return ( return (
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
@ -28,19 +20,8 @@ const FileStatusIndicator = ({
); );
} }
if (isCompleted) { // Return nothing when files are selected
return ( return null;
<Text size="sm" c="green">
Selected: {showFileName ? selectedFiles[0]?.name : `${selectedFiles.length} file${selectedFiles.length > 1 ? 's' : ''}`}
</Text>
);
}
return (
<Text size="sm" c="blue">
Selected: {showFileName ? selectedFiles[0]?.name : `${selectedFiles.length} file${selectedFiles.length > 1 ? 's' : ''}`}
</Text>
);
} }
export default FileStatusIndicator; export default FileStatusIndicator;

View File

@ -0,0 +1,42 @@
import React from 'react';
import { Stack, Text, Divider, SimpleGrid, Card, Group } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { useSuggestedTools } from '../../../hooks/useSuggestedTools';
export interface SuggestedToolsSectionProps {}
export function SuggestedToolsSection(): React.ReactElement {
const { t } = useTranslation();
const suggestedTools = useSuggestedTools();
return (
<Stack gap="md">
<Divider />
<Text size="lg" fw={600}>
{t('editYourNewFiles', 'Edit your new File(s)')}
</Text>
<SimpleGrid cols={2} spacing="sm">
{suggestedTools.map((tool) => {
const IconComponent = tool.icon;
return (
<Card
key={tool.name}
p="sm"
withBorder
style={{ cursor: 'pointer' }}
onClick={tool.navigate}
>
<Group gap="xs">
<IconComponent fontSize="small" />
<Text size="sm" fw={500}>
{tool.title}
</Text>
</Group>
</Card>
);
})}
</SimpleGrid>
</Stack>
);
}

View File

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import DownloadIcon from '@mui/icons-material/Download'; import DownloadIcon from '@mui/icons-material/Download';
import ErrorNotification from './ErrorNotification'; import ErrorNotification from './ErrorNotification';
import ReviewPanel from './ReviewPanel'; import ReviewPanel from './ReviewPanel';
import { SuggestedToolsSection } from './SuggestedToolsSection';
import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation'; import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation';
export interface ReviewToolStepProps<TParams = any> { export interface ReviewToolStepProps<TParams = any> {
@ -31,10 +32,6 @@ export function createReviewToolStep<TParams = any>(
_noPadding: true _noPadding: true
}, ( }, (
<Stack gap="sm" > <Stack gap="sm" >
{operation.status && (
<Text size="sm" c="dimmed">{operation.status}</Text>
)}
<ErrorNotification <ErrorNotification
error={operation.errorMessage} error={operation.errorMessage}
onClose={operation.clearError} onClose={operation.clearError}
@ -62,6 +59,8 @@ export function createReviewToolStep<TParams = any>(
{t("download", "Download")} {t("download", "Download")}
</Button> </Button>
)} )}
<SuggestedToolsSection />
</Stack> </Stack>
)); ));
} }

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { Stack } from '@mantine/core';
import { createToolSteps, ToolStepProvider } from './ToolStep'; import { createToolSteps, ToolStepProvider } from './ToolStep';
import OperationButton from './OperationButton'; import OperationButton from './OperationButton';
import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation'; import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation';
@ -58,44 +59,46 @@ export function createToolFlow(config: ToolFlowConfig) {
const steps = createToolSteps(); const steps = createToolSteps();
return ( return (
<ToolStepProvider> <Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
{/* Files Step */} <ToolStepProvider>
{steps.createFilesStep({ {/* Files Step */}
selectedFiles: config.files.selectedFiles, {steps.createFilesStep({
isCollapsed: config.files.isCollapsed, selectedFiles: config.files.selectedFiles,
placeholder: config.files.placeholder, isCollapsed: config.files.isCollapsed,
onCollapsedClick: config.files.onCollapsedClick placeholder: config.files.placeholder,
})} onCollapsedClick: config.files.onCollapsedClick
})}
{/* Middle Steps */} {/* Middle Steps */}
{config.steps.map((stepConfig, index) => {config.steps.map((stepConfig, index) =>
steps.create(stepConfig.title, { steps.create(stepConfig.title, {
isVisible: stepConfig.isVisible, isVisible: stepConfig.isVisible,
isCollapsed: stepConfig.isCollapsed, isCollapsed: stepConfig.isCollapsed,
onCollapsedClick: stepConfig.onCollapsedClick, onCollapsedClick: stepConfig.onCollapsedClick,
tooltip: stepConfig.tooltip tooltip: stepConfig.tooltip
}, stepConfig.content) }, stepConfig.content)
)} )}
{/* Execute Button */} {/* Execute Button */}
{config.executeButton && config.executeButton.isVisible !== false && ( {config.executeButton && config.executeButton.isVisible !== false && (
<OperationButton <OperationButton
onClick={config.executeButton.onClick} onClick={config.executeButton.onClick}
isLoading={config.review.operation.isLoading} isLoading={config.review.operation.isLoading}
disabled={config.executeButton.disabled} disabled={config.executeButton.disabled}
loadingText={config.executeButton.loadingText} loadingText={config.executeButton.loadingText}
submitText={config.executeButton.text} submitText={config.executeButton.text}
data-testid={config.executeButton.testId} data-testid={config.executeButton.testId}
/> />
)} )}
{/* Review Step */} {/* Review Step */}
{steps.createReviewStep({ {steps.createReviewStep({
isVisible: config.review.isVisible, isVisible: config.review.isVisible,
operation: config.review.operation, operation: config.review.operation,
title: config.review.title, title: config.review.title,
onFileClick: config.review.onFileClick onFileClick: config.review.onFileClick
})} })}
</ToolStepProvider> </ToolStepProvider>
</Stack>
); );
} }

View File

@ -0,0 +1,59 @@
import { useMemo } from 'react';
import { useToolWorkflow } from '../contexts/ToolWorkflowContext';
// Material UI Icons
import CompressIcon from '@mui/icons-material/Compress';
import SwapHorizIcon from '@mui/icons-material/SwapHoriz';
import CleaningServicesIcon from '@mui/icons-material/CleaningServices';
import CropIcon from '@mui/icons-material/Crop';
import TextFieldsIcon from '@mui/icons-material/TextFields';
export interface SuggestedTool {
name: string;
title: string;
icon: React.ComponentType<any>;
navigate: () => void;
}
const ALL_SUGGESTED_TOOLS: Omit<SuggestedTool, 'navigate'>[] = [
{
name: 'compress',
title: 'Compress',
icon: CompressIcon
},
{
name: 'convert',
title: 'Convert',
icon: SwapHorizIcon
},
{
name: 'sanitize',
title: 'Sanitize',
icon: CleaningServicesIcon
},
{
name: 'split',
title: 'Split',
icon: CropIcon
},
{
name: 'ocr',
title: 'OCR',
icon: TextFieldsIcon
}
];
export function useSuggestedTools(): SuggestedTool[] {
const { handleToolSelect, selectedToolKey } = useToolWorkflow();
return useMemo(() => {
// Filter out the current tool
const filteredTools = ALL_SUGGESTED_TOOLS.filter(tool => tool.name !== selectedToolKey);
// Add navigation function to each tool
return filteredTools.map(tool => ({
...tool,
navigate: () => handleToolSelect(tool.name)
}));
}, [selectedToolKey, handleToolSelect]);
}

View File

@ -1,5 +1,4 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig"; import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
@ -63,9 +62,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null; const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
const settingsCollapsed = !hasFiles || hasResults; const settingsCollapsed = !hasFiles || hasResults;
return ( return createToolFlow({
<Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
{createToolFlow({
files: { files: {
selectedFiles, selectedFiles,
isCollapsed: hasFiles && !hasResults, isCollapsed: hasFiles && !hasResults,
@ -96,9 +93,7 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
title: t("compress.title", "Compression Results"), title: t("compress.title", "Compression Results"),
onFileClick: handleThumbnailClick onFileClick: handleThumbnailClick
} }
})} });
</Stack>
);
} }

View File

@ -1,5 +1,4 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig"; import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
@ -100,9 +99,7 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
setCurrentMode('convert'); setCurrentMode('convert');
}; };
return ( return createToolFlow({
<Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
{createToolFlow({
files: { files: {
selectedFiles, selectedFiles,
isCollapsed: filesCollapsed, isCollapsed: filesCollapsed,
@ -137,9 +134,7 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
onFileClick: handleThumbnailClick, onFileClick: handleThumbnailClick,
testId: "conversion-results" testId: "conversion-results"
} }
})} });
</Stack>
);
}; };
export default Convert; export default Convert;

View File

@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig"; import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
@ -80,15 +79,12 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
}; };
const filesCollapsed = expandedStep !== 'files';
const settingsCollapsed = expandedStep !== 'settings'; const settingsCollapsed = expandedStep !== 'settings';
return ( return createToolFlow({
<Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
{createToolFlow({
files: { files: {
selectedFiles, selectedFiles,
isCollapsed: hasFiles && !hasResults && filesCollapsed, isCollapsed: hasFiles || hasResults,
}, },
steps: [ steps: [
{ {
@ -137,9 +133,7 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
title: t("ocr.results.title", "OCR Results"), title: t("ocr.results.title", "OCR Results"),
onFileClick: handleThumbnailClick onFileClick: handleThumbnailClick
} }
})} });
</Stack>
);
} }
export default OCR; export default OCR;

View File

@ -1,5 +1,4 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig"; import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useToolFileSelection } from "../contexts/FileSelectionContext"; import { useToolFileSelection } from "../contexts/FileSelectionContext";
@ -64,9 +63,7 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const filesCollapsed = hasFiles || hasResults; const filesCollapsed = hasFiles || hasResults;
const settingsCollapsed = !hasFiles || hasResults; const settingsCollapsed = !hasFiles || hasResults;
return ( return createToolFlow({
<Stack gap="sm" p="sm" style={{ height: '100vh', overflow: 'auto' }}>
{createToolFlow({
files: { files: {
selectedFiles, selectedFiles,
isCollapsed: filesCollapsed, isCollapsed: filesCollapsed,
@ -97,9 +94,7 @@ const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
title: t("sanitize.sanitizationResults", "Sanitization Results"), title: t("sanitize.sanitizationResults", "Sanitization Results"),
onFileClick: handleThumbnailClick onFileClick: handleThumbnailClick
} }
})} });
</Stack>
);
} }
export default Sanitize; export default Sanitize;

View File

@ -1,5 +1,4 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Stack } from "@mantine/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig"; import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext"; import { useFileContext } from "../contexts/FileContext";
@ -63,9 +62,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const filesCollapsed = hasFiles; const filesCollapsed = hasFiles;
const settingsCollapsed = !hasFiles || hasResults; const settingsCollapsed = !hasFiles || hasResults;
return ( return createToolFlow({
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
{createToolFlow({
files: { files: {
selectedFiles, selectedFiles,
isCollapsed: filesCollapsed, isCollapsed: filesCollapsed,
@ -96,9 +93,7 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
title: "Split Results", title: "Split Results",
onFileClick: handleThumbnailClick onFileClick: handleThumbnailClick
} }
})} });
</Stack>
);
} }
export default Split; export default Split;