V2 reduce tool boilerplate (#4313)

# Description of Changes
Reduce boilerplate in tool frontends by creating a base frontend hook
for the simple tools to use.

I've done all the simple tools here. It'd be nice to add in some of the
more complex tools as well in the future if we can figure out how.
This commit is contained in:
James Brunton
2025-08-28 09:59:38 +00:00
committed by GitHub
parent e142af2863
commit 442b373ff4
10 changed files with 291 additions and 534 deletions
+21 -62
View File
@@ -1,96 +1,55 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
import { useChangePermissionsParameters } from "../hooks/tools/changePermissions/useChangePermissionsParameters";
import { useChangePermissionsOperation } from "../hooks/tools/changePermissions/useChangePermissionsOperation";
import { useChangePermissionsTips } from "../components/tooltips/useChangePermissionsTips";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const ChangePermissions = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const ChangePermissions = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const changePermissionsParams = useChangePermissionsParameters();
const changePermissionsOperation = useChangePermissionsOperation();
const changePermissionsTips = useChangePermissionsTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(changePermissionsParams.getEndpointName());
useEffect(() => {
changePermissionsOperation.resetResults();
onPreviewFile?.(null);
}, [changePermissionsParams.parameters]);
const handleChangePermissions = async () => {
try {
await changePermissionsOperation.executeOperation(changePermissionsParams.parameters, selectedFiles);
if (changePermissionsOperation.files && onComplete) {
onComplete(changePermissionsOperation.files);
}
} catch (error) {
if (onError) {
onError(
error instanceof Error ? error.message : t("changePermissions.error.failed", "Change permissions operation failed")
);
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "changePermissions");
};
const handleSettingsReset = () => {
changePermissionsOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = changePermissionsOperation.files.length > 0 || changePermissionsOperation.downloadUrl !== null;
const settingsCollapsed = !hasFiles || hasResults;
const base = useBaseTool(
'changePermissions',
useChangePermissionsParameters,
useChangePermissionsOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: t("changePermissions.title", "Document Permissions"),
isCollapsed: settingsCollapsed,
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: changePermissionsTips,
content: (
<ChangePermissionsSettings
parameters={changePermissionsParams.parameters}
onParameterChange={changePermissionsParams.updateParameter}
disabled={endpointLoading}
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("changePermissions.submit", "Change Permissions"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleChangePermissions,
disabled: !changePermissionsParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: changePermissionsOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("changePermissions.results.title", "Modified PDFs"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+21 -61
View File
@@ -1,95 +1,55 @@
import React, { use, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import CompressSettings from "../components/tools/compress/CompressSettings";
import { useCompressParameters } from "../hooks/tools/compress/useCompressParameters";
import { useCompressOperation } from "../hooks/tools/compress/useCompressOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
import { useCompressTips } from "../components/tooltips/useCompressTips";
const Compress = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const Compress = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const compressParams = useCompressParameters();
const compressOperation = useCompressOperation();
const compressTips = useCompressTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("compress-pdf");
useEffect(() => {
compressOperation.resetResults();
onPreviewFile?.(null);
}, [compressParams.parameters]);
const handleCompress = async () => {
try {
await compressOperation.executeOperation(compressParams.parameters, selectedFiles);
if (compressOperation.files && onComplete) {
onComplete(compressOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : "Compress operation failed");
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "compress");
};
const handleSettingsReset = () => {
compressOperation.resetResults();
onPreviewFile?.(null); };
const hasFiles = selectedFiles.length > 0;
const hasResults = compressOperation.files.length > 0 || compressOperation.downloadUrl !== null;
const settingsCollapsed = !hasFiles || hasResults;
const base = useBaseTool(
'compress',
useCompressParameters,
useCompressOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: "Settings",
isCollapsed: settingsCollapsed,
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: compressTips,
content: (
<CompressSettings
parameters={compressParams.parameters}
onParameterChange={compressParams.updateParameter}
disabled={endpointLoading}
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("compress.submit", "Compress"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleCompress,
disabled: !compressParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: compressOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("compress.title", "Compression Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+17 -56
View File
@@ -1,78 +1,39 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { useFileSelection } from "../contexts/file/fileHooks";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRemoveCertificateSignParameters } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignParameters";
import { useRemoveCertificateSignOperation } from "../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const RemoveCertificateSign = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const RemoveCertificateSign = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const removeCertificateSignParams = useRemoveCertificateSignParameters();
const removeCertificateSignOperation = useRemoveCertificateSignOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(removeCertificateSignParams.getEndpointName());
useEffect(() => {
removeCertificateSignOperation.resetResults();
onPreviewFile?.(null);
}, [removeCertificateSignParams.parameters]);
const handleRemoveSignature = async () => {
try {
await removeCertificateSignOperation.executeOperation(removeCertificateSignParams.parameters, selectedFiles);
if (removeCertificateSignOperation.files && onComplete) {
onComplete(removeCertificateSignOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("removeCertSign.error.failed", "Remove certificate signature operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "removeCertificateSign");
actions.setMode("viewer");
};
const handleSettingsReset = () => {
removeCertificateSignOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = removeCertificateSignOperation.files.length > 0 || removeCertificateSignOperation.downloadUrl !== null;
const base = useBaseTool(
'removeCertificateSign',
useRemoveCertificateSignParameters,
useRemoveCertificateSignOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
placeholder: t("removeCertSign.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [],
executeButton: {
text: t("removeCertSign.submit", "Remove Signature"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleRemoveSignature,
disabled: !removeCertificateSignParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: removeCertificateSignOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("removeCertSign.results.title", "Certificate Removal Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
@@ -80,4 +41,4 @@ const RemoveCertificateSign = ({ onPreviewFile, onComplete, onError }: BaseToolP
// Static method to get the operation hook for automation
RemoveCertificateSign.tool = () => useRemoveCertificateSignOperation;
export default RemoveCertificateSign as ToolComponent;
export default RemoveCertificateSign as ToolComponent;
+21 -61
View File
@@ -1,95 +1,55 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import RemovePasswordSettings from "../components/tools/removePassword/RemovePasswordSettings";
import { useRemovePasswordParameters } from "../hooks/tools/removePassword/useRemovePasswordParameters";
import { useRemovePasswordOperation } from "../hooks/tools/removePassword/useRemovePasswordOperation";
import { useRemovePasswordTips } from "../components/tooltips/useRemovePasswordTips";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const RemovePassword = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const RemovePassword = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const removePasswordParams = useRemovePasswordParameters();
const removePasswordOperation = useRemovePasswordOperation();
const removePasswordTips = useRemovePasswordTips();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(removePasswordParams.getEndpointName());
useEffect(() => {
removePasswordOperation.resetResults();
onPreviewFile?.(null);
}, [removePasswordParams.parameters]);
const handleRemovePassword = async () => {
try {
await removePasswordOperation.executeOperation(removePasswordParams.parameters, selectedFiles);
if (removePasswordOperation.files && onComplete) {
onComplete(removePasswordOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("removePassword.error.failed", "Remove password operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "removePassword");
};
const handleSettingsReset = () => {
removePasswordOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = removePasswordOperation.files.length > 0 || removePasswordOperation.downloadUrl !== null;
const passwordCollapsed = !hasFiles || hasResults;
const base = useBaseTool(
'removePassword',
useRemovePasswordParameters,
useRemovePasswordOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: t("removePassword.password.stepTitle", "Remove Password"),
isCollapsed: passwordCollapsed,
onCollapsedClick: hasResults ? handleSettingsReset : undefined,
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.hasResults ? base.handleSettingsReset : undefined,
tooltip: removePasswordTips,
content: (
<RemovePasswordSettings
parameters={removePasswordParams.parameters}
onParameterChange={removePasswordParams.updateParameter}
disabled={endpointLoading}
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("removePassword.submit", "Remove Password"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleRemovePassword,
disabled: !removePasswordParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: removePasswordOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("removePassword.results.title", "Decrypted PDFs"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+16 -55
View File
@@ -1,78 +1,39 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { useFileSelection } from "../contexts/file/fileHooks";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRepairParameters } from "../hooks/tools/repair/useRepairParameters";
import { useRepairOperation } from "../hooks/tools/repair/useRepairOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const Repair = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const Repair = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const repairParams = useRepairParameters();
const repairOperation = useRepairOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(repairParams.getEndpointName());
useEffect(() => {
repairOperation.resetResults();
onPreviewFile?.(null);
}, [repairParams.parameters]);
const handleRepair = async () => {
try {
await repairOperation.executeOperation(repairParams.parameters, selectedFiles);
if (repairOperation.files && onComplete) {
onComplete(repairOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("repair.error.failed", "Repair operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "repair");
actions.setMode("viewer");
};
const handleSettingsReset = () => {
repairOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = repairOperation.files.length > 0 || repairOperation.downloadUrl !== null;
const base = useBaseTool(
'repair',
useRepairParameters,
useRepairOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
placeholder: t("repair.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [],
executeButton: {
text: t("repair.submit", "Repair PDF"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleRepair,
disabled: !repairParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: repairOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("repair.results.title", "Repair Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+21 -58
View File
@@ -1,90 +1,53 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import SanitizeSettings from "../components/tools/sanitize/SanitizeSettings";
import { useSanitizeParameters } from "../hooks/tools/sanitize/useSanitizeParameters";
import { useSanitizeOperation } from "../hooks/tools/sanitize/useSanitizeOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const Sanitize = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const Sanitize = (props: BaseToolProps) => {
const { t } = useTranslation();
const { selectedFiles } = useFileSelection();
const sanitizeParams = useSanitizeParameters();
const sanitizeOperation = useSanitizeOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(sanitizeParams.getEndpointName());
useEffect(() => {
sanitizeOperation.resetResults();
onPreviewFile?.(null);
}, [sanitizeParams.parameters]);
const handleSanitize = async () => {
try {
await sanitizeOperation.executeOperation(sanitizeParams.parameters, selectedFiles);
if (sanitizeOperation.files && onComplete) {
onComplete(sanitizeOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("sanitize.error.generic", "Sanitization failed"));
}
}
};
const handleSettingsReset = () => {
sanitizeOperation.resetResults();
onPreviewFile?.(null);
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "sanitize");
};
const hasFiles = selectedFiles.length > 0;
const hasResults = sanitizeOperation.files.length > 0;
const settingsCollapsed = !hasFiles || hasResults;
const base = useBaseTool(
'sanitize',
useSanitizeParameters,
useSanitizeOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
placeholder: t("sanitize.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [
{
title: t("sanitize.steps.settings", "Settings"),
isCollapsed: settingsCollapsed,
onCollapsedClick: settingsCollapsed ? handleSettingsReset : undefined,
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
content: (
<SanitizeSettings
parameters={sanitizeParams.parameters}
onParameterChange={sanitizeParams.updateParameter}
disabled={endpointLoading}
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("sanitize.submit", "Sanitize PDF"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleSanitize,
disabled: !sanitizeParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: sanitizeOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("sanitize.sanitizationResults", "Sanitization Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+17 -56
View File
@@ -1,78 +1,39 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { useFileSelection } from "../contexts/file/fileHooks";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useSingleLargePageParameters } from "../hooks/tools/singleLargePage/useSingleLargePageParameters";
import { useSingleLargePageOperation } from "../hooks/tools/singleLargePage/useSingleLargePageOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const SingleLargePage = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const singleLargePageParams = useSingleLargePageParameters();
const singleLargePageOperation = useSingleLargePageOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(singleLargePageParams.getEndpointName());
useEffect(() => {
singleLargePageOperation.resetResults();
onPreviewFile?.(null);
}, [singleLargePageParams.parameters]);
const handleConvert = async () => {
try {
await singleLargePageOperation.executeOperation(singleLargePageParams.parameters, selectedFiles);
if (singleLargePageOperation.files && onComplete) {
onComplete(singleLargePageOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("pdfToSinglePage.error.failed", "Single large page operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "single-large-page");
actions.setMode("viewer");
};
const handleSettingsReset = () => {
singleLargePageOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = singleLargePageOperation.files.length > 0 || singleLargePageOperation.downloadUrl !== null;
const base = useBaseTool(
'singleLargePage',
useSingleLargePageParameters,
useSingleLargePageOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
placeholder: t("pdfToSinglePage.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [],
executeButton: {
text: t("pdfToSinglePage.submit", "Convert To Single Page"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleConvert,
disabled: !singleLargePageParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: singleLargePageOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("pdfToSinglePage.results.title", "Single Page Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
@@ -80,4 +41,4 @@ const SingleLargePage = ({ onPreviewFile, onComplete, onError }: BaseToolProps)
// Static method to get the operation hook for automation
SingleLargePage.tool = () => useSingleLargePageOperation;
export default SingleLargePage as ToolComponent;
export default SingleLargePage as ToolComponent;
+22 -69
View File
@@ -1,84 +1,37 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import SplitSettings from "../components/tools/split/SplitSettings";
import { useSplitParameters } from "../hooks/tools/split/useSplitParameters";
import { useSplitOperation } from "../hooks/tools/split/useSplitOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const Split = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const splitParams = useSplitParameters();
const splitOperation = useSplitOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(splitParams.getEndpointName());
useEffect(() => {
// Only reset results when parameters change, not when files change
splitOperation.resetResults();
onPreviewFile?.(null);
}, [splitParams.parameters]);
useEffect(() => {
// Reset results when selected files change (user selected different files)
if (selectedFiles.length > 0) {
splitOperation.resetResults();
onPreviewFile?.(null);
}
}, [selectedFiles]);
const handleSplit = async () => {
try {
await splitOperation.executeOperation(splitParams.parameters, selectedFiles);
if (splitOperation.files && onComplete) {
onComplete(splitOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : "Split operation failed");
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "split");
};
const handleSettingsReset = () => {
splitOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = splitOperation.files.length > 0 || splitOperation.downloadUrl !== null;
const settingsCollapsed = !hasFiles || hasResults;
const base = useBaseTool(
'split',
useSplitParameters,
useSplitOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
placeholder: "Select a PDF file in the main view to get started",
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: "Settings",
isCollapsed: settingsCollapsed,
onCollapsedClick: hasResults ? handleSettingsReset : undefined,
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.hasResults ? base.handleSettingsReset : undefined,
content: (
<SplitSettings
parameters={splitParams.parameters}
onParameterChange={splitParams.updateParameter}
disabled={endpointLoading}
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
@@ -86,15 +39,15 @@ const Split = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
executeButton: {
text: t("split.submit", "Split PDF"),
loadingText: t("loading"),
onClick: handleSplit,
isVisible: !hasResults,
disabled: !splitParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
isVisible: !base.hasResults,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: splitOperation,
isVisible: base.hasResults,
operation: base.operation,
title: "Split Results",
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
+17 -56
View File
@@ -1,78 +1,39 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileContext } from "../contexts/FileContext";
import { useNavigationActions } from "../contexts/NavigationContext";
import { useFileSelection } from "../contexts/file/fileHooks";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useUnlockPdfFormsParameters } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters";
import { useUnlockPdfFormsOperation } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const UnlockPdfForms = (props: BaseToolProps) => {
const { t } = useTranslation();
const { actions } = useNavigationActions();
const { selectedFiles } = useFileSelection();
const unlockPdfFormsParams = useUnlockPdfFormsParameters();
const unlockPdfFormsOperation = useUnlockPdfFormsOperation();
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(unlockPdfFormsParams.getEndpointName());
useEffect(() => {
unlockPdfFormsOperation.resetResults();
onPreviewFile?.(null);
}, [unlockPdfFormsParams.parameters]);
const handleUnlock = async () => {
try {
await unlockPdfFormsOperation.executeOperation(unlockPdfFormsParams.parameters, selectedFiles);
if (unlockPdfFormsOperation.files && onComplete) {
onComplete(unlockPdfFormsOperation.files);
}
} catch (error) {
if (onError) {
onError(error instanceof Error ? error.message : t("unlockPDFForms.error.failed", "Unlock PDF forms operation failed"));
}
}
};
const handleThumbnailClick = (file: File) => {
onPreviewFile?.(file);
sessionStorage.setItem("previousMode", "unlockPdfForms");
actions.setMode("viewer");
};
const handleSettingsReset = () => {
unlockPdfFormsOperation.resetResults();
onPreviewFile?.(null);
};
const hasFiles = selectedFiles.length > 0;
const hasResults = unlockPdfFormsOperation.files.length > 0 || unlockPdfFormsOperation.downloadUrl !== null;
const base = useBaseTool(
'unlockPdfForms',
useUnlockPdfFormsParameters,
useUnlockPdfFormsOperation,
props
);
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasFiles || hasResults,
selectedFiles: base.selectedFiles,
isCollapsed: base.hasFiles || base.hasResults,
placeholder: t("unlockPDFForms.files.placeholder", "Select a PDF file in the main view to get started"),
},
steps: [],
executeButton: {
text: t("unlockPDFForms.submit", "Unlock Forms"),
isVisible: !hasResults,
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: handleUnlock,
disabled: !unlockPdfFormsParams.validateParameters() || !hasFiles || !endpointEnabled,
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: hasResults,
operation: unlockPdfFormsOperation,
isVisible: base.hasResults,
operation: base.operation,
title: t("unlockPDFForms.results.title", "Unlocked Forms Results"),
onFileClick: handleThumbnailClick,
onFileClick: base.handleThumbnailClick,
},
});
};
@@ -80,4 +41,4 @@ const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) =
// Static method to get the operation hook for automation
UnlockPdfForms.tool = () => useUnlockPdfFormsOperation;
export default UnlockPdfForms as ToolComponent;
export default UnlockPdfForms as ToolComponent;