mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Workflow
This commit is contained in:
parent
9d2ca3c8c8
commit
3e4afd166e
106
frontend/src/components/tools/shared/createToolFlow.tsx
Normal file
106
frontend/src/components/tools/shared/createToolFlow.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { Stack } from '@mantine/core';
|
||||
import { createToolSteps, ToolStepProvider } from './ToolStep';
|
||||
import OperationButton from './OperationButton';
|
||||
import { ToolOperationHook } from '../../../hooks/tools/shared/useToolOperation';
|
||||
|
||||
export interface FilesStepConfig {
|
||||
selectedFiles: File[];
|
||||
isCollapsed?: boolean;
|
||||
placeholder?: string;
|
||||
onCollapsedClick?: () => void;
|
||||
}
|
||||
|
||||
export interface MiddleStepConfig {
|
||||
title: string;
|
||||
isVisible?: boolean;
|
||||
isCollapsed?: boolean;
|
||||
isCompleted?: boolean;
|
||||
onCollapsedClick?: () => void;
|
||||
completedMessage?: string;
|
||||
content: React.ReactNode;
|
||||
tooltip?: {
|
||||
content?: React.ReactNode;
|
||||
tips?: any[];
|
||||
header?: {
|
||||
title: string;
|
||||
logo?: React.ReactNode;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExecuteButtonConfig {
|
||||
text: string;
|
||||
loadingText: string;
|
||||
onClick: () => Promise<void>;
|
||||
isVisible?: boolean;
|
||||
disabled?: boolean;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export interface ResultsStepConfig {
|
||||
isVisible: boolean;
|
||||
operation: ToolOperationHook<any>;
|
||||
title: string;
|
||||
onFileClick?: (file: File) => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export interface ToolFlowConfig {
|
||||
files: FilesStepConfig;
|
||||
steps: MiddleStepConfig[];
|
||||
executeButton?: ExecuteButtonConfig;
|
||||
results: ResultsStepConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a flexible tool flow with configurable steps and state management left to the tool.
|
||||
* Reduces boilerplate while allowing tools to manage their own collapse/expansion logic.
|
||||
*/
|
||||
export function createToolFlow(config: ToolFlowConfig) {
|
||||
const steps = createToolSteps();
|
||||
|
||||
return (
|
||||
<ToolStepProvider>
|
||||
{/* Files Step */}
|
||||
{steps.createFilesStep({
|
||||
selectedFiles: config.files.selectedFiles,
|
||||
isCollapsed: config.files.isCollapsed,
|
||||
placeholder: config.files.placeholder,
|
||||
onCollapsedClick: config.files.onCollapsedClick
|
||||
})}
|
||||
|
||||
{/* Middle Steps */}
|
||||
{config.steps.map((stepConfig, index) =>
|
||||
steps.create(stepConfig.title, {
|
||||
isVisible: stepConfig.isVisible,
|
||||
isCollapsed: stepConfig.isCollapsed,
|
||||
isCompleted: stepConfig.isCompleted,
|
||||
onCollapsedClick: stepConfig.onCollapsedClick,
|
||||
completedMessage: stepConfig.completedMessage,
|
||||
tooltip: stepConfig.tooltip
|
||||
}, stepConfig.content)
|
||||
)}
|
||||
|
||||
{/* Execute Button */}
|
||||
{config.executeButton && config.executeButton.isVisible !== false && (
|
||||
<OperationButton
|
||||
onClick={config.executeButton.onClick}
|
||||
isLoading={config.results.operation.isLoading}
|
||||
disabled={config.executeButton.disabled}
|
||||
loadingText={config.executeButton.loadingText}
|
||||
submitText={config.executeButton.text}
|
||||
data-testid={config.executeButton.testId}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Results Step */}
|
||||
{steps.createResultsStep({
|
||||
isVisible: config.results.isVisible,
|
||||
operation: config.results.operation,
|
||||
title: config.results.title,
|
||||
onFileClick: config.results.onFileClick
|
||||
})}
|
||||
</ToolStepProvider>
|
||||
);
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||
|
||||
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
|
||||
import CompressSettings from "../components/tools/compress/CompressSettings";
|
||||
|
||||
@ -65,49 +64,41 @@ const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const filesCollapsed = hasFiles;
|
||||
const settingsCollapsed = !hasFiles || hasResults;
|
||||
|
||||
|
||||
const steps = createToolSteps();
|
||||
|
||||
return (
|
||||
<Stack gap="md" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||
<ToolStepProvider>
|
||||
{/* Files Step */}
|
||||
{steps.createFilesStep({
|
||||
{createToolFlow({
|
||||
files: {
|
||||
selectedFiles,
|
||||
isCollapsed: filesCollapsed
|
||||
})}
|
||||
|
||||
{/* Settings Step */}
|
||||
{steps.create("Settings", {
|
||||
},
|
||||
steps: [{
|
||||
title: "Settings",
|
||||
isCollapsed: settingsCollapsed,
|
||||
isCompleted: hasResults,
|
||||
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
|
||||
completedMessage: t("compress.header", "Compression completed"),
|
||||
tooltip: compressTips
|
||||
}, (
|
||||
<Stack gap="md">
|
||||
tooltip: compressTips,
|
||||
content: (
|
||||
<CompressSettings
|
||||
parameters={compressParams.parameters}
|
||||
onParameterChange={compressParams.updateParameter}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
<OperationButton
|
||||
onClick={handleCompress}
|
||||
isLoading={compressOperation.isLoading}
|
||||
disabled={!compressParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||
loadingText={t("loading")}
|
||||
submitText={t("compress.submit", "Compress")}
|
||||
/>
|
||||
{/* Results Step */}
|
||||
{steps.createResultsStep({
|
||||
)
|
||||
}],
|
||||
executeButton: {
|
||||
text: t("compress.submit", "Compress"),
|
||||
loadingText: t("loading"),
|
||||
onClick: handleCompress,
|
||||
disabled: !compressParams.validateParameters() || !hasFiles || !endpointEnabled
|
||||
},
|
||||
results: {
|
||||
isVisible: hasResults,
|
||||
operation: compressOperation,
|
||||
title: t("compress.title", "Compression Results"),
|
||||
onFileClick: handleThumbnailClick
|
||||
}
|
||||
})}
|
||||
</ToolStepProvider>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||
|
||||
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
|
||||
import ConvertSettings from "../components/tools/convert/ConvertSettings";
|
||||
|
||||
@ -101,26 +100,21 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
setCurrentMode('convert');
|
||||
};
|
||||
|
||||
const steps = createToolSteps();
|
||||
|
||||
return (
|
||||
<div className="h-full max-h-screen overflow-y-auto" ref={scrollContainerRef}>
|
||||
<Stack gap="sm" p="sm">
|
||||
<ToolStepProvider>
|
||||
{/* Files Step */}
|
||||
{steps.createFilesStep({
|
||||
{createToolFlow({
|
||||
files: {
|
||||
selectedFiles,
|
||||
isCollapsed: filesCollapsed,
|
||||
placeholder: t("convert.selectFilesPlaceholder", "Select files in the main view to get started")
|
||||
})}
|
||||
|
||||
{/* Settings Step */}
|
||||
{steps.create(t("convert.settings", "Settings"), {
|
||||
},
|
||||
steps: [{
|
||||
title: t("convert.settings", "Settings"),
|
||||
isCollapsed: settingsCollapsed,
|
||||
isCompleted: settingsCollapsed,
|
||||
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
|
||||
}, (
|
||||
<Stack gap="sm">
|
||||
content: (
|
||||
<ConvertSettings
|
||||
parameters={convertParams.parameters}
|
||||
onParameterChange={convertParams.updateParameter}
|
||||
@ -128,27 +122,24 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
selectedFiles={selectedFiles}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
{!hasResults && (
|
||||
<OperationButton
|
||||
onClick={handleConvert}
|
||||
isLoading={convertOperation.isLoading}
|
||||
disabled={!convertParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||
loadingText={t("convert.converting", "Converting...")}
|
||||
submitText={t("convert.convertFiles", "Convert Files")}
|
||||
data-testid="convert-button"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Results Step */}
|
||||
{steps.createResultsStep({
|
||||
)
|
||||
}],
|
||||
executeButton: {
|
||||
text: t("convert.convertFiles", "Convert Files"),
|
||||
loadingText: t("convert.converting", "Converting..."),
|
||||
onClick: handleConvert,
|
||||
isVisible: !hasResults,
|
||||
disabled: !convertParams.validateParameters() || !hasFiles || !endpointEnabled,
|
||||
testId: "convert-button"
|
||||
},
|
||||
results: {
|
||||
isVisible: hasResults,
|
||||
operation: convertOperation,
|
||||
title: t("convert.conversionResults", "Conversion Results"),
|
||||
onFileClick: handleThumbnailClick
|
||||
onFileClick: handleThumbnailClick,
|
||||
testId: "conversion-results"
|
||||
}
|
||||
})}
|
||||
</ToolStepProvider>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Stack, Box } from "@mantine/core";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||
|
||||
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
|
||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||
import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
|
||||
@ -75,78 +74,66 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
};
|
||||
|
||||
|
||||
const resultsVisible = hasResults;
|
||||
|
||||
const filesCollapsed = expandedStep !== 'files';
|
||||
const settingsCollapsed = expandedStep !== 'settings';
|
||||
|
||||
|
||||
const steps = createToolSteps();
|
||||
|
||||
return (
|
||||
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||
<ToolStepProvider>
|
||||
{/* Files Step */}
|
||||
{steps.createFilesStep({
|
||||
{createToolFlow({
|
||||
files: {
|
||||
selectedFiles,
|
||||
isCollapsed: hasFiles && filesCollapsed,
|
||||
})}
|
||||
|
||||
{/* Settings Step */}
|
||||
{steps.create("Settings", {
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
title: "Settings",
|
||||
isCollapsed: !hasFiles || settingsCollapsed,
|
||||
isCompleted: hasFiles && hasValidSettings,
|
||||
onCollapsedClick: () => {
|
||||
if (!hasFiles) return; // Only allow if files are selected
|
||||
setExpandedStep(expandedStep === 'settings' ? null : 'settings');
|
||||
},
|
||||
tooltip: ocrTips
|
||||
}, (
|
||||
<Stack gap="sm">
|
||||
tooltip: ocrTips,
|
||||
content: (
|
||||
<OCRSettings
|
||||
parameters={ocrParams.parameters}
|
||||
onParameterChange={ocrParams.updateParameter}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{/* Advanced Step */}
|
||||
{steps.create("Advanced", {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Advanced",
|
||||
isCollapsed: expandedStep !== 'advanced',
|
||||
isCompleted: hasFiles && hasResults,
|
||||
onCollapsedClick: () => {
|
||||
if (!hasFiles) return; // Only allow if files are selected
|
||||
setExpandedStep(expandedStep === 'advanced' ? null : 'advanced');
|
||||
},
|
||||
}, (
|
||||
content: (
|
||||
<AdvancedOCRSettings
|
||||
advancedOptions={ocrParams.parameters.additionalOptions}
|
||||
ocrRenderType={ocrParams.parameters.ocrRenderType}
|
||||
onParameterChange={ocrParams.updateParameter}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Process Button - Available after all configuration */}
|
||||
{hasValidSettings && !hasResults && (
|
||||
<OperationButton
|
||||
onClick={handleOCR}
|
||||
isLoading={ocrOperation.isLoading}
|
||||
disabled={!ocrParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||
loadingText={t("loading")}
|
||||
submitText={t("ocr.operation.submit", "Process OCR and Review")}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Results Step */}
|
||||
{steps.createResultsStep({
|
||||
isVisible: resultsVisible,
|
||||
)
|
||||
}
|
||||
],
|
||||
executeButton: {
|
||||
text: t("ocr.operation.submit", "Process OCR and Review"),
|
||||
loadingText: t("loading"),
|
||||
onClick: handleOCR,
|
||||
isVisible: hasValidSettings && !hasResults,
|
||||
disabled: !ocrParams.validateParameters() || !hasFiles || !endpointEnabled
|
||||
},
|
||||
results: {
|
||||
isVisible: hasResults,
|
||||
operation: ocrOperation,
|
||||
title: t("ocr.results.title", "OCR Results"),
|
||||
onFileClick: handleThumbnailClick
|
||||
}
|
||||
})}
|
||||
</ToolStepProvider>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection } from "../contexts/FileSelectionContext";
|
||||
|
||||
import { createToolSteps, ToolStepProvider } from "../components/tools/shared/ToolStep";
|
||||
import OperationButton from "../components/tools/shared/OperationButton";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import SplitSettings from "../components/tools/split/SplitSettings";
|
||||
|
||||
import { useSplitParameters } from "../hooks/tools/split/useSplitParameters";
|
||||
@ -64,52 +63,41 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const filesCollapsed = hasFiles;
|
||||
const settingsCollapsed = !hasFiles || hasResults;
|
||||
|
||||
const steps = createToolSteps();
|
||||
|
||||
return (
|
||||
<Stack gap="sm" h="100%" p="sm" style={{ overflow: 'auto' }}>
|
||||
<ToolStepProvider>
|
||||
{/* Files Step */}
|
||||
{steps.createFilesStep({
|
||||
{createToolFlow({
|
||||
files: {
|
||||
selectedFiles,
|
||||
isCollapsed: filesCollapsed,
|
||||
placeholder: "Select a PDF file in the main view to get started"
|
||||
})}
|
||||
|
||||
{/* Settings Step */}
|
||||
{steps.create("Settings", {
|
||||
},
|
||||
steps: [{
|
||||
title: "Settings",
|
||||
isCollapsed: settingsCollapsed,
|
||||
isCompleted: hasResults,
|
||||
onCollapsedClick: hasResults ? handleSettingsReset : undefined,
|
||||
}, (
|
||||
<Stack gap="sm">
|
||||
content: (
|
||||
<SplitSettings
|
||||
parameters={splitParams.parameters}
|
||||
onParameterChange={splitParams.updateParameter}
|
||||
disabled={endpointLoading}
|
||||
/>
|
||||
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{!hasResults && (
|
||||
<OperationButton
|
||||
onClick={handleSplit}
|
||||
isLoading={splitOperation.isLoading}
|
||||
disabled={!splitParams.validateParameters() || !hasFiles || !endpointEnabled}
|
||||
loadingText={t("loading")}
|
||||
submitText={t("split.submit", "Split PDF")}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Results Step */}
|
||||
{steps.createResultsStep({
|
||||
)
|
||||
}],
|
||||
executeButton: {
|
||||
text: t("split.submit", "Split PDF"),
|
||||
loadingText: t("loading"),
|
||||
onClick: handleSplit,
|
||||
isVisible: !hasResults,
|
||||
disabled: !splitParams.validateParameters() || !hasFiles || !endpointEnabled
|
||||
},
|
||||
results: {
|
||||
isVisible: hasResults,
|
||||
operation: splitOperation,
|
||||
title: "Split Results",
|
||||
onFileClick: handleThumbnailClick
|
||||
}
|
||||
})}
|
||||
</ToolStepProvider>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user