V2 change metadata (#4433)

# Description of Changes
Add Change Metadata tool
This commit is contained in:
James Brunton
2025-09-18 10:41:39 +01:00
committed by GitHub
parent 756cbc4780
commit a5693ee116
21 changed files with 1597 additions and 20 deletions

View File

@@ -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: <K extends keyof ChangeMetadataParameters>(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 (
<Stack gap="md">
{/* Delete All */}
<Stack gap="md">
<Text size="sm" fw={500}>
{t('changeMetadata.deleteAll.label', 'Delete All Metadata')}
</Text>
<DeleteAllStep
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
/>
</Stack>
<Divider />
{/* Standard Metadata Fields */}
<Stack gap="md">
<Text size="sm" fw={500}>
{t('changeMetadata.standardFields.title', 'Standard Metadata')}
</Text>
<StandardMetadataStep
parameters={parameters}
onParameterChange={onParameterChange}
disabled={fieldsDisabled}
/>
</Stack>
<Divider />
{/* Document Dates */}
<Stack gap="md">
<Text size="sm" fw={500}>
{t('changeMetadata.dates.title', 'Document Dates')}
</Text>
<DocumentDatesStep
parameters={parameters}
onParameterChange={onParameterChange}
disabled={fieldsDisabled}
/>
</Stack>
<Divider />
{/* Advanced Options */}
<Stack gap="md">
<Text size="sm" fw={500}>
{t('changeMetadata.advanced.title', 'Advanced Options')}
</Text>
<AdvancedOptionsStep
parameters={parameters}
onParameterChange={onParameterChange}
disabled={fieldsDisabled}
addCustomMetadata={addCustomMetadata}
removeCustomMetadata={removeCustomMetadata}
updateCustomMetadata={updateCustomMetadata}
/>
</Stack>
</Stack>
);
};
export default ChangeMetadataSingleStep;

View File

@@ -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: <K extends keyof ChangeMetadataParameters>(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 (
<Stack gap="md">
{/* Trapped Status */}
<Select
label={t('changeMetadata.trapped.label', 'Trapped Status')}
value={parameters.trapped}
onChange={(value) => {
if (value) {
onParameterChange('trapped', value as TrappedStatus);
}
}}
disabled={disabled || parameters.deleteAll}
data={[
{ value: TrappedStatus.UNKNOWN, label: t('changeMetadata.trapped.unknown', 'Unknown') },
{ value: TrappedStatus.TRUE, label: t('changeMetadata.trapped.true', 'True') },
{ value: TrappedStatus.FALSE, label: t('changeMetadata.trapped.false', 'False') }
]}
/>
<Divider />
{/* Custom Metadata */}
<CustomMetadataStep
parameters={parameters}
onParameterChange={onParameterChange}
disabled={disabled}
addCustomMetadata={addCustomMetadata}
removeCustomMetadata={removeCustomMetadata}
updateCustomMetadata={updateCustomMetadata}
/>
</Stack>
);
};
export default AdvancedOptionsStep;

View File

@@ -0,0 +1,74 @@
import { Stack, TextInput, Button, Group, Text } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
interface CustomMetadataStepProps {
parameters: ChangeMetadataParameters;
onParameterChange: <K extends keyof ChangeMetadataParameters>(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 CustomMetadataStep = ({
parameters,
disabled = false,
addCustomMetadata,
removeCustomMetadata,
updateCustomMetadata
}: CustomMetadataStepProps) => {
const { t } = useTranslation();
return (
<Stack gap="sm">
<Group justify="space-between" align="center">
<Text size="sm" fw={500}>
{t('changeMetadata.customFields.title', 'Custom Metadata')}
</Text>
<Button
size="xs"
variant="light"
onClick={() => addCustomMetadata()}
disabled={disabled}
>
{t('changeMetadata.customFields.add', 'Add Field')}
</Button>
</Group>
{parameters.customMetadata.length > 0 && (
<Text size="xs" c="dimmed">
{t('changeMetadata.customFields.description', 'Add custom metadata fields to the document')}
</Text>
)}
{parameters.customMetadata.map((entry) => (
<Stack key={entry.id} gap="xs">
<TextInput
placeholder={t('changeMetadata.customFields.keyPlaceholder', 'Custom key')}
value={entry.key}
onChange={(e) => updateCustomMetadata(entry.id, e.target.value, entry.value)}
disabled={disabled}
/>
<TextInput
placeholder={t('changeMetadata.customFields.valuePlaceholder', 'Custom value')}
value={entry.value}
onChange={(e) => updateCustomMetadata(entry.id, entry.key, e.target.value)}
disabled={disabled}
/>
<Button
size="xs"
variant="light"
color="red"
onClick={() => removeCustomMetadata(entry.id)}
disabled={disabled}
>
{t('changeMetadata.customFields.remove', 'Remove')}
</Button>
</Stack>
))}
</Stack>
);
};
export default CustomMetadataStep;

View File

@@ -0,0 +1,28 @@
import { Checkbox } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
interface DeleteAllStepProps {
parameters: ChangeMetadataParameters;
onParameterChange: <K extends keyof ChangeMetadataParameters>(key: K, value: ChangeMetadataParameters[K]) => void;
disabled?: boolean;
}
const DeleteAllStep = ({
parameters,
onParameterChange,
disabled = false
}: DeleteAllStepProps) => {
const { t } = useTranslation();
return (
<Checkbox
label={t('changeMetadata.deleteAll.checkbox', 'Delete all metadata')}
checked={parameters.deleteAll}
onChange={(e) => onParameterChange('deleteAll', e.target.checked)}
disabled={disabled}
/>
);
};
export default DeleteAllStep;

View File

@@ -0,0 +1,42 @@
import { Stack } from "@mantine/core";
import { DateTimePicker } from "@mantine/dates";
import { useTranslation } from "react-i18next";
import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
interface DocumentDatesStepProps {
parameters: ChangeMetadataParameters;
onParameterChange: <K extends keyof ChangeMetadataParameters>(key: K, value: ChangeMetadataParameters[K]) => void;
disabled?: boolean;
}
const DocumentDatesStep = ({
parameters,
onParameterChange,
disabled = false
}: DocumentDatesStepProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
<DateTimePicker
label={t('changeMetadata.creationDate.label', 'Creation Date')}
placeholder={t('changeMetadata.creationDate.placeholder', 'Creation date')}
value={parameters.creationDate}
onChange={(date) => onParameterChange('creationDate', date ? new Date(date) : null)}
disabled={disabled}
clearable
/>
<DateTimePicker
label={t('changeMetadata.modificationDate.label', 'Modification Date')}
placeholder={t('changeMetadata.modificationDate.placeholder', 'Modification date')}
value={parameters.modificationDate}
onChange={(date) => onParameterChange('modificationDate', date ? new Date(date) : null)}
disabled={disabled}
clearable
/>
</Stack>
);
};
export default DocumentDatesStep;

View File

@@ -0,0 +1,71 @@
import { Stack, TextInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
interface StandardMetadataStepProps {
parameters: ChangeMetadataParameters;
onParameterChange: <K extends keyof ChangeMetadataParameters>(key: K, value: ChangeMetadataParameters[K]) => void;
disabled?: boolean;
}
const StandardMetadataStep = ({
parameters,
onParameterChange,
disabled = false
}: StandardMetadataStepProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
<TextInput
label={t('changeMetadata.title.label', 'Title')}
placeholder={t('changeMetadata.title.placeholder', 'Document title')}
value={parameters.title}
onChange={(e) => onParameterChange('title', e.target.value)}
disabled={disabled}
/>
<TextInput
label={t('changeMetadata.author.label', 'Author')}
placeholder={t('changeMetadata.author.placeholder', 'Document author')}
value={parameters.author}
onChange={(e) => onParameterChange('author', e.target.value)}
disabled={disabled}
/>
<TextInput
label={t('changeMetadata.subject.label', 'Subject')}
placeholder={t('changeMetadata.subject.placeholder', 'Document subject')}
value={parameters.subject}
onChange={(e) => onParameterChange('subject', e.target.value)}
disabled={disabled}
/>
<TextInput
label={t('changeMetadata.keywords.label', 'Keywords')}
placeholder={t('changeMetadata.keywords.placeholder', 'Document keywords')}
value={parameters.keywords}
onChange={(e) => onParameterChange('keywords', e.target.value)}
disabled={disabled}
/>
<TextInput
label={t('changeMetadata.creator.label', 'Creator')}
placeholder={t('changeMetadata.creator.placeholder', 'Document creator')}
value={parameters.creator}
onChange={(e) => onParameterChange('creator', e.target.value)}
disabled={disabled}
/>
<TextInput
label={t('changeMetadata.producer.label', 'Producer')}
placeholder={t('changeMetadata.producer.placeholder', 'Document producer')}
value={parameters.producer}
onChange={(e) => onParameterChange('producer', e.target.value)}
disabled={disabled}
/>
</Stack>
);
};
export default StandardMetadataStep;

View File

@@ -0,0 +1,108 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useDeleteAllTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("changeMetadata.tooltip.deleteAll.title", "Remove Existing Metadata")
},
tips: [
{
description: t("changeMetadata.tooltip.deleteAll.text", "Complete metadata deletion to ensure privacy."),
}
]
};
};
export const useStandardMetadataTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("changeMetadata.tooltip.standardFields.title", "Standard Fields")
},
tips: [
{
description: t("changeMetadata.tooltip.standardFields.text", "Common PDF metadata fields that describe the document."),
bullets: [
t("changeMetadata.tooltip.standardFields.bullet1", "Title: Document name or heading"),
t("changeMetadata.tooltip.standardFields.bullet2", "Author: Person who created the document"),
t("changeMetadata.tooltip.standardFields.bullet3", "Subject: Brief description of content"),
t("changeMetadata.tooltip.standardFields.bullet4", "Keywords: Search terms for the document"),
t("changeMetadata.tooltip.standardFields.bullet5", "Creator/Producer: Software used to create the PDF")
]
}
]
};
};
export const useDocumentDatesTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("changeMetadata.tooltip.dates.title", "Date Fields")
},
tips: [
{
description: t("changeMetadata.tooltip.dates.text", "When the document was created and modified."),
bullets: [
t("changeMetadata.tooltip.dates.bullet1", "Creation Date: When original document was made"),
t("changeMetadata.tooltip.dates.bullet2", "Modification Date: When last changed"),
]
}
]
};
};
export const useCustomMetadataTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("changeMetadata.tooltip.customFields.title", "Custom Metadata")
},
tips: [
{
description: t("changeMetadata.tooltip.customFields.text", "Add your own custom key-value metadata pairs."),
bullets: [
t("changeMetadata.tooltip.customFields.bullet1", "Add any custom fields relevant to your document"),
t("changeMetadata.tooltip.customFields.bullet2", "Examples: Department, Project, Version, Status"),
t("changeMetadata.tooltip.customFields.bullet3", "Both key and value are required for each entry")
]
}
]
};
};
export const useAdvancedOptionsTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("changeMetadata.tooltip.advanced.title", "Advanced Options")
},
tips: [
{
title: t("changeMetadata.tooltip.advanced.trapped.title", "Trapped Status"),
description: t("changeMetadata.tooltip.advanced.trapped.description", "Indicates if document is prepared for high-quality printing."),
bullets: [
t("changeMetadata.tooltip.advanced.trapped.bullet1", "True: Document has been trapped for printing"),
t("changeMetadata.tooltip.advanced.trapped.bullet2", "False: Document has not been trapped"),
t("changeMetadata.tooltip.advanced.trapped.bullet3", "Unknown: Trapped status is not specified")
]
},
{
title: t("changeMetadata.tooltip.customFields.title", "Custom Metadata"),
description: t("changeMetadata.tooltip.customFields.text", "Add your own custom key-value metadata pairs."),
bullets: [
t("changeMetadata.tooltip.customFields.bullet1", "Add any custom fields relevant to your document"),
t("changeMetadata.tooltip.customFields.bullet2", "Examples: Department, Project, Version, Status"),
t("changeMetadata.tooltip.customFields.bullet3", "Both key and value are required for each entry")
]
}
]
};
};