From a5693ee1164acd23ec31d313e53823d510721024 Mon Sep 17 00:00:00 2001 From: James Brunton Date: Thu, 18 Sep 2025 10:41:39 +0100 Subject: [PATCH] V2 change metadata (#4433) # Description of Changes Add Change Metadata tool --- frontend/package-lock.json | 24 +++ frontend/package.json | 1 + .../public/locales/en-GB/translation.json | 135 +++++++++++-- .../ChangeMetadataSingleStep.tsx | 99 ++++++++++ .../steps/AdvancedOptionsStep.tsx | 60 ++++++ .../steps/CustomMetadataStep.tsx | 74 +++++++ .../changeMetadata/steps/DeleteAllStep.tsx | 28 +++ .../steps/DocumentDatesStep.tsx | 42 ++++ .../steps/StandardMetadataStep.tsx | 71 +++++++ .../tooltips/useChangeMetadataTips.ts | 108 +++++++++++ .../src/data/useTranslatedToolRegistry.tsx | 9 +- .../useChangeMetadataOperation.test.ts | 144 ++++++++++++++ .../useChangeMetadataOperation.ts | 71 +++++++ .../useChangeMetadataParameters.test.ts | 168 ++++++++++++++++ .../useChangeMetadataParameters.ts | 136 +++++++++++++ .../changeMetadata/useMetadataExtraction.ts | 70 +++++++ frontend/src/index.tsx | 1 + frontend/src/services/pdfMetadataService.ts | 181 ++++++++++++++++++ frontend/src/services/pdfWorkerManager.ts | 7 +- frontend/src/tools/ChangeMetadata.tsx | 164 ++++++++++++++++ frontend/src/types/metadata.ts | 24 +++ 21 files changed, 1597 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/tools/changeMetadata/ChangeMetadataSingleStep.tsx create mode 100644 frontend/src/components/tools/changeMetadata/steps/AdvancedOptionsStep.tsx create mode 100644 frontend/src/components/tools/changeMetadata/steps/CustomMetadataStep.tsx create mode 100644 frontend/src/components/tools/changeMetadata/steps/DeleteAllStep.tsx create mode 100644 frontend/src/components/tools/changeMetadata/steps/DocumentDatesStep.tsx create mode 100644 frontend/src/components/tools/changeMetadata/steps/StandardMetadataStep.tsx create mode 100644 frontend/src/components/tooltips/useChangeMetadataTips.ts create mode 100644 frontend/src/hooks/tools/changeMetadata/useChangeMetadataOperation.test.ts create mode 100644 frontend/src/hooks/tools/changeMetadata/useChangeMetadataOperation.ts create mode 100644 frontend/src/hooks/tools/changeMetadata/useChangeMetadataParameters.test.ts create mode 100644 frontend/src/hooks/tools/changeMetadata/useChangeMetadataParameters.ts create mode 100644 frontend/src/hooks/tools/changeMetadata/useMetadataExtraction.ts create mode 100644 frontend/src/services/pdfMetadataService.ts create mode 100644 frontend/src/tools/ChangeMetadata.tsx create mode 100644 frontend/src/types/metadata.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 342f0512f..f16d1f9cb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "@emotion/styled": "^11.14.0", "@iconify/react": "^6.0.0", "@mantine/core": "^8.0.1", + "@mantine/dates": "^8.0.1", "@mantine/dropzone": "^8.0.1", "@mantine/hooks": "^8.0.1", "@mui/icons-material": "^7.1.0", @@ -1653,6 +1654,22 @@ "react-dom": "^18.x || ^19.x" } }, + "node_modules/@mantine/dates": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.0.1.tgz", + "integrity": "sha512-YCmV5jiGE9Ts2uhNS217IA1Hd5kAa8oaEtfnU0bS1sL36zKEf2s6elmzY718XdF8tFil0jJWAj0jiCrA3/udMg==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "8.0.1", + "@mantine/hooks": "8.0.1", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, "node_modules/@mantine/dropzone": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-8.0.1.tgz", @@ -4367,6 +4384,13 @@ "node": ">=18" } }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT", + "peer": true + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0b14a8ffc..214830cd2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "@emotion/styled": "^11.14.0", "@iconify/react": "^6.0.0", "@mantine/core": "^8.0.1", + "@mantine/dates": "^8.0.1", "@mantine/dropzone": "^8.0.1", "@mantine/hooks": "^8.0.1", "@mui/icons-material": "^7.1.0", diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index ccc811781..24ae3e1af 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1220,24 +1220,127 @@ }, "changeMetadata": { "tags": "Title,author,date,creation,time,publisher,producer,stats", - "title": "Change Metadata", "header": "Change Metadata", - "selectText": { - "1": "Please edit the variables you wish to change", - "2": "Delete all metadata", - "3": "Show Custom Metadata:", - "4": "Other Metadata:", - "5": "Add Custom Metadata Entry" + "submit": "Change", + "filenamePrefix": "metadata", + "settings": { + "title": "Metadata Settings" }, - "author": "Author:", - "creationDate": "Creation Date (yyyy/MM/dd HH:mm:ss):", - "creator": "Creator:", - "keywords": "Keywords:", - "modDate": "Modification Date (yyyy/MM/dd HH:mm:ss):", - "producer": "Producer:", - "subject": "Subject:", - "trapped": "Trapped:", - "submit": "Change" + "standardFields": { + "title": "Standard Fields" + }, + "deleteAll": { + "label": "Remove Existing Metadata", + "checkbox": "Delete all metadata" + }, + "title": { + "label": "Title", + "placeholder": "Document title" + }, + "author": { + "label": "Author", + "placeholder": "Document author" + }, + "subject": { + "label": "Subject", + "placeholder": "Document subject" + }, + "keywords": { + "label": "Keywords", + "placeholder": "Document keywords" + }, + "creator": { + "label": "Creator", + "placeholder": "Document creator" + }, + "producer": { + "label": "Producer", + "placeholder": "Document producer" + }, + "dates": { + "title": "Date Fields" + }, + "creationDate": { + "label": "Creation Date", + "placeholder": "Creation date" + }, + "modificationDate": { + "label": "Modification Date", + "placeholder": "Modification date" + }, + "trapped": { + "label": "Trapped Status", + "unknown": "Unknown", + "true": "True", + "false": "False" + }, + "advanced": { + "title": "Advanced Options" + }, + "customFields": { + "title": "Custom Metadata", + "description": "Add custom metadata fields to the document", + "add": "Add Field", + "key": "Key", + "keyPlaceholder": "Custom key", + "value": "Value", + "valuePlaceholder": "Custom value", + "remove": "Remove" + }, + "results": { + "title": "Updated PDFs" + }, + "error": { + "failed": "An error occurred while changing the PDF metadata." + }, + "tooltip": { + "header": { + "title": "PDF Metadata Overview" + }, + "standardFields": { + "title": "Standard Fields", + "text": "Common PDF metadata fields that describe the document.", + "bullet1": "Title: Document name or heading", + "bullet2": "Author: Person who created the document", + "bullet3": "Subject: Brief description of content", + "bullet4": "Keywords: Search terms for the document", + "bullet5": "Creator/Producer: Software used to create the PDF" + }, + "dates": { + "title": "Date Fields", + "text": "When the document was created and modified.", + "bullet1": "Creation Date: When original document was made", + "bullet2": "Modification Date: When last changed" + }, + "options": { + "title": "Additional Options", + "text": "Custom fields and privacy controls.", + "bullet1": "Custom Metadata: Add your own key-value pairs", + "bullet2": "Trapped Status: High-quality printing setting", + "bullet3": "Delete All: Remove all metadata for privacy" + }, + "deleteAll": { + "title": "Remove Existing Metadata", + "text": "Complete metadata deletion to ensure privacy." + }, + "customFields": { + "title": "Custom Metadata", + "text": "Add your own custom key-value metadata pairs.", + "bullet1": "Add any custom fields relevant to your document", + "bullet2": "Examples: Department, Project, Version, Status", + "bullet3": "Both key and value are required for each entry" + }, + "advanced": { + "title": "Advanced Options", + "trapped": { + "title": "Trapped Status", + "description": "Indicates if document is prepared for high-quality printing.", + "bullet1": "True: Document has been trapped for printing", + "bullet2": "False: Document has not been trapped", + "bullet3": "Unknown: Trapped status is not specified" + } + } + } }, "fileToPDF": { "tags": "transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint", diff --git a/frontend/src/components/tools/changeMetadata/ChangeMetadataSingleStep.tsx b/frontend/src/components/tools/changeMetadata/ChangeMetadataSingleStep.tsx new file mode 100644 index 000000000..ef95718e9 --- /dev/null +++ b/frontend/src/components/tools/changeMetadata/ChangeMetadataSingleStep.tsx @@ -0,0 +1,99 @@ +import { Stack, Divider, Text } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { ChangeMetadataParameters, createCustomMetadataFunctions } from "../../../hooks/tools/changeMetadata/useChangeMetadataParameters"; +import { useMetadataExtraction } from "../../../hooks/tools/changeMetadata/useMetadataExtraction"; +import DeleteAllStep from "./steps/DeleteAllStep"; +import StandardMetadataStep from "./steps/StandardMetadataStep"; +import DocumentDatesStep from "./steps/DocumentDatesStep"; +import AdvancedOptionsStep from "./steps/AdvancedOptionsStep"; + +interface ChangeMetadataSingleStepProps { + parameters: ChangeMetadataParameters; + onParameterChange: (key: K, value: ChangeMetadataParameters[K]) => void; + disabled?: boolean; +} + +const ChangeMetadataSingleStep = ({ + parameters, + onParameterChange, + disabled = false +}: ChangeMetadataSingleStepProps) => { + const { t } = useTranslation(); + + // Get custom metadata functions using the utility + const { addCustomMetadata, removeCustomMetadata, updateCustomMetadata } = createCustomMetadataFunctions( + parameters, + onParameterChange + ); + + // Extract metadata from uploaded files + const { isExtractingMetadata } = useMetadataExtraction({ + updateParameter: onParameterChange, + }); + + const isDeleteAllEnabled = parameters.deleteAll; + const fieldsDisabled = disabled || isDeleteAllEnabled || isExtractingMetadata; + + return ( + + {/* Delete All */} + + + {t('changeMetadata.deleteAll.label', 'Delete All Metadata')} + + + + + + + {/* Standard Metadata Fields */} + + + {t('changeMetadata.standardFields.title', 'Standard Metadata')} + + + + + + + {/* Document Dates */} + + + {t('changeMetadata.dates.title', 'Document Dates')} + + + + + + + {/* Advanced Options */} + + + {t('changeMetadata.advanced.title', 'Advanced Options')} + + + + + ); +}; + +export default ChangeMetadataSingleStep; diff --git a/frontend/src/components/tools/changeMetadata/steps/AdvancedOptionsStep.tsx b/frontend/src/components/tools/changeMetadata/steps/AdvancedOptionsStep.tsx new file mode 100644 index 000000000..0edffe21d --- /dev/null +++ b/frontend/src/components/tools/changeMetadata/steps/AdvancedOptionsStep.tsx @@ -0,0 +1,60 @@ +import { Stack, Select, Divider } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters"; +import { TrappedStatus } from "../../../../types/metadata"; +import CustomMetadataStep from "./CustomMetadataStep"; + +interface AdvancedOptionsStepProps { + parameters: ChangeMetadataParameters; + onParameterChange: (key: K, value: ChangeMetadataParameters[K]) => void; + disabled?: boolean; + addCustomMetadata: (key?: string, value?: string) => void; + removeCustomMetadata: (id: string) => void; + updateCustomMetadata: (id: string, key: string, value: string) => void; +} + +const AdvancedOptionsStep = ({ + parameters, + onParameterChange, + disabled = false, + addCustomMetadata, + removeCustomMetadata, + updateCustomMetadata +}: AdvancedOptionsStepProps) => { + const { t } = useTranslation(); + + return ( + + {/* Trapped Status */} +