diff --git a/frontend/src/hooks/tools/shared/useBaseTool.ts b/frontend/src/hooks/tools/shared/useBaseTool.ts new file mode 100644 index 000000000..c18444546 --- /dev/null +++ b/frontend/src/hooks/tools/shared/useBaseTool.ts @@ -0,0 +1,118 @@ +import { useEffect, useCallback } from 'react'; +import { useFileSelection } from '../../../contexts/FileContext'; +import { useEndpointEnabled } from '../../useEndpointConfig'; +import { BaseToolProps } from '../../../types/tool'; +import { ToolOperationHook } from './useToolOperation'; +import { BaseParametersHook } from './useBaseParameters'; + +interface BaseToolReturn { + // File management + selectedFiles: File[]; + + // Tool-specific hooks + params: BaseParametersHook; + operation: ToolOperationHook; + + // Endpoint validation + endpointEnabled: boolean | null; + endpointLoading: boolean; + + // Standard handlers + handleExecute: () => Promise; + handleThumbnailClick: (file: File) => void; + handleSettingsReset: () => void; + + // Standard computed state + hasFiles: boolean; + hasResults: boolean; + settingsCollapsed: boolean; +} + +/** + * Base tool hook for tool components. Manages standard behaviour for tools. + */ +export function useBaseTool( + toolName: string, + useParams: () => BaseParametersHook, + useOperation: () => ToolOperationHook, + props: BaseToolProps, +): BaseToolReturn { + const { onPreviewFile, onComplete, onError } = props; + + // File selection + const { selectedFiles } = useFileSelection(); + + // Tool-specific hooks + const params = useParams(); + const operation = useOperation(); + + // Endpoint validation using parameters hook + const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(params.getEndpointName()); + + // Reset results when parameters change + useEffect(() => { + operation.resetResults(); + onPreviewFile?.(null); + }, [params.parameters]); + + // Reset results when selected files change + useEffect(() => { + if (selectedFiles.length > 0) { + operation.resetResults(); + onPreviewFile?.(null); + } + }, [selectedFiles.length]); + + // Standard handlers + const handleExecute = useCallback(async () => { + try { + await operation.executeOperation(params.parameters, selectedFiles); + if (operation.files && onComplete) { + onComplete(operation.files); + } + } catch (error) { + if (onError) { + const message = error instanceof Error ? error.message : `${toolName} operation failed`; + onError(message); + } + } + }, [operation, params.parameters, selectedFiles, onComplete, onError, toolName]); + + const handleThumbnailClick = useCallback((file: File) => { + onPreviewFile?.(file); + sessionStorage.setItem('previousMode', toolName); + }, [onPreviewFile, toolName]); + + const handleSettingsReset = useCallback(() => { + operation.resetResults(); + onPreviewFile?.(null); + }, [operation, onPreviewFile]); + + // Standard computed state + const hasFiles = selectedFiles.length > 0; + const hasResults = operation.files.length > 0 || operation.downloadUrl !== null; + const settingsCollapsed = !hasFiles || hasResults; + + return { + // File management + selectedFiles, + + // Tool-specific hooks + params, + operation, + + // Endpoint validation + endpointEnabled, + endpointLoading, + + // Handlers + handleExecute, + handleThumbnailClick, + handleSettingsReset, + + // State + hasFiles, + hasResults, + settingsCollapsed + }; +} diff --git a/frontend/src/tools/ChangePermissions.tsx b/frontend/src/tools/ChangePermissions.tsx index e9a4eaa80..5f6582a76 100644 --- a/frontend/src/tools/ChangePermissions.tsx +++ b/frontend/src/tools/ChangePermissions.tsx @@ -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: ( ), }, ], 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, }, }); }; diff --git a/frontend/src/tools/Compress.tsx b/frontend/src/tools/Compress.tsx index 91dd89e86..bb57e70a4 100644 --- a/frontend/src/tools/Compress.tsx +++ b/frontend/src/tools/Compress.tsx @@ -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: ( ), }, ], 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, }, }); }; diff --git a/frontend/src/tools/RemoveCertificateSign.tsx b/frontend/src/tools/RemoveCertificateSign.tsx index c9a3f4b82..8583b34c6 100644 --- a/frontend/src/tools/RemoveCertificateSign.tsx +++ b/frontend/src/tools/RemoveCertificateSign.tsx @@ -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; \ No newline at end of file +export default RemoveCertificateSign as ToolComponent; diff --git a/frontend/src/tools/RemovePassword.tsx b/frontend/src/tools/RemovePassword.tsx index e84cf6e6c..9e8b1f5f4 100644 --- a/frontend/src/tools/RemovePassword.tsx +++ b/frontend/src/tools/RemovePassword.tsx @@ -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: ( ), }, ], 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, }, }); }; diff --git a/frontend/src/tools/Repair.tsx b/frontend/src/tools/Repair.tsx index f5f09017a..61d3986b6 100644 --- a/frontend/src/tools/Repair.tsx +++ b/frontend/src/tools/Repair.tsx @@ -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, }, }); }; diff --git a/frontend/src/tools/Sanitize.tsx b/frontend/src/tools/Sanitize.tsx index 274331fd6..d6f9bbe5d 100644 --- a/frontend/src/tools/Sanitize.tsx +++ b/frontend/src/tools/Sanitize.tsx @@ -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: ( ), }, ], 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, }, }); }; diff --git a/frontend/src/tools/SingleLargePage.tsx b/frontend/src/tools/SingleLargePage.tsx index d071c2b99..dc71c83cd 100644 --- a/frontend/src/tools/SingleLargePage.tsx +++ b/frontend/src/tools/SingleLargePage.tsx @@ -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; \ No newline at end of file +export default SingleLargePage as ToolComponent; diff --git a/frontend/src/tools/Split.tsx b/frontend/src/tools/Split.tsx index 06c7743fd..0c94b6cef 100644 --- a/frontend/src/tools/Split.tsx +++ b/frontend/src/tools/Split.tsx @@ -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: ( ), }, @@ -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, }, }); }; diff --git a/frontend/src/tools/UnlockPdfForms.tsx b/frontend/src/tools/UnlockPdfForms.tsx index 6c5bd4cb5..65445290f 100644 --- a/frontend/src/tools/UnlockPdfForms.tsx +++ b/frontend/src/tools/UnlockPdfForms.tsx @@ -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; \ No newline at end of file +export default UnlockPdfForms as ToolComponent;