diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index a9d38b142..e3bb3c0c6 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1020,7 +1020,18 @@ "tags": "remove,delete,form,field,readonly", "title": "Remove Read-Only from Form Fields", "header": "Unlock PDF Forms", - "submit": "Remove" + "submit": "Unlock Forms", + "description": "This tool will remove read-only restrictions from PDF form fields, making them editable and fillable.", + "filenamePrefix": "unlocked_forms", + "files": { + "placeholder": "Select a PDF file in the main view to get started" + }, + "error": { + "failed": "An error occurred whilst unlocking PDF forms." + }, + "results": { + "title": "Unlocked Forms Results" + } }, "changeMetadata": { "tags": "Title,author,date,creation,time,publisher,producer,stats", diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 43fbd72f1..541c19fb5 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -747,7 +747,18 @@ "tags": "remove,delete,form,field,readonly", "title": "Remove Read-Only from Form Fields", "header": "Unlock PDF Forms", - "submit": "Remove" + "submit": "Unlock Forms", + "description": "This tool will remove read-only restrictions from PDF form fields, making them editable and fillable.", + "filenamePrefix": "unlocked_forms", + "files": { + "placeholder": "Select a PDF file in the main view to get started" + }, + "error": { + "failed": "An error occurred while unlocking PDF forms." + }, + "results": { + "title": "Unlocked Forms Results" + } }, "changeMetadata": { "tags": "Title,author,date,creation,time,publisher,producer,stats", diff --git a/frontend/src/components/tools/unlockPdfForms/UnlockPdfFormsSettings.tsx b/frontend/src/components/tools/unlockPdfForms/UnlockPdfFormsSettings.tsx new file mode 100644 index 000000000..cc8697d7a --- /dev/null +++ b/frontend/src/components/tools/unlockPdfForms/UnlockPdfFormsSettings.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { UnlockPdfFormsParameters } from '../../../hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters'; + +interface UnlockPdfFormsSettingsProps { + parameters: UnlockPdfFormsParameters; + onParameterChange: (parameter: K, value: UnlockPdfFormsParameters[K]) => void; + disabled?: boolean; +} + +const UnlockPdfFormsSettings: React.FC = ({ + parameters, + onParameterChange, // Unused - kept for interface consistency and future extensibility + disabled = false +}) => { + const { t } = useTranslation(); + + return ( +
+

+ {t('unlockPDFForms.description', 'This tool will remove read-only restrictions from PDF form fields, making them editable and fillable.')} +

+
+ ); +}; + +export default UnlockPdfFormsSettings; \ No newline at end of file diff --git a/frontend/src/data/useTranslatedToolRegistry.tsx b/frontend/src/data/useTranslatedToolRegistry.tsx index 364c54211..db6aca089 100644 --- a/frontend/src/data/useTranslatedToolRegistry.tsx +++ b/frontend/src/data/useTranslatedToolRegistry.tsx @@ -11,8 +11,10 @@ import RemovePassword from '../tools/RemovePassword'; import { SubcategoryId, ToolCategory, ToolRegistry } from './toolsTaxonomy'; import AddWatermark from '../tools/AddWatermark'; import Repair from '../tools/Repair'; +import UnlockPdfForms from '../tools/UnlockPdfForms'; import RemoveCertificateSign from '../tools/RemoveCertificateSign'; + // Hook to get the translated tool registry export function useFlatToolRegistry(): ToolRegistry { const { t } = useTranslation(); @@ -96,11 +98,13 @@ export function useFlatToolRegistry(): ToolRegistry { "unlock-pdf-forms": { icon: preview_off, name: t("home.unlockPDFForms.title", "Unlock PDF Forms"), - component: null, + component: UnlockPdfForms, view: "security", description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."), category: ToolCategory.STANDARD_TOOLS, - subcategory: SubcategoryId.DOCUMENT_SECURITY + subcategory: SubcategoryId.DOCUMENT_SECURITY, + maxFiles: -1, + endpoints: ["unlock-pdf-forms"] }, "manage-certificates": { icon: license, diff --git a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts new file mode 100644 index 000000000..3b648762b --- /dev/null +++ b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation.ts @@ -0,0 +1,23 @@ +import { useTranslation } from 'react-i18next'; +import { useToolOperation } from '../shared/useToolOperation'; +import { createStandardErrorHandler } from '../../../utils/toolErrorHandler'; +import { UnlockPdfFormsParameters } from './useUnlockPdfFormsParameters'; + +export const useUnlockPdfFormsOperation = () => { + const { t } = useTranslation(); + + const buildFormData = (parameters: UnlockPdfFormsParameters, file: File): FormData => { + const formData = new FormData(); + formData.append("fileInput", file); + return formData; + }; + + return useToolOperation({ + operationType: 'unlockPdfForms', + endpoint: '/api/v1/misc/unlock-pdf-forms', + buildFormData, + filePrefix: t('unlockPDFForms.filenamePrefix', 'unlocked_forms') + '_', + multiFileEndpoint: false, + getErrorMessage: createStandardErrorHandler(t('unlockPDFForms.error.failed', 'An error occurred while unlocking PDF forms.')) + }); +}; \ No newline at end of file diff --git a/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters.ts b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters.ts new file mode 100644 index 000000000..ad2536643 --- /dev/null +++ b/frontend/src/hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters.ts @@ -0,0 +1,19 @@ +import { BaseParameters } from '../../../types/parameters'; +import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters'; + +export interface UnlockPdfFormsParameters extends BaseParameters { + // Extends BaseParameters - ready for future parameter additions if needed +} + +export const defaultParameters: UnlockPdfFormsParameters = { + // No parameters needed +}; + +export type UnlockPdfFormsParametersHook = BaseParametersHook; + +export const useUnlockPdfFormsParameters = (): UnlockPdfFormsParametersHook => { + return useBaseParameters({ + defaultParameters, + endpointName: 'unlock-pdf-forms', + }); +}; \ No newline at end of file diff --git a/frontend/src/tools/UnlockPdfForms.tsx b/frontend/src/tools/UnlockPdfForms.tsx new file mode 100644 index 000000000..b8aee7894 --- /dev/null +++ b/frontend/src/tools/UnlockPdfForms.tsx @@ -0,0 +1,80 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { useEndpointEnabled } from "../hooks/useEndpointConfig"; +import { useFileContext } from "../contexts/FileContext"; +import { useToolFileSelection } from "../contexts/FileSelectionContext"; + +import { createToolFlow } from "../components/tools/shared/createToolFlow"; + +import { useUnlockPdfFormsParameters } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsParameters"; +import { useUnlockPdfFormsOperation } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation"; +import { BaseToolProps } from "../types/tool"; + +const UnlockPdfForms = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { + const { t } = useTranslation(); + const { setCurrentMode } = useFileContext(); + const { selectedFiles } = useToolFileSelection(); + + 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"); + setCurrentMode("viewer"); + }; + + const handleSettingsReset = () => { + unlockPdfFormsOperation.resetResults(); + onPreviewFile?.(null); + setCurrentMode("unlockPdfForms"); + }; + + const hasFiles = selectedFiles.length > 0; + const hasResults = unlockPdfFormsOperation.files.length > 0 || unlockPdfFormsOperation.downloadUrl !== null; + + return createToolFlow({ + files: { + selectedFiles, + isCollapsed: hasFiles || 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, + loadingText: t("loading"), + onClick: handleUnlock, + disabled: !unlockPdfFormsParams.validateParameters() || !hasFiles || !endpointEnabled, + }, + review: { + isVisible: hasResults, + operation: unlockPdfFormsOperation, + title: t("unlockPDFForms.results.title", "Unlocked Forms Results"), + onFileClick: handleThumbnailClick, + }, + }); +}; + +export default UnlockPdfForms; \ No newline at end of file diff --git a/frontend/src/types/fileContext.ts b/frontend/src/types/fileContext.ts index b868bf0e6..6e2a2ef93 100644 --- a/frontend/src/types/fileContext.ts +++ b/frontend/src/types/fileContext.ts @@ -20,6 +20,7 @@ export type ModeType = | 'watermark' | 'removePassword' | 'repair' + | 'unlockPdfForms' | 'removeCertificateSign'; export type ViewType = 'viewer' | 'pageEditor' | 'fileEditor';